From 75340ab9def8dee984c4eae17ad9a773c08c6bc7 Mon Sep 17 00:00:00 2001 From: Donavan Becker Date: Wed, 11 Sep 2024 06:10:14 -0500 Subject: [PATCH] v3.8.0 (#1040) ## [3.8.0](https://github.com/OpenWonderLabs/homebridge-switchbot/releases/tag/v3.8.0) (2024-09-11) ### What's Changed - Added specific macOS Bluetooth permission instructions to Readme [#1026](https://github.com/OpenWonderLabs/homebridge-switchbot/pull/1026), Thanks [@rSffsE](https://github.com/rSffsE) - Added partial support for `Roller Shade` deviceType. Currently only supports status. - Added `silentModeSwitch` config option for both `Curtain` & `Blind Titl` deviceTypes, allowing two switches to be display for Closing and Moding Mode. If turned on then Silent Mode is enabled. - Added option to allow invalid Characters in displayName with config `allowInvalidCharacters` - Added `dry` config option to enable Dry Status support for Water Detector - Fixed Platform BLE Scanning events not registering - Fix `On` state for robot vacuum cleaners [#1028](https://github.com/OpenWonderLabs/homebridge-switchbot/pull/1028), Thanks [@JannThomas](https://github.com/JannThomas) - Housekeeping and updated dependencies. **Full Changelog**: https://github.com/OpenWonderLabs/homebridge-switchbot/compare/v3.7.0...v3.8.0 --- CHANGELOG.md | 14 + README.md | 14 + assets/security-privacy-bluetooth.png | Bin 0 -> 487676 bytes config.schema.json | 85 +- eslint.config.js | 141 +- package-lock.json | 19428 +++++++++++++++--------- package.json | 119 +- src/device/blindtilt.ts | 993 +- src/device/bot.ts | 1453 +- src/device/ceilinglight.ts | 727 +- src/device/colorbulb.ts | 894 +- src/device/contact.ts | 455 +- src/device/curtain.ts | 985 +- src/device/device.ts | 1060 +- src/device/fan.ts | 681 +- src/device/hub.ts | 427 +- src/device/humidifier.ts | 673 +- src/device/iosensor.ts | 420 +- src/device/lightstrip.ts | 795 +- src/device/lock.ts | 574 +- src/device/meter.ts | 422 +- src/device/meterplus.ts | 421 +- src/device/motion.ts | 388 +- src/device/plug.ts | 385 +- src/device/robotvacuumcleaner.ts | 549 +- src/device/waterdetector.ts | 404 +- src/homebridge-ui/server.ts | 31 +- src/index.ts | 11 +- src/irdevice/airconditioner.ts | 603 +- src/irdevice/airpurifier.ts | 267 +- src/irdevice/camera.ts | 122 +- src/irdevice/fan.ts | 229 +- src/irdevice/irdevice.ts | 405 +- src/irdevice/light.ts | 262 +- src/irdevice/other.ts | 878 +- src/irdevice/tv.ts | 332 +- src/irdevice/vacuumcleaner.ts | 114 +- src/irdevice/waterheater.ts | 119 +- src/platform.ts | 3172 ++-- src/settings.ts | 342 +- src/types/bledevicestatus.ts | 508 +- src/types/devicelist.ts | 158 +- src/types/deviceresponse.ts | 25 +- src/types/devicestatus.ts | 248 +- src/types/devicewebhookstatus.ts | 257 +- src/types/irdevicelist.ts | 20 +- src/types/pushbody.ts | 10 +- src/utils.ts | 129 +- tsconfig.json | 19 +- 49 files changed, 22388 insertions(+), 18380 deletions(-) create mode 100644 assets/security-privacy-bluetooth.png diff --git a/CHANGELOG.md b/CHANGELOG.md index 76909774..813461e1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,20 @@ All notable changes to this project will be documented in this file. This project uses [Semantic Versioning](https://semver.org/) +## [3.8.0](https://github.com/OpenWonderLabs/homebridge-switchbot/releases/tag/v3.8.0) (2024-09-11) + +### What's Changed +- Added specific macOS Bluetooth permission instructions to Readme [#1026](https://github.com/OpenWonderLabs/homebridge-switchbot/pull/1026), Thanks [@rSffsE](https://github.com/rSffsE) +- Added partial support for `Roller Shade` deviceType. Currently only supports status. +- Added `silentModeSwitch` config option for both `Curtain` & `Blind Titl` deviceTypes, allowing two switches to be display for Closing and Moding Mode. If turned on then Silent Mode is enabled. +- Added option to allow invalid Characters in displayName with config `allowInvalidCharacters` +- Added `dry` config option to enable Dry Status support for Water Detector +- Fixed Platform BLE Scanning events not registering +- Fix `On` state for robot vacuum cleaners [#1028](https://github.com/OpenWonderLabs/homebridge-switchbot/pull/1028), Thanks [@JannThomas](https://github.com/JannThomas) +- Housekeeping and updated dependencies. + +**Full Changelog**: https://github.com/OpenWonderLabs/homebridge-switchbot/compare/v3.7.0...v3.8.0 + ## [3.7.0](https://github.com/OpenWonderLabs/homebridge-switchbot/releases/tag/v3.7.0) (2024-07-21) ### What's Changed diff --git a/README.md b/README.md index 2c3bd619..be9e2434 100644 --- a/README.md +++ b/README.md @@ -65,6 +65,20 @@ This lists all discovered Bluetooth devices. The BLE address of the SwitchBot device should be included in this list, otherwise your computer does not discover it. +- ### If using MacOS + 1. Manually grant Bluetooth access in System Settings UI for `Security & Privacy -> Privacy` to the node executable, eg `/usr/local/bin/node` + ![Security & Privacy -> Privacy](assets/security-privacy-bluetooth.png) + (This is what is intended in documentation for the noble bluetooth package [prerequisites](https://github.com/abandonware/noble#prerequisites) by "Add terminal app", however for HomeBridge it is `node` that needs the permission granted, not `terminal`. + Without this step, then you will receive the following error when the swichbot plugin launches, which will cause Homebridge or the child bridge process to restart: + ``` + Error: Failed to initialize the Noble object: unauthorized + at Noble. (file:///usr/local/lib/node_modules/@switchbot/homebridge-switchbot/node_modules/node-switchbot/src/switchbot.ts:244:19) + at Object.onceWrapper (node:events:629:26) + at Noble.emit (node:events:514:28) + at Noble.onStateChange (/usr/local/lib/node_modules/@switchbot/homebridge-switchbot/node_modules/@stoprocent/noble/lib/noble.js:92:8) + at NobleMac.emit (node:events:514:28) + ``` + ## Supported SwitchBot Devices - [SwitchBot Humidifier](https://www.switch-bot.com/products/switchbot-smart-humidifier) diff --git a/assets/security-privacy-bluetooth.png b/assets/security-privacy-bluetooth.png new file mode 100644 index 0000000000000000000000000000000000000000..7aceaf0e4695b3a66ca7c673d3b390d7ddd6ce5c GIT binary patch literal 487676 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¬YT*gj8O6?7X@-FH>mfP)FoH@ogRqf#ZZK&$)Y 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

mnWg_RV5P;elDrNBLk;;nPcfH; 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 literal 0 HcmV?d00001 diff --git a/config.schema.json b/config.schema.json index fa707cc7..50dfd1f3 100644 --- a/config.schema.json +++ b/config.schema.json @@ -221,6 +221,12 @@ "Robot Vacuum Cleaner S1 Plus" ] }, + { + "title": "Roller Shade", + "enum": [ + "Roller Shade" + ] + }, { "title": "Smart Fan", "enum": [ @@ -587,6 +593,14 @@ "condition": { "functionBody": "return (model.options && model.options.devices && !model.options.devices[arrayIndices].hide_device && model.options.devices[arrayIndices].configDeviceType === 'Water Detector' && model.options.devices[arrayIndices].deviceId);" } + }, + "dry": { + "title": "Water Detector's Dry Alert", + "type": "boolean", + "description": "If true, the Water Detector will send an alert when it is dry.", + "condition": { + "functionBody": "return (model.options && model.options.devices && !model.options.devices[arrayIndices].hide_device && model.options.devices[arrayIndices].configDeviceType === 'Water Detector' && model.options.devices[arrayIndices].deviceId);" + } } } }, @@ -699,6 +713,13 @@ "functionBody": "return (model.options && model.options.devices && !model.options.devices[arrayIndices].hide_device && (model.options.devices[arrayIndices].configDeviceType === 'Curtain' || model.options.devices[arrayIndices].configDeviceType === 'Curtain3') && model.options.devices[arrayIndices].deviceId);" } }, + "silentModeSwitch": { + "title": "Enable Curtain Silent Mode Switches", + "type": "boolean", + "condition": { + "functionBody": "return (model.options && model.options.devices && !model.options.devices[arrayIndices].hide_device && (model.options.devices[arrayIndices].configDeviceType === 'Curtain' || model.options.devices[arrayIndices].configDeviceType === 'Curtain3') && model.options.devices[arrayIndices].deviceId);" + } + }, "hide_lightsensor": { "title": "Hide Light Sensor", "type": "boolean", @@ -810,6 +831,55 @@ "condition": { "functionBody": "return (model.options && model.options.devices && !model.options.devices[arrayIndices].hide_device && model.options.devices[arrayIndices].configDeviceType === 'Blind Tilt' && model.options.devices[arrayIndices].deviceId);" } + }, + "setOpenMode": { + "title": "Blind Tilt's Opening Mode", + "type": "string", + "oneOf": [ + { + "enum": [ + "0" + ], + "title": "Performance Mode" + }, + { + "enum": [ + "1" + ], + "title": "Silent Mode" + } + ], + "condition": { + "functionBody": "return (model.options && model.options.devices && !model.options.devices[arrayIndices].hide_device && model.options.devices[arrayIndices].configDeviceType === 'Blind Tilt' && model.options.devices[arrayIndices].deviceId);" + } + }, + "setCloseMode": { + "title": "Blind Tilt's Closing Mode", + "type": "string", + "oneOf": [ + { + "enum": [ + "0" + ], + "title": "Performance Mode" + }, + { + "enum": [ + "1" + ], + "title": "Silent Mode" + } + ], + "condition": { + "functionBody": "return (model.options && model.options.devices && !model.options.devices[arrayIndices].hide_device && model.options.devices[arrayIndices].configDeviceType === 'Blind Tilt' && model.options.devices[arrayIndices].deviceId);" + } + }, + "silentModeSwitch": { + "title": "Enable Blind Tilt Silent Mode Switches", + "type": "boolean", + "condition": { + "functionBody": "return (model.options && model.options.devices && !model.options.devices[arrayIndices].hide_device && model.options.devices[arrayIndices].configDeviceType === 'Blind Tilt' && model.options.devices[arrayIndices].deviceId);" + } } } }, @@ -1737,6 +1807,11 @@ ] } ] + }, + "allowInvalidCharacters": { + "title": "Allow Invalid Characters", + "type": "boolean", + "description": "If true, invalid characters will be allowed in the device name." } }, "required": [ @@ -1795,6 +1870,7 @@ "options.devices[].hub.hide_humidity", "options.devices[].hub.hide_lightsensor", "options.devices[].waterdetector.hide_leak", + "options.devices[].waterdetector.dry", "options.devices[].humidifier.set_minStep", "options.devices[].humidifier.hide_temperature", "options.devices[].curtain.set_minStep", @@ -1802,6 +1878,7 @@ "options.devices[].curtain.set_max", "options.devices[].curtain.setOpenMode", "options.devices[].curtain.setCloseMode", + "options.devices[].curtain.silentModeSwitch", "options.devices[].curtain.updateRate", "options.devices[].curtain.disable_group", "options.devices[].curtain.hide_lightsensor", @@ -1813,6 +1890,9 @@ "options.devices[].blindTilt.set_max", "options.devices[].blindTilt.hide_lightsensor", "options.devices[].blindTilt.updateRate", + "options.devices[].blindTilt.setOpenMode", + "options.devices[].blindTilt.setCloseMode", + "options.devices[].blindTilt.silentModeSwitch", "options.devices[].contact.hide_lightsensor", "options.devices[].contact.set_minlux", "options.devices[].contact.set_maxlux", @@ -1929,8 +2009,9 @@ "key": "options.pushRate", "description": "Specifies the interval, in seconds, between pushes to the SwitchBot API." }, - "options.logging" + "options.logging", + "options.allowInvalidCharacters" ] } ] -} \ No newline at end of file +} diff --git a/eslint.config.js b/eslint.config.js index 6890c8c0..5a64bd75 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -1,96 +1,51 @@ -import pluginJs from '@eslint/js'; -import tseslint from 'typescript-eslint'; -import stylistic from '@stylistic/eslint-plugin' +import antfu from '@antfu/eslint-config' - -export default tseslint.config({ - plugins: { - '@stylistic': stylistic, - '@typescript-eslint': tseslint.plugin, - }, - languageOptions: { - parser: tseslint.parser, - parserOptions: { - project: true, +export default antfu( + { + ignores: ['dist', 'docs'], + jsx: false, + typescript: true, + formatters: { + markdown: true, + }, + rules: { + 'curly': ['error', 'multi-line'], + 'import/extensions': ['error', 'ignorePackages'], + 'import/order': 0, + 'jsdoc/check-alignment': 'error', + 'jsdoc/check-line-alignment': 'error', + 'no-undef': 'error', + 'perfectionist/sort-exports': 'error', + 'perfectionist/sort-imports': [ + 'error', + { + groups: [ + 'builtin-type', + 'external-type', + 'internal-type', + ['parent-type', 'sibling-type', 'index-type'], + 'builtin', + 'external', + 'internal', + ['parent', 'sibling', 'index'], + 'object', + 'unknown', + ], + order: 'asc', + type: 'natural', + }, + ], + 'perfectionist/sort-named-exports': 'error', + 'perfectionist/sort-named-imports': 'error', + 'sort-imports': 0, + 'style/brace-style': ['error', '1tbs', { allowSingleLine: true }], + 'style/quote-props': ['error', 'consistent-as-needed'], + 'test/no-only-tests': 'error', + 'unicorn/no-useless-spread': 'error', + 'unused-imports/no-unused-vars': ['error', { caughtErrors: 'none' }], + 'no-new': 0, // Disable the no-new rule + 'new-cap': 0, // Disable the new-cap rule + 'no-undef': 0, // Disable the no-undef rule }, }, - files: ['**/*.ts'], - ignores: ['.dist/*'], - extends: [ - pluginJs.configs.recommended, - ...tseslint.configs.recommended, - ], - rules: { - '@typescript-eslint/array-type': 'error', - '@typescript-eslint/consistent-type-imports': 'error', - '@stylistic/type-annotation-spacing': 'error', - '@stylistic/quotes': [ - 'warn', - 'single', - ], - '@stylistic/indent': [ - 'warn', - 2, - { - 'SwitchCase': 1, - }, - ], - '@stylistic/linebreak-style': [ - 'warn', - 'unix', - ], - '@stylistic/semi': [ - 'warn', - 'always', - ], - '@stylistic/comma-dangle': [ - 'warn', - 'always-multiline', - ], - '@stylistic/dot-notation': 'off', - 'eqeqeq': 'warn', - 'curly': [ - 'warn', - 'all', - ], - '@stylistic/brace-style': [ - 'warn', - ], - 'prefer-arrow-callback': [ - 'warn', - ], - '@stylistic/max-len': [ - 'warn', - 150, - ], - 'no-console': [ - 'warn', - ], // use the provided Homebridge log method instead - 'no-non-null-assertion': [ - 'off', - ], - '@stylistic/comma-spacing': [ - 'error', - ], - '@stylistic/no-multi-spaces': [ - 'warn', - { - 'ignoreEOLComments': true, - }, - ], - '@stylistic/no-trailing-spaces': [ - 'warn', - ], - '@stylistic/lines-between-class-members': [ - 'warn', - 'always', - { - 'exceptAfterSingleLine': true, - }, - ], - '@typescript-eslint/explicit-function-return-type': 'off', - '@typescript-eslint/no-non-null-assertion': 'off', - '@typescript-eslint/explicit-module-boundary-types': 'off', - '@typescript-eslint/no-explicit-any': 'off', - }, -}); \ No newline at end of file +) diff --git a/package-lock.json b/package-lock.json index 3b39874e..4e2b6d4b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@switchbot/homebridge-switchbot", - "version": "3.7.0", + "version": "3.8.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@switchbot/homebridge-switchbot", - "version": "3.7.0", + "version": "3.8.0", "funding": [ { "type": "Paypal", @@ -21,2776 +21,5874 @@ "dependencies": { "@homebridge/plugin-ui-utils": "^1.0.3", "async-mqtt": "^2.6.3", - "fakegato-history": "^0.6.4", - "homebridge-lib": "^7.0.4", + "fakegato-history": "^0.6.5", + "homebridge-lib": "^7.0.8", + "node-switchbot": "2.3.0", "rxjs": "^7.8.1", - "undici": "^6.19.2" + "undici": "^6.19.8" }, "devDependencies": { - "@eslint/js": "^9.7.0", - "@stylistic/eslint-plugin": "^2.3.0", - "@types/eslint__js": "^8.42.3", - "@types/node": "^20.14.11", - "eslint": "^9.7.0", - "globals": "^15.8.0", + "@antfu/eslint-config": "^3.5.1", + "@types/aes-js": "^3.1.4", + "@types/debug": "^4.1.12", + "@types/fs-extra": "^11.0.4", + "@types/jest": "^29.5.12", + "@types/mdast": "^4.0.4", + "@types/node": "^22.5.4", + "@types/semver": "^7.5.8", + "@types/source-map-support": "^0.5.10", + "@vitest/coverage-v8": "^2.0.5", + "eslint": "^9.10.0", + "eslint-plugin-format": "^0.1.2", "homebridge": "^1.8.4", - "homebridge-config-ui-x": "4.56.4", + "homebridge-config-ui-x": "4.58.0", + "jest": "^29.7.0", "nodemon": "^3.1.4", - "npm-check-updates": "^16.14.20", - "rimraf": "^6.0.1", + "npm-check-updates": "^17.1.1", + "shx": "^0.3.4", "ts-node": "^10.9.2", - "typedoc": "^0.26.5", - "typescript": "^5.5.3", - "typescript-eslint": "^8.0.0-alpha.44" + "typedoc": "^0.26.7", + "typescript": "^5.6.2", + "typescript-axios-wb": "^1.0.3", + "vitest": "^2.0.5" }, "engines": { - "homebridge": "^1.8.4 || ^2.0.0-alpha.0", + "homebridge": "^1.8.4 || ^2.0.0 || ^2.0.0-beta.11 || ^2.0.0-alpha.10", "node": "^18 || ^20 || ^22" - }, - "optionalDependencies": { - "node-switchbot": "2.3.0" - } - }, - "node_modules/@colors/colors": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", - "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">=0.1.90" } }, - "node_modules/@cspotcode/source-map-support": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", - "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "@jridgewell/trace-mapping": "0.3.9" + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" }, "engines": { - "node": ">=12" + "node": ">=6.0.0" } }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", - "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", - "dev": true, - "license": "MIT", - "dependencies": { - "eslint-visitor-keys": "^3.3.0" + "node_modules/@antfu/eslint-config": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/@antfu/eslint-config/-/eslint-config-3.5.1.tgz", + "integrity": "sha512-zSQs+1B1/rw/gdM1959e/tmNORibN1nLYdysV3qnH7qOD4c5spi1C9ogJbwXJ49dFD56GZw+Eue8FJ2HQx0hKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@antfu/install-pkg": "^0.4.1", + "@clack/prompts": "^0.7.0", + "@eslint-community/eslint-plugin-eslint-comments": "^4.4.0", + "@eslint/markdown": "^6.1.0", + "@stylistic/eslint-plugin": "^2.8.0", + "@typescript-eslint/eslint-plugin": "^8.5.0", + "@typescript-eslint/parser": "^8.5.0", + "@vitest/eslint-plugin": "^1.1.0", + "eslint-config-flat-gitignore": "^0.3.0", + "eslint-flat-config-utils": "^0.4.0", + "eslint-merge-processors": "^0.1.0", + "eslint-plugin-antfu": "^2.6.0", + "eslint-plugin-command": "^0.2.4", + "eslint-plugin-import-x": "^4.2.1", + "eslint-plugin-jsdoc": "^50.2.2", + "eslint-plugin-jsonc": "^2.16.0", + "eslint-plugin-n": "^17.10.2", + "eslint-plugin-no-only-tests": "^3.3.0", + "eslint-plugin-perfectionist": "^3.5.0", + "eslint-plugin-regexp": "^2.6.0", + "eslint-plugin-toml": "^0.11.1", + "eslint-plugin-unicorn": "^55.0.0", + "eslint-plugin-unused-imports": "^4.1.3", + "eslint-plugin-vue": "^9.28.0", + "eslint-plugin-yml": "^1.14.0", + "eslint-processor-vue-blocks": "^0.1.2", + "globals": "^15.9.0", + "jsonc-eslint-parser": "^2.4.0", + "local-pkg": "^0.5.0", + "parse-gitignore": "^2.0.0", + "picocolors": "^1.1.0", + "toml-eslint-parser": "^0.10.0", + "vue-eslint-parser": "^9.4.3", + "yaml-eslint-parser": "^1.2.3", + "yargs": "^17.7.2" }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "bin": { + "eslint-config": "bin/index.js" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" }, "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + "@eslint-react/eslint-plugin": "^1.5.8", + "@prettier/plugin-xml": "^3.4.1", + "@unocss/eslint-plugin": ">=0.50.0", + "astro-eslint-parser": "^1.0.2", + "eslint": "^9.10.0", + "eslint-plugin-astro": "^1.2.0", + "eslint-plugin-format": ">=0.1.0", + "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-react-refresh": "^0.4.4", + "eslint-plugin-solid": "^0.14.3", + "eslint-plugin-svelte": ">=2.35.1", + "prettier-plugin-astro": "^0.13.0", + "prettier-plugin-slidev": "^1.0.5", + "svelte-eslint-parser": ">=0.37.0" + }, + "peerDependenciesMeta": { + "@eslint-react/eslint-plugin": { + "optional": true + }, + "@prettier/plugin-xml": { + "optional": true + }, + "@unocss/eslint-plugin": { + "optional": true + }, + "astro-eslint-parser": { + "optional": true + }, + "eslint-plugin-astro": { + "optional": true + }, + "eslint-plugin-format": { + "optional": true + }, + "eslint-plugin-react-hooks": { + "optional": true + }, + "eslint-plugin-react-refresh": { + "optional": true + }, + "eslint-plugin-solid": { + "optional": true + }, + "eslint-plugin-svelte": { + "optional": true + }, + "prettier-plugin-astro": { + "optional": true + }, + "prettier-plugin-slidev": { + "optional": true + }, + "svelte-eslint-parser": { + "optional": true + } } }, - "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "node_modules/@antfu/install-pkg": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@antfu/install-pkg/-/install-pkg-0.4.1.tgz", + "integrity": "sha512-T7yB5QNG29afhWVkVq7XeIMBa5U/vs9mX69YqayXypPRmYzUmzwnYltplHmPtZ4HPCn+sQKeXW8I47wCbuBOjw==", "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "license": "MIT", + "dependencies": { + "package-manager-detector": "^0.2.0", + "tinyexec": "^0.3.0" }, "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://github.com/sponsors/antfu" } }, - "node_modules/@eslint-community/regexpp": { - "version": "4.11.0", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.0.tgz", - "integrity": "sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==", + "node_modules/@antfu/utils": { + "version": "0.7.10", + "resolved": "https://registry.npmjs.org/@antfu/utils/-/utils-0.7.10.tgz", + "integrity": "sha512-+562v9k4aI80m1+VuMHehNJWLOFjBnXn3tdOitzD0il5b7smkSBal4+a3oKiQTbrwMmN/TBUMDvbdoWDehgOww==", "dev": true, "license": "MIT", - "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + "funding": { + "url": "https://github.com/sponsors/antfu" } }, - "node_modules/@eslint/config-array": { - "version": "0.17.0", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.17.0.tgz", - "integrity": "sha512-A68TBu6/1mHHuc5YJL0U0VVeGNiklLAL6rRmhTCP2B5XjWLMnrX+HkO+IAXyHvks5cyyY1jjK5ITPQ1HGS2EVA==", + "node_modules/@babel/code-frame": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", + "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "dependencies": { - "@eslint/object-schema": "^2.1.4", - "debug": "^4.3.1", - "minimatch": "^3.1.2" + "@babel/highlight": "^7.24.7", + "picocolors": "^1.0.0" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=6.9.0" } }, - "node_modules/@eslint/eslintrc": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.1.0.tgz", - "integrity": "sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ==", + "node_modules/@babel/compat-data": { + "version": "7.25.4", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.25.4.tgz", + "integrity": "sha512-+LGRog6RAsCJrrrg/IO6LGmpphNe5DiK30dGjCoxxeGv49B10/3XYGxPsAwrDlMFcFEvdAUavDT8r9k/hSyQqQ==", "dev": true, "license": "MIT", - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^10.0.1", - "globals": "^14.0.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" + "node": ">=6.9.0" } }, - "node_modules/@eslint/eslintrc/node_modules/globals": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", - "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "node_modules/@babel/core": { + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.25.2.tgz", + "integrity": "sha512-BBt3opiCOxUr9euZ5/ro/Xv8/V7yJ5bjYMqG/C1YAo8MIKAnumZalCN+msbci3Pigy4lIQfPUpfMM27HMGaYEA==", "dev": true, "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.24.7", + "@babel/generator": "^7.25.0", + "@babel/helper-compilation-targets": "^7.25.2", + "@babel/helper-module-transforms": "^7.25.2", + "@babel/helpers": "^7.25.0", + "@babel/parser": "^7.25.0", + "@babel/template": "^7.25.0", + "@babel/traverse": "^7.25.2", + "@babel/types": "^7.25.2", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, "engines": { - "node": ">=18" + "node": ">=6.9.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "opencollective", + "url": "https://opencollective.com/babel" } }, - "node_modules/@eslint/js": { - "version": "9.7.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.7.0.tgz", - "integrity": "sha512-ChuWDQenef8OSFnvuxv0TCVxEwmu3+hPNKvM9B34qpM0rDRbjL8t5QkQeHHeAfsKQjuH9wS82WeCi1J/owatng==", + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "license": "ISC", + "bin": { + "semver": "bin/semver.js" } }, - "node_modules/@eslint/object-schema": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.4.tgz", - "integrity": "sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==", + "node_modules/@babel/generator": { + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.6.tgz", + "integrity": "sha512-VPC82gr1seXOpkjAAKoLhP50vx4vGNlF4msF64dSFq1P8RfB+QAuJWGHPXXPc8QyfVWwwB/TNNU4+ayZmHNbZw==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.25.6", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^2.5.1" + }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=6.9.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==", + "node_modules/@babel/generator/node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", "dev": true, "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, "engines": { - "node": ">=14" + "node": ">=4" } }, - "node_modules/@fastify/ajv-compiler": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/@fastify/ajv-compiler/-/ajv-compiler-3.6.0.tgz", - "integrity": "sha512-LwdXQJjmMD+GwLOkP7TVC68qa+pSSogeWWmznRJ/coyTcfe9qA05AHFSe1eZFwK6q+xVRpChnvFUkf1iYaSZsQ==", + "node_modules/@babel/helper-compilation-targets": { + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.2.tgz", + "integrity": "sha512-U2U5LsSaZ7TAt3cfaymQ8WHh0pxvdHoEk6HVpaexxixjyEquMh0L0YNJNM6CTGKMXV1iksi0iZkGw4AcFkPaaw==", "dev": true, "license": "MIT", "dependencies": { - "ajv": "^8.11.0", - "ajv-formats": "^2.1.1", - "fast-uri": "^2.0.0" + "@babel/compat-data": "^7.25.2", + "@babel/helper-validator-option": "^7.24.8", + "browserslist": "^4.23.1", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" } }, - "node_modules/@fastify/ajv-compiler/node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz", + "integrity": "sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==", "dev": true, "license": "MIT", "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "engines": { + "node": ">=6.9.0" } }, - "node_modules/@fastify/ajv-compiler/node_modules/ajv/node_modules/fast-uri": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.1.tgz", - "integrity": "sha512-MWipKbbYiYI0UC7cl8m/i/IWTqfC8YXsqjzybjddLsFjStroQzsHXkc73JutMvBiXmOvapk+axIl79ig5t55Bw==", + "node_modules/@babel/helper-module-transforms": { + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.25.2.tgz", + "integrity": "sha512-BjyRAbix6j/wv83ftcVJmBt72QtHI56C7JXZoG2xATiLpmoC7dpd8WnkikExHDVPpi/3qCmO6WY1EaXOluiecQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.24.7", + "@babel/helper-simple-access": "^7.24.7", + "@babel/helper-validator-identifier": "^7.24.7", + "@babel/traverse": "^7.25.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } }, - "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==", + "node_modules/@babel/helper-plugin-utils": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.8.tgz", + "integrity": "sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } }, - "node_modules/@fastify/busboy": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", - "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==", + "node_modules/@babel/helper-simple-access": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.7.tgz", + "integrity": "sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==", "dev": true, "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" + }, "engines": { - "node": ">=14" + "node": ">=6.9.0" } }, - "node_modules/@fastify/cors": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/@fastify/cors/-/cors-9.0.1.tgz", - "integrity": "sha512-YY9Ho3ovI+QHIL2hW+9X4XqQjXLjJqsU+sMV/xFsxZkE8p3GNnYVFpoOxF7SsP5ZL76gwvbo3V9L+FIekBGU4Q==", + "node_modules/@babel/helper-string-parser": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz", + "integrity": "sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==", "dev": true, "license": "MIT", - "dependencies": { - "fastify-plugin": "^4.0.0", - "mnemonist": "0.39.6" + "engines": { + "node": ">=6.9.0" } }, - "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==", + "node_modules/@babel/helper-validator-identifier": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", + "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } }, - "node_modules/@fastify/error": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/@fastify/error/-/error-3.4.1.tgz", - "integrity": "sha512-wWSvph+29GR783IhmvdwWnN4bUxTD01Vm5Xad4i7i1VuAOItLvbPAb69sb0IQ2N57yprvhNIwAP5B6xfKTmjmQ==", + "node_modules/@babel/helper-validator-option": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.8.tgz", + "integrity": "sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } }, - "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==", + "node_modules/@babel/helpers": { + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.25.6.tgz", + "integrity": "sha512-Xg0tn4HcfTijTwfDwYlvVCl43V6h4KyVVX2aEm4qdO/PC6L2YvzLHFdmxhoeSA3eslcE6+ZVXHgWwopXYLNq4Q==", "dev": true, "license": "MIT", "dependencies": { - "fast-json-stringify": "^5.7.0" + "@babel/template": "^7.25.0", + "@babel/types": "^7.25.6" + }, + "engines": { + "node": ">=6.9.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==", + "node_modules/@babel/highlight": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz", + "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==", "dev": true, "license": "MIT", "dependencies": { - "fast-querystring": "^1.0.0", - "fastify-plugin": "^4.0.0" + "@babel/helper-validator-identifier": "^7.24.7", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.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==", + "node_modules/@babel/highlight/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "license": "MIT", "dependencies": { - "fastify-plugin": "^4.2.1", - "helmet": "^7.0.0" + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" } }, - "node_modules/@fastify/merge-json-schemas": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@fastify/merge-json-schemas/-/merge-json-schemas-0.1.1.tgz", - "integrity": "sha512-fERDVz7topgNjtXsJTTW1JKLy0rhuLRcquYqNR9rF7OcVpCa2OVW49ZPDIhaRRCaUuvVxI+N416xUoF76HNSXA==", + "node_modules/@babel/highlight/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", "dev": true, "license": "MIT", "dependencies": { - "fast-deep-equal": "^3.1.3" + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" } }, - "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==", + "node_modules/@babel/highlight/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", "dev": true, "license": "MIT", "dependencies": { - "@fastify/error": "^3.2.0", - "fastify-plugin": "^4.0.0", - "path-to-regexp": "^6.1.0", - "reusify": "^1.0.4" + "color-name": "1.1.3" } }, - "node_modules/@fastify/middie/node_modules/path-to-regexp": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.2.tgz", - "integrity": "sha512-GQX3SSMokngb36+whdpRXE+3f9V8UzyAorlYvOGx87ufGHehNTn5lCxrKtLyZ4Yl/wEKnNnr98ZzOwwDZV5ogw==", + "node_modules/@babel/highlight/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", "dev": true, "license": "MIT" }, - "node_modules/@fastify/multipart": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/@fastify/multipart/-/multipart-8.2.0.tgz", - "integrity": "sha512-OZ8nsyyoS2TV7Yeu3ZdrdDGsKUTAbfjrKC9jSxGgT2qdgek+BxpWX31ZubTrWMNZyU5xwk4ox6AvTjAbYWjrWg==", + "node_modules/@babel/highlight/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", "dev": true, "license": "MIT", - "dependencies": { - "@fastify/busboy": "^2.1.0", - "@fastify/deepmerge": "^1.0.0", - "@fastify/error": "^3.0.0", - "fastify-plugin": "^4.0.0", - "secure-json-parse": "^2.4.0", - "stream-wormhole": "^1.1.0" + "engines": { + "node": ">=0.8.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==", + "node_modules/@babel/highlight/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", "dev": true, "license": "MIT", - "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" + "engines": { + "node": ">=4" } }, - "node_modules/@fastify/static": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@fastify/static/-/static-7.0.2.tgz", - "integrity": "sha512-5opbHpZj29EGVBNgELW6gDkueiFWxjLsLVQQCgKencJctq0aqk3vBlkO97z5It4zaSAb3FXOeAxm7KP2tL/hQA==", + "node_modules/@babel/highlight/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "license": "MIT", "dependencies": { - "@fastify/accept-negotiator": "^1.0.0", - "@fastify/send": "^2.0.0", - "content-disposition": "^0.5.3", - "fastify-plugin": "^4.0.0", - "fastq": "^1.17.0", - "glob": "^10.3.4" + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" } }, - "node_modules/@gar/promisify": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", - "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@homebridge/ciao": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@homebridge/ciao/-/ciao-1.3.0.tgz", - "integrity": "sha512-PRbY5FiukY13gIDwiAAZng598gMyI61GL9VU19G2CB4D3vNwh8vESgI9TkjeecCDethTqJmNiruYvdunfQkt7w==", + "node_modules/@babel/parser": { + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.6.tgz", + "integrity": "sha512-trGdfBdbD0l1ZPmcJ83eNxB9rbEax4ALFTF7fN386TMYbeCQbyme5cOEXQhbGXKebwGaB/J52w1mrklMcbgy6Q==", "dev": true, "license": "MIT", "dependencies": { - "debug": "^4.3.5", - "fast-deep-equal": "^3.1.3", - "source-map-support": "^0.5.21", - "tslib": "^2.6.3" + "@babel/types": "^7.25.6" }, "bin": { - "ciao-bcs": "lib/bonjour-conformance-testing.js" + "parser": "bin/babel-parser.js" }, "engines": { - "node": "^18 || ^20" + "node": ">=6.0.0" } }, - "node_modules/@homebridge/dbus-native": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/@homebridge/dbus-native/-/dbus-native-0.6.0.tgz", - "integrity": "sha512-xObqQeYHTXmt6wsfj10+krTo4xbzR9BgUfX2aQ+edDC9nc4ojfzLScfXCh3zluAm6UCowKw+AFfXn6WLWUOPkg==", + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", "dev": true, "license": "MIT", "dependencies": { - "@homebridge/long": "^5.2.1", - "@homebridge/put": "^0.0.8", - "event-stream": "^4.0.1", - "hexy": "^0.3.5", - "minimist": "^1.2.6", - "safe-buffer": "^5.1.2", - "xml2js": "^0.6.2" + "@babel/helper-plugin-utils": "^7.8.0" }, - "bin": { - "dbus2js": "bin/dbus2js.js" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@homebridge/hap-client": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@homebridge/hap-client/-/hap-client-1.10.0.tgz", - "integrity": "sha512-jbCfsrT97EF9IkW6Wj9fq0ALeSa1y+3UtZ2V+QsacHBd4cfrBr8S5UvlgU+zemctb/EllxMA8S92XqWg5yL2UA==", + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", "dev": true, - "license": "GPL-3.0", + "license": "MIT", "dependencies": { - "axios": "^1.6.8", - "bonjour-service": "^1.2.1", - "decamelize": "^5.0.1", - "inflection": "^3.0.0", - "source-map-support": "^0.5.21" + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@homebridge/long": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/@homebridge/long/-/long-5.2.1.tgz", - "integrity": "sha512-i5Df8R63XNPCn+Nj1OgAoRdw9e+jHUQb3CNUbvJneI2iu3j4+OtzQj+5PA1Ce+747NR1SPqZSvyvD483dOT3AA==", - "dev": true, - "license": "Apache-2.0" - }, - "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==", + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", "dev": true, - "hasInstallScript": true, "license": "MIT", "dependencies": { - "nan": "^2.18.0", - "prebuild-install": "^7.1.1" - } - }, - "node_modules/@homebridge/plugin-ui-utils": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@homebridge/plugin-ui-utils/-/plugin-ui-utils-1.0.3.tgz", - "integrity": "sha512-p2S/czGYNRnRtMICxBUk4Uar+KCezfyxjqfStfxKgykD2082SNayVDncYUK1xRai78EGHCbif9eoyrmDweh4tQ==", - "license": "MIT" - }, - "node_modules/@homebridge/put": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/@homebridge/put/-/put-0.0.8.tgz", - "integrity": "sha512-mwxLHHqKebOmOSU0tsPEWQSBHGApPhuaqtNpCe7U+AMdsduweANiu64E9SXXUtdpyTjsOpgSMLhD1+kbLHD2gA==", - "dev": true, - "license": "MIT/X11", - "engines": { - "node": ">=0.3.0" + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, "engines": { - "node": ">=12.22" + "node": ">=6.9.0" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@humanwhocodes/retry": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.0.tgz", - "integrity": "sha512-d2CGZR2o7fS6sWB7DG/3a95bGKQyHMACZ5aW8qGkkqQpUoZV6C0X7Pc7l4ZNMZkfNBf4VWNe9E1jRsf0G146Ew==", + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.25.6.tgz", + "integrity": "sha512-sXaDXaJN9SNLymBdlWFA+bjzBhFD617ZaFiY13dGt7TVslVvVgA6fkZOP7Ki3IGElC45lwHdOTrCtKZGVAWeLQ==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.8" + }, "engines": { - "node": ">=18.18" + "node": ">=6.9.0" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@isaacs/cliui": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", - "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + "@babel/helper-plugin-utils": "^7.10.4" }, - "engines": { - "node": ">=12" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@isaacs/cliui/node_modules/ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", "dev": true, "license": "MIT", - "engines": { - "node": ">=12" + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@isaacs/cliui/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.7.tgz", + "integrity": "sha512-6ddciUPe/mpMnOKv/U+RSd2vvVy+Yw/JfBB0ZHYjEZt9NLHmCUylNYlsbqCCS1Bffjlb0fCwC9Vqz+sBz6PsiQ==", "dev": true, "license": "MIT", "dependencies": { - "ansi-regex": "^6.0.1" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { - "node": ">=12" + "node": ">=6.9.0" }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", "dev": true, "license": "MIT", - "engines": { - "node": ">=6.0.0" + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", - "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", "dev": true, "license": "MIT", "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@leichtgewicht/ip-codec": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", - "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==", - "license": "MIT" - }, - "node_modules/@lukeed/csprng": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@lukeed/csprng/-/csprng-1.1.0.tgz", - "integrity": "sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA==", + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", "dev": true, "license": "MIT", - "engines": { - "node": ">=8" + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "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==", + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", "dev": true, "license": "MIT", - "engines": { - "node": ">=8" + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@microsoft/tsdoc": { - "version": "0.14.2", - "resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.14.2.tgz", - "integrity": "sha512-9b8mPpKrfeGRuhFH5iO1iwCLeIIsV6+H1sRfxbkoGXIyQE2BTsPd9zqSqQJ+pv5sJ/hT5M1zvOFL02MnEezFug==", + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", "dev": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } }, - "node_modules/@nestjs/axios": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@nestjs/axios/-/axios-3.0.2.tgz", - "integrity": "sha512-Z6GuOUdNQjP7FX+OuV2Ybyamse+/e0BFdTWBX5JxpBDKA+YkdLynDgG6HTF04zy6e9zPa19UX0WA2VDoehwhXQ==", + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", "dev": true, "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, "peerDependencies": { - "@nestjs/common": "^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0", - "axios": "^1.3.1", - "rxjs": "^6.0.0 || ^7.0.0" + "@babel/core": "^7.0.0-0" } }, - "node_modules/@nestjs/common": { - "version": "10.3.7", - "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-10.3.7.tgz", - "integrity": "sha512-gKFtFzcJznrwsRYjtNZoPAvSOPYdNgxbTYoAyLTpoy393cIKgLmJTHu6ReH8/qIB9AaZLdGaFLkx98W/tFWFUw==", + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", "dev": true, "license": "MIT", "dependencies": { - "iterare": "1.2.1", - "tslib": "2.6.2", - "uid": "2.0.2" + "@babel/helper-plugin-utils": "^7.14.5" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/nest" + "engines": { + "node": ">=6.9.0" }, "peerDependencies": { - "class-transformer": "*", - "class-validator": "*", - "reflect-metadata": "^0.1.12 || ^0.2.0", - "rxjs": "^7.1.0" - }, - "peerDependenciesMeta": { - "class-transformer": { - "optional": true - }, - "class-validator": { - "optional": true - } + "@babel/core": "^7.0.0-0" } }, - "node_modules/@nestjs/common/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", "dev": true, - "license": "0BSD" + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } }, - "node_modules/@nestjs/core": { - "version": "10.3.7", - "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-10.3.7.tgz", - "integrity": "sha512-hsdlnfiQ3kgqHL5k7js3CU0PV7hBJVi+LfFMgCkoagRxNMf67z0GFGeOV2jk5d65ssB19qdYsDa1MGVuEaoUpg==", + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.25.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.25.4.tgz", + "integrity": "sha512-uMOCoHVU52BsSWxPOMVv5qKRdeSlPuImUCB2dlPuBSU+W2/ROE7/Zg8F2Kepbk+8yBa68LlRKxO+xgEVWorsDg==", "dev": true, - "hasInstallScript": true, "license": "MIT", "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" + "@babel/helper-plugin-utils": "^7.24.8" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/nest" + "engines": { + "node": ">=6.9.0" }, "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 || ^0.2.0", - "rxjs": "^7.1.0" - }, - "peerDependenciesMeta": { - "@nestjs/microservices": { - "optional": true - }, - "@nestjs/platform-express": { - "optional": true - }, - "@nestjs/websockets": { - "optional": true - } + "@babel/core": "^7.0.0-0" } }, - "node_modules/@nestjs/core/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@nestjs/jwt": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/@nestjs/jwt/-/jwt-10.2.0.tgz", - "integrity": "sha512-x8cG90SURkEiLOehNaN2aRlotxT0KZESUliOPKKnjWiyJOcWurkF3w345WOX0P4MgFzUjGoZ1Sy0aZnxeihT0g==", + "node_modules/@babel/template": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.0.tgz", + "integrity": "sha512-aOOgh1/5XzKvg1jvVz7AVrx2piJ2XBi227DHmbY6y+bM9H2FlN+IfecYu4Xl0cNiiVejlsCri89LUsbj8vJD9Q==", "dev": true, "license": "MIT", "dependencies": { - "@types/jsonwebtoken": "9.0.5", - "jsonwebtoken": "9.0.2" + "@babel/code-frame": "^7.24.7", + "@babel/parser": "^7.25.0", + "@babel/types": "^7.25.0" }, - "peerDependencies": { - "@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0" + "engines": { + "node": ">=6.9.0" } }, - "node_modules/@nestjs/mapped-types": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nestjs/mapped-types/-/mapped-types-2.0.5.tgz", - "integrity": "sha512-bSJv4pd6EY99NX9CjBIyn4TVDoSit82DUZlL4I3bqNfy5Gt+gXTa86i3I/i0iIV9P4hntcGM5GyO+FhZAhxtyg==", + "node_modules/@babel/traverse": { + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.6.tgz", + "integrity": "sha512-9Vrcx5ZW6UwK5tvqsj0nGpp/XzqthkT0dqIc9g1AdtygFToNtTF67XzYS//dm+SAK9cp3B9R4ZO/46p63SCjlQ==", "dev": true, "license": "MIT", - "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 || ^0.2.0" + "dependencies": { + "@babel/code-frame": "^7.24.7", + "@babel/generator": "^7.25.6", + "@babel/parser": "^7.25.6", + "@babel/template": "^7.25.0", + "@babel/types": "^7.25.6", + "debug": "^4.3.1", + "globals": "^11.1.0" }, - "peerDependenciesMeta": { - "class-transformer": { - "optional": true - }, - "class-validator": { - "optional": true - } + "engines": { + "node": ">=6.9.0" } }, - "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==", + "node_modules/@babel/traverse/node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", "dev": true, "license": "MIT", - "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" + "engines": { + "node": ">=4" } }, - "node_modules/@nestjs/platform-fastify": { - "version": "10.3.7", - "resolved": "https://registry.npmjs.org/@nestjs/platform-fastify/-/platform-fastify-10.3.7.tgz", - "integrity": "sha512-J7ICAOC/zTSfJLTWwvVLK9LON+Dw/NdgPQwPBsi3GNIw3TLLXLrQ4WXnHNER8gnXS3sfKOIngRLusMirLqYjEQ==", + "node_modules/@babel/types": { + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.6.tgz", + "integrity": "sha512-/l42B1qxpG6RdfYf343Uw1vmDjeNhneUXtzhojE7pDgfpEypmRhI6j1kr17XCVv4Cgl9HdAiQY2x0GwKm7rWCw==", "dev": true, "license": "MIT", "dependencies": { - "@fastify/cors": "9.0.1", - "@fastify/formbody": "7.4.0", - "@fastify/middie": "8.3.0", - "fastify": "4.26.2", - "light-my-request": "5.12.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 || ^7.0.0", - "@fastify/view": "^7.0.0 || ^8.0.0", - "@nestjs/common": "^10.0.0", - "@nestjs/core": "^10.0.0" + "@babel/helper-string-parser": "^7.24.8", + "@babel/helper-validator-identifier": "^7.24.7", + "to-fast-properties": "^2.0.0" }, - "peerDependenciesMeta": { - "@fastify/static": { - "optional": true - }, - "@fastify/view": { - "optional": true - } + "engines": { + "node": ">=6.9.0" } }, - "node_modules/@nestjs/platform-fastify/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", "dev": true, - "license": "0BSD" + "license": "MIT" }, - "node_modules/@nestjs/platform-socket.io": { - "version": "10.3.7", - "resolved": "https://registry.npmjs.org/@nestjs/platform-socket.io/-/platform-socket.io-10.3.7.tgz", - "integrity": "sha512-T9VbVgEUnbid/RiywN9/8YQ8pAGDP++0nX73l4kIWeDWkz5DEh4aLB7O/JvLA3/xRHdjTZ4RiRZazwqSWi1Sog==", + "node_modules/@clack/core": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@clack/core/-/core-0.3.4.tgz", + "integrity": "sha512-H4hxZDXgHtWTwV3RAVenqcC4VbJZNegbBjlPvzOzCouXtS2y3sDvlO3IsbrPNWuLWPPlYVYPghQdSF64683Ldw==", "dev": true, "license": "MIT", "dependencies": { - "socket.io": "4.7.5", - "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" + "picocolors": "^1.0.0", + "sisteransi": "^1.0.5" } }, - "node_modules/@nestjs/platform-socket.io/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@nestjs/swagger": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/@nestjs/swagger/-/swagger-7.3.1.tgz", - "integrity": "sha512-LUC4mr+5oAleEC/a2j8pNRh1S5xhKXJ1Gal5ZdRjt9XebQgbngXCdW7JTA9WOEcwGtFZN9EnKYdquzH971LZfw==", + "node_modules/@clack/prompts": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@clack/prompts/-/prompts-0.7.0.tgz", + "integrity": "sha512-0MhX9/B4iL6Re04jPrttDm+BsP8y6mS7byuv0BvXgdXhbV5PdlsHt55dvNsuBCPZ7xq1oTAOOuotR9NFbQyMSA==", + "bundleDependencies": [ + "is-unicode-supported" + ], "dev": true, "license": "MIT", "dependencies": { - "@microsoft/tsdoc": "^0.14.2", - "@nestjs/mapped-types": "2.0.5", - "js-yaml": "4.1.0", - "lodash": "4.17.21", - "path-to-regexp": "3.2.0", - "swagger-ui-dist": "5.11.2" - }, - "peerDependencies": { - "@fastify/static": "^6.0.0 || ^7.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 || ^0.2.0" - }, - "peerDependenciesMeta": { - "@fastify/static": { - "optional": true - }, - "class-transformer": { - "optional": true - }, - "class-validator": { - "optional": true - } + "@clack/core": "^0.3.3", + "is-unicode-supported": "*", + "picocolors": "^1.0.0", + "sisteransi": "^1.0.5" } }, - "node_modules/@nestjs/websockets": { - "version": "10.3.7", - "resolved": "https://registry.npmjs.org/@nestjs/websockets/-/websockets-10.3.7.tgz", - "integrity": "sha512-iYdsWiRNPUy0XzPoW44bx2MW1griuraTr5fNhoe2rUSNO0mEW1aeXp4v56KeZDLAss31WbeckC5P3N223Fys5g==", - "dev": true, + "node_modules/@clack/prompts/node_modules/is-unicode-supported": { + "version": "1.3.0", + "inBundle": true, "license": "MIT", - "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 || ^0.2.0", - "rxjs": "^7.1.0" + "engines": { + "node": ">=12" }, - "peerDependenciesMeta": { - "@nestjs/platform-socket.io": { - "optional": true - } + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@nestjs/websockets/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", "dev": true, "license": "MIT", "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" + "@jridgewell/trace-mapping": "0.3.9" }, "engines": { - "node": ">= 8" + "node": ">=12" } }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", "dev": true, "license": "MIT", - "engines": { - "node": ">= 8" + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" } }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "node_modules/@dprint/formatter": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@dprint/formatter/-/formatter-0.3.0.tgz", + "integrity": "sha512-N9fxCxbaBOrDkteSOzaCqwWjso5iAe+WJPsHC021JfHNj2ThInPNEF13ORDKta3llq5D1TlclODCvOvipH7bWQ==", "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } + "license": "MIT" }, - "node_modules/@npmcli/fs": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-3.1.1.tgz", - "integrity": "sha512-q9CRWjpHCMIh5sVyefoD1cA7PkvILqCZsnSOEUUivORLjxCO/Irmue2DprETiNgEqktDBZaM1Bi+jrarx1XdCg==", + "node_modules/@dprint/markdown": { + "version": "0.17.8", + "resolved": "https://registry.npmjs.org/@dprint/markdown/-/markdown-0.17.8.tgz", + "integrity": "sha512-ukHFOg+RpG284aPdIg7iPrCYmMs3Dqy43S1ejybnwlJoFiW02b+6Bbr5cfZKFRYNP3dKGM86BqHEnMzBOyLvvA==", "dev": true, - "license": "ISC", - "dependencies": { - "semver": "^7.3.5" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } + "license": "MIT" }, - "node_modules/@npmcli/git": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-4.1.0.tgz", - "integrity": "sha512-9hwoB3gStVfa0N31ymBmrX+GuDGdVA/QWShZVqE0HK2Af+7QGGrCTbZia/SW0ImUTjTne7SP91qxDmtXvDHRPQ==", + "node_modules/@dprint/toml": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/@dprint/toml/-/toml-0.6.2.tgz", + "integrity": "sha512-Mk5unEANsL/L+WHYU3NpDXt1ARU5bNU5k5OZELxaJodDycKG6RoRnSlZXpW6+7UN2PSnETAFVUdKrh937ZwtHA==", "dev": true, - "license": "ISC", + "license": "MIT" + }, + "node_modules/@es-joy/jsdoccomment": { + "version": "0.48.0", + "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.48.0.tgz", + "integrity": "sha512-G6QUWIcC+KvSwXNsJyDTHvqUdNoAVJPPgkc3+Uk4WBKqZvoXhlvazOgm9aL0HwihJLQf0l+tOE2UFzXBqCqgDw==", + "dev": true, + "license": "MIT", "dependencies": { - "@npmcli/promise-spawn": "^6.0.0", - "lru-cache": "^7.4.4", - "npm-pick-manifest": "^8.0.0", - "proc-log": "^3.0.0", - "promise-inflight": "^1.0.1", - "promise-retry": "^2.0.1", - "semver": "^7.3.5", - "which": "^3.0.0" + "comment-parser": "1.4.1", + "esquery": "^1.6.0", + "jsdoc-type-pratt-parser": "~4.1.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": ">=16" } }, - "node_modules/@npmcli/git/node_modules/lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], "dev": true, - "license": "ISC", + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], "engines": { "node": ">=12" } }, - "node_modules/@npmcli/git/node_modules/which": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/which/-/which-3.0.1.tgz", - "integrity": "sha512-XA1b62dzQzLfaEOSQFTCOd5KFf/1VSzZo7/7TUjnya6u0vGGKzU96UQBZTAThCb2j4/xjBAyii1OhRLJEivHvg==", + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/which.js" - }, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": ">=12" } }, - "node_modules/@npmcli/installed-package-contents": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@npmcli/installed-package-contents/-/installed-package-contents-2.1.0.tgz", - "integrity": "sha512-c8UuGLeZpm69BryRykLuKRyKFZYJsZSCT4aVY5ds4omyZqJ172ApzgfKJ5eV/r3HgLdUYgFVe54KSFVjKoe27w==", + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "ISC", - "dependencies": { - "npm-bundled": "^3.0.0", - "npm-normalize-package-bin": "^3.0.0" - }, - "bin": { - "installed-package-contents": "bin/index.js" - }, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": ">=12" } }, - "node_modules/@npmcli/move-file": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-2.0.1.tgz", - "integrity": "sha512-mJd2Z5TjYWq/ttPLLGqArdtnC74J6bOzg4rMDnN+p1xTacZ2yPRCk2y0oSWQtygLR9YVQXgOcONrwtnk3JupxQ==", - "deprecated": "This functionality has been moved to @npmcli/fs", + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "mkdirp": "^1.0.4", - "rimraf": "^3.0.2" - }, + "optional": true, + "os": [ + "android" + ], "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": ">=12" } }, - "node_modules/@npmcli/move-file/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">=12" } }, - "node_modules/@npmcli/move-file/node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "bin": { - "mkdirp": "bin/cmd.js" - }, + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=10" + "node": ">=12" } }, - "node_modules/@npmcli/move-file/node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "ISC", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" } }, - "node_modules/@npmcli/node-gyp": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@npmcli/node-gyp/-/node-gyp-3.0.0.tgz", - "integrity": "sha512-gp8pRXC2oOxu0DUE1/M3bYtb1b3/DbJ5aM113+XJBgfXdussRAsX0YOrOhdd8WvnAR6auDBvJomGAkLKA5ydxA==", + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], "dev": true, - "license": "ISC", + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": ">=12" } }, - "node_modules/@npmcli/promise-spawn": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-6.0.2.tgz", - "integrity": "sha512-gGq0NJkIGSwdbUt4yhdF8ZrmkGKVz9vAdVzpOfnom+V8PLSmSOVhZwbNvZZS1EYcJN5hzzKBxmmVVAInM6HQLg==", + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], "dev": true, - "license": "ISC", - "dependencies": { - "which": "^3.0.0" - }, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": ">=12" } }, - "node_modules/@npmcli/promise-spawn/node_modules/which": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/which/-/which-3.0.1.tgz", - "integrity": "sha512-XA1b62dzQzLfaEOSQFTCOd5KFf/1VSzZo7/7TUjnya6u0vGGKzU96UQBZTAThCb2j4/xjBAyii1OhRLJEivHvg==", + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/which.js" - }, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": ">=12" } }, - "node_modules/@npmcli/run-script": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-6.0.2.tgz", - "integrity": "sha512-NCcr1uQo1k5U+SYlnIrbAh3cxy+OQT1VtqiAbxdymSlptbzBb62AjH2xXgjNCoP073hoa1CfCAcwoZ8k96C4nA==", + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], "dev": true, - "license": "ISC", - "dependencies": { - "@npmcli/node-gyp": "^3.0.0", - "@npmcli/promise-spawn": "^6.0.0", - "node-gyp": "^9.0.0", - "read-package-json-fast": "^3.0.0", - "which": "^3.0.0" - }, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": ">=12" } }, - "node_modules/@npmcli/run-script/node_modules/which": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/which/-/which-3.0.1.tgz", - "integrity": "sha512-XA1b62dzQzLfaEOSQFTCOd5KFf/1VSzZo7/7TUjnya6u0vGGKzU96UQBZTAThCb2j4/xjBAyii1OhRLJEivHvg==", + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/which.js" - }, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": ">=12" } }, - "node_modules/@nuxtjs/opencollective": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@nuxtjs/opencollective/-/opencollective-0.3.2.tgz", - "integrity": "sha512-um0xL3fO7Mf4fDxcqx9KryrB7zgRM5JSlvGN5AGkP6JLM5XEKyjeAiPbNxdXVXQ16isuAhYpvP88NgL2BGd6aA==", + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], "dev": true, "license": "MIT", - "dependencies": { - "chalk": "^4.1.0", - "consola": "^2.15.0", - "node-fetch": "^2.6.1" - }, - "bin": { - "opencollective": "bin/opencollective.js" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=8.0.0", - "npm": ">=5.0.0" + "node": ">=12" } }, - "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, - "license": "MIT" - }, - "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==", + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@otplib/core": "^12.0.1" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" } }, - "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==", + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@otplib/core": "^12.0.1", - "thirty-two": "^1.0.2" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" } }, - "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==", + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], "dev": true, "license": "MIT", - "dependencies": { - "@otplib/core": "^12.0.1", - "@otplib/plugin-crypto": "^12.0.1", - "@otplib/plugin-thirty-two": "^12.0.1" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" } }, - "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==", + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@otplib/core": "^12.0.1", - "@otplib/plugin-crypto": "^12.0.1", - "@otplib/plugin-thirty-two": "^12.0.1" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" } }, - "node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", "optional": true, + "os": [ + "netbsd" + ], "engines": { - "node": ">=14" + "node": ">=12" } }, - "node_modules/@pnpm/config.env-replace": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@pnpm/config.env-replace/-/config.env-replace-1.1.0.tgz", - "integrity": "sha512-htyl8TWnKL7K/ESFa1oW2UB5lVDxuF5DpM7tBi6Hu2LNL3mWkIzNLG6N4zoCUP1lCKNxWy/3iu8mS8MvToGd6w==", + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], "engines": { - "node": ">=12.22.0" + "node": ">=12" } }, - "node_modules/@pnpm/network.ca-file": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@pnpm/network.ca-file/-/network.ca-file-1.0.2.tgz", - "integrity": "sha512-YcPQ8a0jwYU9bTdJDpXjMi7Brhkr1mXsXrUJvjqM2mQDgkRiz8jFaQGOdaLxgjtUfQgZhKy/O3cG/YwmgKaxLA==", + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "graceful-fs": "4.2.10" - }, + "optional": true, + "os": [ + "sunos" + ], "engines": { - "node": ">=12.22.0" + "node": ">=12" } }, - "node_modules/@pnpm/network.ca-file/node_modules/graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "ISC" + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } }, - "node_modules/@pnpm/npm-conf": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/@pnpm/npm-conf/-/npm-conf-2.2.2.tgz", - "integrity": "sha512-UA91GwWPhFExt3IizW6bOeY/pQ0BkuNwKjk9iQW9KqxluGCrg4VenZ0/L+2Y0+ZOtme72EVvg6v0zo3AMQRCeA==", + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], "dev": true, "license": "MIT", - "dependencies": { - "@pnpm/config.env-replace": "^1.1.0", - "@pnpm/network.ca-file": "^1.0.1", - "config-chain": "^1.1.11" - }, + "optional": true, + "os": [ + "win32" + ], "engines": { "node": ">=12" } }, - "node_modules/@serialport/binding-mock": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/@serialport/binding-mock/-/binding-mock-10.2.2.tgz", - "integrity": "sha512-HAFzGhk9OuFMpuor7aT5G1ChPgn5qSsklTFOTUX72Rl6p0xwcSVsRtG/xaGp6bxpN7fI9D/S8THLBWbBgS6ldw==", + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, "license": "MIT", "optional": true, - "dependencies": { - "@serialport/bindings-interface": "^1.2.1", - "debug": "^4.3.3" - }, + "os": [ + "win32" + ], "engines": { - "node": ">=12.0.0" + "node": ">=12" } }, - "node_modules/@serialport/bindings-cpp": { - "version": "12.0.1", - "resolved": "https://registry.npmjs.org/@serialport/bindings-cpp/-/bindings-cpp-12.0.1.tgz", - "integrity": "sha512-r2XOwY2dDvbW7dKqSPIk2gzsr6M6Qpe9+/Ngs94fNaNlcTRCV02PfaoDmRgcubpNVVcLATlxSxPTIDw12dbKOg==", - "hasInstallScript": true, + "node_modules/@eslint-community/eslint-plugin-eslint-comments": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-plugin-eslint-comments/-/eslint-plugin-eslint-comments-4.4.0.tgz", + "integrity": "sha512-yljsWl5Qv3IkIRmJ38h3NrHXFCm4EUl55M8doGTF6hvzvFF8kRpextgSrg2dwHev9lzBZyafCr9RelGIyQm6fw==", + "dev": true, "license": "MIT", - "optional": true, "dependencies": { - "@serialport/bindings-interface": "1.2.2", - "@serialport/parser-readline": "11.0.0", - "debug": "4.3.4", - "node-addon-api": "7.0.0", - "node-gyp-build": "4.6.0" + "escape-string-regexp": "^4.0.0", + "ignore": "^5.2.4" }, "engines": { - "node": ">=16.0.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { - "url": "https://opencollective.com/serialport/donate" + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0" } }, - "node_modules/@serialport/bindings-cpp/node_modules/@serialport/parser-delimiter": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/@serialport/parser-delimiter/-/parser-delimiter-11.0.0.tgz", - "integrity": "sha512-aZLJhlRTjSmEwllLG7S4J8s8ctRAS0cbvCpO87smLvl3e4BgzbVgF6Z6zaJd3Aji2uSiYgfedCdNc4L6W+1E2g==", + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, "license": "MIT", - "optional": true, + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, "engines": { - "node": ">=12.0.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, - "funding": { - "url": "https://opencollective.com/serialport/donate" + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, - "node_modules/@serialport/bindings-cpp/node_modules/@serialport/parser-readline": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/@serialport/parser-readline/-/parser-readline-11.0.0.tgz", - "integrity": "sha512-rRAivhRkT3YO28WjmmG4FQX6L+KMb5/ikhyylRfzWPw0nSXy97+u07peS9CbHqaNvJkMhH1locp2H36aGMOEIA==", - "license": "MIT", - "optional": true, - "dependencies": { - "@serialport/parser-delimiter": "11.0.0" - }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", "engines": { - "node": ">=12.0.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { - "url": "https://opencollective.com/serialport/donate" + "url": "https://opencollective.com/eslint" } }, - "node_modules/@serialport/bindings-cpp/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "node_modules/@eslint-community/regexpp": { + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.0.tgz", + "integrity": "sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==", + "dev": true, "license": "MIT", - "optional": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/compat": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@eslint/compat/-/compat-1.1.1.tgz", + "integrity": "sha512-lpHyRyplhGPL5mGEh6M9O5nnKk0Gz4bFI+Zu6tKlPpDUN7XshWvH9C/px4UVm87IAANE0W81CEsNGbS1KlzXpA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.18.0.tgz", + "integrity": "sha512-fTxvnS1sRMu3+JjXwJG0j/i4RT9u4qJ+lqS/yCGap4lH4zZGzQ7tu+xZqQmcMZq5OBZDL4QRxQzRjkWcGt8IVw==", + "dev": true, + "license": "Apache-2.0", "dependencies": { - "ms": "2.1.2" + "@eslint/object-schema": "^2.1.4", + "debug": "^4.3.1", + "minimatch": "^3.1.2" }, "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@serialport/bindings-cpp/node_modules/node-addon-api": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.0.0.tgz", - "integrity": "sha512-vgbBJTS4m5/KkE16t5Ly0WW9hz46swAstv0hYYwMtbG7AznRhNyfLRe8HZAiWIpcHzoO7HxhLuBQj9rJ/Ho0ZA==", - "license": "MIT", - "optional": true - }, - "node_modules/@serialport/bindings-cpp/node_modules/node-gyp-build": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.6.0.tgz", - "integrity": "sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ==", + "node_modules/@eslint/config-array/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, "license": "MIT", - "optional": true, - "bin": { - "node-gyp-build": "bin.js", - "node-gyp-build-optional": "optional.js", - "node-gyp-build-test": "build-test.js" + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, - "node_modules/@serialport/bindings-interface": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@serialport/bindings-interface/-/bindings-interface-1.2.2.tgz", - "integrity": "sha512-CJaUd5bLvtM9c5dmO9rPBHPXTa9R2UwpkJ0wdh9JCYcbrPWsKz+ErvR0hBLeo7NPeiFdjFO4sonRljiw4d2XiA==", - "license": "MIT", - "optional": true, + "node_modules/@eslint/config-array/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, "engines": { - "node": "^12.22 || ^14.13 || >=16" + "node": "*" } }, - "node_modules/@serialport/parser-byte-length": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/@serialport/parser-byte-length/-/parser-byte-length-12.0.0.tgz", - "integrity": "sha512-0ei0txFAj+s6FTiCJFBJ1T2hpKkX8Md0Pu6dqMrYoirjPskDLJRgZGLqoy3/lnU1bkvHpnJO+9oJ3PB9v8rNlg==", + "node_modules/@eslint/eslintrc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.1.0.tgz", + "integrity": "sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ==", + "dev": true, "license": "MIT", - "optional": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, "engines": { - "node": ">=12.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://opencollective.com/serialport/donate" + "url": "https://opencollective.com/eslint" } }, - "node_modules/@serialport/parser-cctalk": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/@serialport/parser-cctalk/-/parser-cctalk-12.0.0.tgz", - "integrity": "sha512-0PfLzO9t2X5ufKuBO34DQKLXrCCqS9xz2D0pfuaLNeTkyGUBv426zxoMf3rsMRodDOZNbFblu3Ae84MOQXjnZw==", + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, "license": "MIT", - "optional": true, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://opencollective.com/serialport/donate" + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, - "node_modules/@serialport/parser-delimiter": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/@serialport/parser-delimiter/-/parser-delimiter-12.0.0.tgz", - "integrity": "sha512-gu26tVt5lQoybhorLTPsH2j2LnX3AOP2x/34+DUSTNaUTzu2fBXw+isVjQJpUBFWu6aeQRZw5bJol5X9Gxjblw==", + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, "license": "MIT", - "optional": true, "engines": { - "node": ">=12.0.0" + "node": ">=18" }, "funding": { - "url": "https://opencollective.com/serialport/donate" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@serialport/parser-inter-byte-timeout": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/@serialport/parser-inter-byte-timeout/-/parser-inter-byte-timeout-12.0.0.tgz", - "integrity": "sha512-GnCh8K0NAESfhCuXAt+FfBRz1Cf9CzIgXfp7SdMgXwrtuUnCC/yuRTUFWRvuzhYKoAo1TL0hhUo77SFHUH1T/w==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=12.0.0" + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" }, - "funding": { - "url": "https://opencollective.com/serialport/donate" + "engines": { + "node": "*" } }, - "node_modules/@serialport/parser-packet-length": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/@serialport/parser-packet-length/-/parser-packet-length-12.0.0.tgz", - "integrity": "sha512-p1hiCRqvGHHLCN/8ZiPUY/G0zrxd7gtZs251n+cfNTn+87rwcdUeu9Dps3Aadx30/sOGGFL6brIRGK4l/t7MuQ==", + "node_modules/@eslint/js": { + "version": "9.10.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.10.0.tgz", + "integrity": "sha512-fuXtbiP5GWIn8Fz+LWoOMVf/Jxm+aajZYkhi6CuEm4SxymFM+eUWzbO9qXT+L0iCkL5+KGYMCSGxo686H19S1g==", + "dev": true, "license": "MIT", - "optional": true, "engines": { - "node": ">=8.6.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@serialport/parser-readline": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/@serialport/parser-readline/-/parser-readline-12.0.0.tgz", - "integrity": "sha512-O7cywCWC8PiOMvo/gglEBfAkLjp/SENEML46BXDykfKP5mTPM46XMaX1L0waWU6DXJpBgjaL7+yX6VriVPbN4w==", + "node_modules/@eslint/markdown": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@eslint/markdown/-/markdown-6.1.0.tgz", + "integrity": "sha512-cX1tyD+aIbhzKrCKe/9M5s2jZhldWGOR+cy7cIVpxG9RkoaN4XU+gG3dy6oEKtBFXjDx06GtP0OGO7jgbqa2DA==", + "dev": true, "license": "MIT", - "optional": true, "dependencies": { - "@serialport/parser-delimiter": "12.0.0" + "mdast-util-from-markdown": "^2.0.1", + "mdast-util-gfm": "^3.0.0", + "micromark-extension-gfm": "^3.0.0" }, "engines": { - "node": ">=12.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, - "funding": { - "url": "https://opencollective.com/serialport/donate" + "peerDependencies": { + "eslint": ">=9" } }, - "node_modules/@serialport/parser-ready": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/@serialport/parser-ready/-/parser-ready-12.0.0.tgz", - "integrity": "sha512-ygDwj3O4SDpZlbrRUraoXIoIqb8sM7aMKryGjYTIF0JRnKeB1ys8+wIp0RFMdFbO62YriUDextHB5Um5cKFSWg==", - "license": "MIT", - "optional": true, + "node_modules/@eslint/object-schema": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.4.tgz", + "integrity": "sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==", + "dev": true, + "license": "Apache-2.0", "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://opencollective.com/serialport/donate" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@serialport/parser-regex": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/@serialport/parser-regex/-/parser-regex-12.0.0.tgz", - "integrity": "sha512-dCAVh4P/pZrLcPv9NJ2mvPRBg64L5jXuiRxIlyxxdZGH4WubwXVXY/kBTihQmiAMPxbT3yshSX8f2+feqWsxqA==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=12.0.0" + "node_modules/@eslint/plugin-kit": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.1.0.tgz", + "integrity": "sha512-autAXT203ixhqei9xt+qkYOvY8l6LAFIdT2UXc/RPNeUVfqRF1BV94GTJyVPFKT8nFM6MyVJhjLj9E8JWvf5zQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "levn": "^0.4.1" }, - "funding": { - "url": "https://opencollective.com/serialport/donate" + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@serialport/parser-slip-encoder": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/@serialport/parser-slip-encoder/-/parser-slip-encoder-12.0.0.tgz", - "integrity": "sha512-0APxDGR9YvJXTRfY+uRGhzOhTpU5akSH183RUcwzN7QXh8/1jwFsFLCu0grmAUfi+fItCkR+Xr1TcNJLR13VNA==", + "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, "license": "MIT", - "optional": true, "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://opencollective.com/serialport/donate" + "node": ">=14" } }, - "node_modules/@serialport/parser-spacepacket": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/@serialport/parser-spacepacket/-/parser-spacepacket-12.0.0.tgz", - "integrity": "sha512-dozONxhPC/78pntuxpz/NOtVps8qIc/UZzdc/LuPvVsqCoJXiRxOg6ZtCP/W58iibJDKPZPAWPGYeZt9DJxI+Q==", + "node_modules/@fastify/ajv-compiler": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@fastify/ajv-compiler/-/ajv-compiler-3.6.0.tgz", + "integrity": "sha512-LwdXQJjmMD+GwLOkP7TVC68qa+pSSogeWWmznRJ/coyTcfe9qA05AHFSe1eZFwK6q+xVRpChnvFUkf1iYaSZsQ==", + "dev": true, "license": "MIT", - "optional": true, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://opencollective.com/serialport/donate" + "dependencies": { + "ajv": "^8.11.0", + "ajv-formats": "^2.1.1", + "fast-uri": "^2.0.0" } }, - "node_modules/@serialport/stream": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/@serialport/stream/-/stream-12.0.0.tgz", - "integrity": "sha512-9On64rhzuqKdOQyiYLYv2lQOh3TZU/D3+IWCR5gk0alPel2nwpp4YwDEGiUBfrQZEdQ6xww0PWkzqth4wqwX3Q==", + "node_modules/@fastify/ajv-compiler/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dev": true, "license": "MIT", - "optional": true, "dependencies": { - "@serialport/bindings-interface": "1.2.2", - "debug": "4.3.4" - }, - "engines": { - "node": ">=12.0.0" + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" }, "funding": { - "url": "https://opencollective.com/serialport/donate" + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/@serialport/stream/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "node_modules/@fastify/ajv-compiler/node_modules/ajv/node_modules/fast-uri": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.1.tgz", + "integrity": "sha512-MWipKbbYiYI0UC7cl8m/i/IWTqfC8YXsqjzybjddLsFjStroQzsHXkc73JutMvBiXmOvapk+axIl79ig5t55Bw==", + "dev": true, + "license": "MIT" + }, + "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, + "license": "MIT" + }, + "node_modules/@fastify/busboy": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", + "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==", + "dev": true, "license": "MIT", - "optional": true, - "dependencies": { - "ms": "2.1.2" - }, "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "node": ">=14" } }, - "node_modules/@shikijs/core": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-1.10.3.tgz", - "integrity": "sha512-D45PMaBaeDHxww+EkcDQtDAtzv00Gcsp72ukBtaLSmqRvh0WgGMq3Al0rl1QQBZfuneO75NXMIzEZGFitThWbg==", + "node_modules/@fastify/cors": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/@fastify/cors/-/cors-9.0.1.tgz", + "integrity": "sha512-YY9Ho3ovI+QHIL2hW+9X4XqQjXLjJqsU+sMV/xFsxZkE8p3GNnYVFpoOxF7SsP5ZL76gwvbo3V9L+FIekBGU4Q==", "dev": true, "license": "MIT", "dependencies": { - "@types/hast": "^3.0.4" + "fastify-plugin": "^4.0.0", + "mnemonist": "0.39.6" } }, - "node_modules/@sigstore/bundle": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@sigstore/bundle/-/bundle-1.1.0.tgz", - "integrity": "sha512-PFutXEy0SmQxYI4texPw3dd2KewuNqv7OuK1ZFtY2fM754yhvG2KdgwIhRnoEE2uHdtdGNQ8s0lb94dW9sELog==", + "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, - "license": "Apache-2.0", + "license": "MIT" + }, + "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, + "license": "MIT" + }, + "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, + "license": "MIT", "dependencies": { - "@sigstore/protobuf-specs": "^0.2.0" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "fast-json-stringify": "^5.7.0" } }, - "node_modules/@sigstore/protobuf-specs": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/@sigstore/protobuf-specs/-/protobuf-specs-0.2.1.tgz", - "integrity": "sha512-XTWVxnWJu+c1oCshMLwnKvz8ZQJJDVOlciMfgpJBQbThVjKTCG8dwyhgLngBD2KN0ap9F/gOV8rFDEx8uh7R2A==", + "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, - "license": "Apache-2.0", - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "license": "MIT", + "dependencies": { + "fast-querystring": "^1.0.0", + "fastify-plugin": "^4.0.0" } }, - "node_modules/@sigstore/sign": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@sigstore/sign/-/sign-1.0.0.tgz", - "integrity": "sha512-INxFVNQteLtcfGmcoldzV6Je0sbbfh9I16DM4yJPw3j5+TFP8X6uIiA18mvpEa9yyeycAKgPmOA3X9hVdVTPUA==", + "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, - "license": "Apache-2.0", + "license": "MIT", "dependencies": { - "@sigstore/bundle": "^1.1.0", - "@sigstore/protobuf-specs": "^0.2.0", - "make-fetch-happen": "^11.0.1" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "fastify-plugin": "^4.2.1", + "helmet": "^7.0.0" } }, - "node_modules/@sigstore/tuf": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@sigstore/tuf/-/tuf-1.0.3.tgz", - "integrity": "sha512-2bRovzs0nJZFlCN3rXirE4gwxCn97JNjMmwpecqlbgV9WcxX7WRuIrgzx/X7Ib7MYRbyUTpBYE0s2x6AmZXnlg==", + "node_modules/@fastify/merge-json-schemas": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@fastify/merge-json-schemas/-/merge-json-schemas-0.1.1.tgz", + "integrity": "sha512-fERDVz7topgNjtXsJTTW1JKLy0rhuLRcquYqNR9rF7OcVpCa2OVW49ZPDIhaRRCaUuvVxI+N416xUoF76HNSXA==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "dependencies": { - "@sigstore/protobuf-specs": "^0.2.0", - "tuf-js": "^1.1.7" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "fast-deep-equal": "^3.1.3" } }, - "node_modules/@sindresorhus/is": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-5.6.0.tgz", - "integrity": "sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==", + "node_modules/@fastify/middie": { + "version": "8.3.1", + "resolved": "https://registry.npmjs.org/@fastify/middie/-/middie-8.3.1.tgz", + "integrity": "sha512-qrQ8U3iCdjNum3+omnIvAyz21ifFx+Pp5jYW7PJJ7b9ueKTCPXsH6vEvaZQrjEZvOpTnWte+CswfBODWD0NqYQ==", "dev": true, "license": "MIT", - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sindresorhus/is?sponsor=1" + "dependencies": { + "@fastify/error": "^3.2.0", + "fastify-plugin": "^4.0.0", + "path-to-regexp": "^6.1.0", + "reusify": "^1.0.4" } }, - "node_modules/@socket.io/component-emitter": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", - "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", + "node_modules/@fastify/middie/node_modules/path-to-regexp": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.2.tgz", + "integrity": "sha512-GQX3SSMokngb36+whdpRXE+3f9V8UzyAorlYvOGx87ufGHehNTn5lCxrKtLyZ4Yl/wEKnNnr98ZzOwwDZV5ogw==", "dev": true, "license": "MIT" }, - "node_modules/@stoprocent/bluetooth-hci-socket": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@stoprocent/bluetooth-hci-socket/-/bluetooth-hci-socket-1.2.1.tgz", - "integrity": "sha512-s2yiocTcYyRpoOwrOZ5GeR7mGy6/SFXYdwSgK6Dy/uIqQnjCx1ZBhDPYnbnBdbkajRHKI4dDnz/LL0TesNQnEg==", - "hasInstallScript": true, + "node_modules/@fastify/multipart": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/@fastify/multipart/-/multipart-8.3.0.tgz", + "integrity": "sha512-A8h80TTyqUzaMVH0Cr9Qcm6RxSkVqmhK/MVBYHYeRRSUbUYv08WecjWKSlG2aSnD4aGI841pVxAjC+G1GafUeQ==", + "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux", - "android", - "freebsd", - "win32", - "darwin" - ], "dependencies": { - "async": "^3.2.4", - "debug": "^4.3.4", - "nan": "^2.17.0", - "node-addon-api": "^4.3.0", - "node-gyp-build": "^4.6.1", - "serialport": "^12.0.0" - }, - "optionalDependencies": { - "usb": "^1.9.2" + "@fastify/busboy": "^2.1.0", + "@fastify/deepmerge": "^1.0.0", + "@fastify/error": "^3.0.0", + "fastify-plugin": "^4.0.0", + "secure-json-parse": "^2.4.0", + "stream-wormhole": "^1.1.0" } }, - "node_modules/@stoprocent/noble": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@stoprocent/noble/-/noble-1.14.1.tgz", - "integrity": "sha512-m+ktaIV4EEtBIvAaPZKD8Z48PlCwKgdvP/nYvxkW6xfaJ6XPimxqsA02gXyobEUFUeXrvaS1JBy3qDzWQhXL0Q==", - "hasInstallScript": true, + "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, "license": "MIT", - "optional": true, "dependencies": { - "debug": "^4.3.4", - "napi-thread-safe-callback": "^0.0.6", - "node-addon-api": "^4.3.0", - "node-gyp-build": "^4.6.1" - }, - "engines": { - "node": ">=6" - }, - "optionalDependencies": { - "@stoprocent/bluetooth-hci-socket": "^1.2.1" + "@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/@stylistic/eslint-plugin": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin/-/eslint-plugin-2.3.0.tgz", - "integrity": "sha512-rtiz6u5gRyyEZp36FcF1/gHJbsbT3qAgXZ1qkad6Nr/xJ9wrSJkiSFFQhpYVTIZ7FJNRJurEcumZDCwN9dEI4g==", + "node_modules/@fastify/static": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/@fastify/static/-/static-7.0.4.tgz", + "integrity": "sha512-p2uKtaf8BMOZWLs6wu+Ihg7bWNBdjNgCwDza4MJtTqg+5ovKmcbgbR9Xs5/smZ1YISfzKOCNYmZV8LaCj+eJ1Q==", "dev": true, "license": "MIT", "dependencies": { - "@stylistic/eslint-plugin-js": "2.3.0", - "@stylistic/eslint-plugin-jsx": "2.3.0", - "@stylistic/eslint-plugin-plus": "2.3.0", - "@stylistic/eslint-plugin-ts": "2.3.0", - "@types/eslint": "^8.56.10" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "peerDependencies": { - "eslint": ">=8.40.0" + "@fastify/accept-negotiator": "^1.0.0", + "@fastify/send": "^2.0.0", + "content-disposition": "^0.5.3", + "fastify-plugin": "^4.0.0", + "fastq": "^1.17.0", + "glob": "^10.3.4" } }, - "node_modules/@stylistic/eslint-plugin-js": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin-js/-/eslint-plugin-js-2.3.0.tgz", - "integrity": "sha512-lQwoiYb0Fs6Yc5QS3uT8+T9CPKK2Eoxc3H8EnYJgM26v/DgtW+1lvy2WNgyBflU+ThShZaHm3a6CdD9QeKx23w==", + "node_modules/@homebridge/ciao": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@homebridge/ciao/-/ciao-1.3.1.tgz", + "integrity": "sha512-87tQCBNNnTymlbg8pKlQjRsk7a5uuqhWBpCbUriVYUebz3voJkLbbTmp0TQg7Sa6Jnpk/Uo6LA8zAOy2sbK9bw==", "dev": true, "license": "MIT", "dependencies": { - "@types/eslint": "^8.56.10", - "acorn": "^8.11.3", - "eslint-visitor-keys": "^4.0.0", - "espree": "^10.0.1" + "debug": "^4.3.6", + "fast-deep-equal": "^3.1.3", + "source-map-support": "^0.5.21", + "tslib": "^2.6.3" }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "bin": { + "ciao-bcs": "lib/bonjour-conformance-testing.js" }, - "peerDependencies": { - "eslint": ">=8.40.0" + "engines": { + "node": "^18 || ^20 || ^22" } }, - "node_modules/@stylistic/eslint-plugin-jsx": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin-jsx/-/eslint-plugin-jsx-2.3.0.tgz", - "integrity": "sha512-tsQ0IEKB195H6X9A4iUSgLLLKBc8gUBWkBIU8tp1/3g2l8stu+PtMQVV/VmK1+3bem5FJCyvfcZIQ/WF1fsizA==", + "node_modules/@homebridge/dbus-native": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@homebridge/dbus-native/-/dbus-native-0.6.0.tgz", + "integrity": "sha512-xObqQeYHTXmt6wsfj10+krTo4xbzR9BgUfX2aQ+edDC9nc4ojfzLScfXCh3zluAm6UCowKw+AFfXn6WLWUOPkg==", "dev": true, "license": "MIT", "dependencies": { - "@stylistic/eslint-plugin-js": "^2.3.0", - "@types/eslint": "^8.56.10", - "estraverse": "^5.3.0", - "picomatch": "^4.0.2" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "@homebridge/long": "^5.2.1", + "@homebridge/put": "^0.0.8", + "event-stream": "^4.0.1", + "hexy": "^0.3.5", + "minimist": "^1.2.6", + "safe-buffer": "^5.1.2", + "xml2js": "^0.6.2" }, - "peerDependencies": { - "eslint": ">=8.40.0" + "bin": { + "dbus2js": "bin/dbus2js.js" } }, - "node_modules/@stylistic/eslint-plugin-plus": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin-plus/-/eslint-plugin-plus-2.3.0.tgz", - "integrity": "sha512-xboPWGUU5yaPlR+WR57GwXEuY4PSlPqA0C3IdNA/+1o2MuBi95XgDJcZiJ9N+aXsqBXAPIpFFb+WQ7QEHo4f7g==", + "node_modules/@homebridge/hap-client": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/@homebridge/hap-client/-/hap-client-1.10.2.tgz", + "integrity": "sha512-ZpbIqeI0XxbGKsjIjqcTXSFt+pgK3y/RwbFbvXwvxWGzc6kgS/U/ms/R9Nr5IbpBhWkxptzO6o497uYMejAPJw==", "dev": true, "license": "MIT", "dependencies": { - "@types/eslint": "^8.56.10", - "@typescript-eslint/utils": "^7.12.0" - }, - "peerDependencies": { - "eslint": "*" + "axios": "1.6.8", + "bonjour-service": "1.2.1", + "decamelize": "5.0.1", + "inflection": "3.0.0", + "source-map-support": "0.5.21" } }, - "node_modules/@stylistic/eslint-plugin-plus/node_modules/@typescript-eslint/scope-manager": { - "version": "7.16.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.16.1.tgz", - "integrity": "sha512-nYpyv6ALte18gbMz323RM+vpFpTjfNdyakbf3nsLvF43uF9KeNC289SUEW3QLZ1xPtyINJ1dIsZOuWuSRIWygw==", + "node_modules/@homebridge/hap-client/node_modules/axios": { + "version": "1.6.8", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.8.tgz", + "integrity": "sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "7.16.1", - "@typescript-eslint/visitor-keys": "7.16.1" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" } }, - "node_modules/@stylistic/eslint-plugin-plus/node_modules/@typescript-eslint/types": { - "version": "7.16.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.16.1.tgz", - "integrity": "sha512-AQn9XqCzUXd4bAVEsAXM/Izk11Wx2u4H3BAfQVhSfzfDOm/wAON9nP7J5rpkCxts7E5TELmN845xTUCQrD1xIQ==", + "node_modules/@homebridge/long": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/@homebridge/long/-/long-5.2.1.tgz", + "integrity": "sha512-i5Df8R63XNPCn+Nj1OgAoRdw9e+jHUQb3CNUbvJneI2iu3j4+OtzQj+5PA1Ce+747NR1SPqZSvyvD483dOT3AA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/@homebridge/node-pty-prebuilt-multiarch": { + "version": "0.11.14", + "resolved": "https://registry.npmjs.org/@homebridge/node-pty-prebuilt-multiarch/-/node-pty-prebuilt-multiarch-0.11.14.tgz", + "integrity": "sha512-fuiq5kb4i0Ao0BTf7O6kvtwUhCCCJHLhWLWaaUaLuniDGS4xmj+gxvkidJpxYVT/zTXdbcLuCY44UnoWC7xODg==", "dev": true, + "hasInstallScript": true, "license": "MIT", + "dependencies": { + "nan": "^2.19.0", + "prebuild-install": "^7.1.2" + } + }, + "node_modules/@homebridge/plugin-ui-utils": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@homebridge/plugin-ui-utils/-/plugin-ui-utils-1.0.3.tgz", + "integrity": "sha512-p2S/czGYNRnRtMICxBUk4Uar+KCezfyxjqfStfxKgykD2082SNayVDncYUK1xRai78EGHCbif9eoyrmDweh4tQ==", + "license": "MIT" + }, + "node_modules/@homebridge/put": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/@homebridge/put/-/put-0.0.8.tgz", + "integrity": "sha512-mwxLHHqKebOmOSU0tsPEWQSBHGApPhuaqtNpCe7U+AMdsduweANiu64E9SXXUtdpyTjsOpgSMLhD1+kbLHD2gA==", + "dev": true, + "license": "MIT/X11", + "engines": { + "node": ">=0.3.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": ">=12.22" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "type": "github", + "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@stylistic/eslint-plugin-plus/node_modules/@typescript-eslint/typescript-estree": { - "version": "7.16.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.16.1.tgz", - "integrity": "sha512-0vFPk8tMjj6apaAZ1HlwM8w7jbghC8jc1aRNJG5vN8Ym5miyhTQGMqU++kuBFDNKe9NcPeZ6x0zfSzV8xC1UlQ==", + "node_modules/@humanwhocodes/retry": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.0.tgz", + "integrity": "sha512-d2CGZR2o7fS6sWB7DG/3a95bGKQyHMACZ5aW8qGkkqQpUoZV6C0X7Pc7l4ZNMZkfNBf4VWNe9E1jRsf0G146Ew==", "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "@typescript-eslint/types": "7.16.1", - "@typescript-eslint/visitor-keys": "7.16.1", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "minimatch": "^9.0.4", - "semver": "^7.6.0", - "ts-api-utils": "^1.3.0" - }, + "license": "Apache-2.0", "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": ">=18.18" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "type": "github", + "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@stylistic/eslint-plugin-plus/node_modules/@typescript-eslint/utils": { - "version": "7.16.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.16.1.tgz", - "integrity": "sha512-WrFM8nzCowV0he0RlkotGDujx78xudsxnGMBHI88l5J8wEhED6yBwaSLP99ygfrzAjsQvcYQ94quDwI0d7E1fA==", + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "7.16.1", - "@typescript-eslint/types": "7.16.1", - "@typescript-eslint/typescript-estree": "7.16.1" + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" }, "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.56.0" + "node": ">=12" } }, - "node_modules/@stylistic/eslint-plugin-plus/node_modules/@typescript-eslint/visitor-keys": { - "version": "7.16.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.16.1.tgz", - "integrity": "sha512-Qlzzx4sE4u3FsHTPQAAQFJFNOuqtuY0LFrZHwQ8IHK705XxBiWOFkfKRWu6niB7hwfgnwIpO4jTC75ozW1PHWg==", + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", "dev": true, "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "7.16.1", - "eslint-visitor-keys": "^3.4.3" - }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": ">=12" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, - "node_modules/@stylistic/eslint-plugin-plus/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/@stylistic/eslint-plugin-plus/node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, - "license": "Apache-2.0", + "ansi-regex": "^6.0.1" + }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": ">=12" }, "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/@stylistic/eslint-plugin-plus/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", "dev": true, "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.1" + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" }, "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">=8" } }, - "node_modules/@stylistic/eslint-plugin-ts": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin-ts/-/eslint-plugin-ts-2.3.0.tgz", - "integrity": "sha512-wqOR38/uz/0XPnHX68ftp8sNMSAqnYGjovOTN7w00xnjS6Lxr3Sk7q6AaxWWqbMvOj7V2fQiMC5HWAbTruJsCg==", + "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", "dev": true, "license": "MIT", "dependencies": { - "@stylistic/eslint-plugin-js": "2.3.0", - "@types/eslint": "^8.56.10", - "@typescript-eslint/utils": "^7.12.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "peerDependencies": { - "eslint": ">=8.40.0" + "sprintf-js": "~1.0.2" } }, - "node_modules/@stylistic/eslint-plugin-ts/node_modules/@typescript-eslint/scope-manager": { - "version": "7.16.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.16.1.tgz", - "integrity": "sha512-nYpyv6ALte18gbMz323RM+vpFpTjfNdyakbf3nsLvF43uF9KeNC289SUEW3QLZ1xPtyINJ1dIsZOuWuSRIWygw==", + "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "7.16.1", - "@typescript-eslint/visitor-keys": "7.16.1" + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" }, "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "node": ">=8" } }, - "node_modules/@stylistic/eslint-plugin-ts/node_modules/@typescript-eslint/types": { - "version": "7.16.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.16.1.tgz", - "integrity": "sha512-AQn9XqCzUXd4bAVEsAXM/Izk11Wx2u4H3BAfQVhSfzfDOm/wAON9nP7J5rpkCxts7E5TELmN845xTUCQrD1xIQ==", + "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", "dev": true, "license": "MIT", - "engines": { - "node": "^18.18.0 || >=20.0.0" + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "bin": { + "js-yaml": "bin/js-yaml.js" } }, - "node_modules/@stylistic/eslint-plugin-ts/node_modules/@typescript-eslint/typescript-estree": { - "version": "7.16.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.16.1.tgz", - "integrity": "sha512-0vFPk8tMjj6apaAZ1HlwM8w7jbghC8jc1aRNJG5vN8Ym5miyhTQGMqU++kuBFDNKe9NcPeZ6x0zfSzV8xC1UlQ==", + "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "dev": true, - "license": "BSD-2-Clause", + "license": "MIT", "dependencies": { - "@typescript-eslint/types": "7.16.1", - "@typescript-eslint/visitor-keys": "7.16.1", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "minimatch": "^9.0.4", - "semver": "^7.6.0", - "ts-api-utils": "^1.3.0" + "p-locate": "^4.1.0" }, "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "node": ">=8" } }, - "node_modules/@stylistic/eslint-plugin-ts/node_modules/@typescript-eslint/utils": { - "version": "7.16.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.16.1.tgz", - "integrity": "sha512-WrFM8nzCowV0he0RlkotGDujx78xudsxnGMBHI88l5J8wEhED6yBwaSLP99ygfrzAjsQvcYQ94quDwI0d7E1fA==", + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "dev": true, "license": "MIT", "dependencies": { - "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "7.16.1", - "@typescript-eslint/types": "7.16.1", - "@typescript-eslint/typescript-estree": "7.16.1" + "p-try": "^2.0.0" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": ">=6" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.56.0" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@stylistic/eslint-plugin-ts/node_modules/@typescript-eslint/visitor-keys": { - "version": "7.16.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.16.1.tgz", - "integrity": "sha512-Qlzzx4sE4u3FsHTPQAAQFJFNOuqtuY0LFrZHwQ8IHK705XxBiWOFkfKRWu6niB7hwfgnwIpO4jTC75ozW1PHWg==", + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "7.16.1", - "eslint-visitor-keys": "^3.4.3" + "p-limit": "^2.2.0" }, "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "node": ">=8" } }, - "node_modules/@stylistic/eslint-plugin-ts/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", "dev": true, "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" + "engines": { + "node": ">=8" } }, - "node_modules/@stylistic/eslint-plugin-ts/node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" + "node": ">=8" } }, - "node_modules/@stylistic/eslint-plugin-ts/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "node_modules/@jest/console": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", + "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "brace-expansion": "^2.0.1" + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0" }, "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@szmarczak/http-timer": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-5.0.1.tgz", - "integrity": "sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==", + "node_modules/@jest/core": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", + "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", "dev": true, "license": "MIT", "dependencies": { - "defer-to-connect": "^2.0.1" + "@jest/console": "^29.7.0", + "@jest/reporters": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^29.7.0", + "jest-config": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-resolve-dependencies": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "jest-watcher": "^29.7.0", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" }, "engines": { - "node": ">=14.16" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } } }, - "node_modules/@tootallnate/once": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", - "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "node_modules/@jest/core/node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10" + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" } }, - "node_modules/@tsconfig/node10": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", - "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", + "node_modules/@jest/environment": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", + "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", "dev": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } }, - "node_modules/@tsconfig/node12": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", - "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "node_modules/@jest/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "expect": "^29.7.0", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } }, - "node_modules/@tsconfig/node14": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", - "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "node_modules/@jest/expect-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", + "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", "dev": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "jest-get-type": "^29.6.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } }, - "node_modules/@tsconfig/node16": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", - "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "node_modules/@jest/fake-timers": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", + "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } }, - "node_modules/@tufjs/canonical-json": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@tufjs/canonical-json/-/canonical-json-1.0.0.tgz", - "integrity": "sha512-QTnf++uxunWvG2z3UFNzAoQPHxnSXOwtaI3iJ+AohhV+5vONuArPjJE7aPXPVXfXJsqrVbZBu9b81AJoSd09IQ==", + "node_modules/@jest/globals": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", + "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", "dev": true, "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/types": "^29.6.3", + "jest-mock": "^29.7.0" + }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@tufjs/models": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@tufjs/models/-/models-1.0.4.tgz", - "integrity": "sha512-qaGV9ltJP0EO25YfFUPhxRVK0evXFIAGicsVXuRim4Ed9cjPxYhNnNJ49SFmbeLgtxpslIkX317IgpfcHPVj/A==", + "node_modules/@jest/reporters": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", + "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", "dev": true, "license": "MIT", "dependencies": { - "@tufjs/canonical-json": "1.0.0", - "minimatch": "^9.0.0" + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^6.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "v8-to-istanbul": "^9.0.1" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } } }, - "node_modules/@tufjs/models/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "node_modules/@jest/reporters/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0" + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, - "node_modules/@tufjs/models/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "node_modules/@jest/reporters/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", "dev": true, "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.1" + "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": ">=16 || 14 >=14.17" + "node": "*" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, - "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, - "license": "MIT" - }, - "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==", + "node_modules/@jest/reporters/node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", "dev": true, - "license": "MIT", + "license": "BSD-3-Clause", "dependencies": { - "@types/node": "*" + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" } }, - "node_modules/@types/eslint": { - "version": "8.56.10", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.10.tgz", - "integrity": "sha512-Shavhk87gCtY2fhXDctcfS3e6FdxWkCx1iUZ9eEUbh7rTqlZT0/IzOkCOVt0fCjcFuZ9FPYfuezTBImfHCDBGQ==", + "node_modules/@jest/reporters/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "@types/estree": "*", - "@types/json-schema": "*" + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" } }, - "node_modules/@types/eslint__js": { - "version": "8.42.3", - "resolved": "https://registry.npmjs.org/@types/eslint__js/-/eslint__js-8.42.3.tgz", - "integrity": "sha512-alfG737uhmPdnvkrLdZLcEKJ/B8s9Y4hrZ+YAdzUeoArBlSUERA2E87ROfOaS4jd/C45fzOoZzidLc1IPwLqOw==", + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", "dev": true, "license": "MIT", "dependencies": { - "@types/eslint": "*" + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@types/estree": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", - "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/hast": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", - "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "node_modules/@jest/source-map": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", + "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", "dev": true, "license": "MIT", "dependencies": { - "@types/unist": "*" + "@jridgewell/trace-mapping": "^0.3.18", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "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==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/json-schema": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/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==", + "node_modules/@jest/test-result": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", + "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", "dev": true, "license": "MIT", "dependencies": { - "@types/node": "*" + "@jest/console": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@types/node": { - "version": "20.14.11", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.11.tgz", - "integrity": "sha512-kprQpL8MMeszbz6ojB5/tU8PLN4kesnN8Gjzw349rDlNgsSzg90lAVj3llK99Dh7JON+t9AuscPPFW6mPbTnSA==", + "node_modules/@jest/test-sequencer": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", + "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", "dev": true, "license": "MIT", "dependencies": { - "undici-types": "~5.26.4" + "@jest/test-result": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@types/semver-utils": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@types/semver-utils/-/semver-utils-1.1.3.tgz", - "integrity": "sha512-T+YwkslhsM+CeuhYUxyAjWm7mJ5am/K10UX40RuA6k6Lc7eGtq8iY2xOzy7Vq0GOqhl/xZl5l2FwURZMTPTUww==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/unist": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.2.tgz", - "integrity": "sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/validator": { - "version": "13.12.0", - "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.12.0.tgz", - "integrity": "sha512-nH45Lk7oPIJ1RVOF6JgFI6Dy0QpHEzq4QecZhvguxYPDwT8c93prCMqAtiIttm39voZ+DDR+qkNnMpJmMBRqag==", - "dev": true, - "license": "MIT" - }, - "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.0.0-alpha.48", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.0.0-alpha.48.tgz", - "integrity": "sha512-A85+5m5HBzZmlzmm5Q/bfeFlIsZeg+2Xx6rsLCIjvg6zUfheD0wY/dRVAF3jCDueHVhxNXtTt3nu9sK7O4PFBw==", + "node_modules/@jest/transform": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", "dev": true, "license": "MIT", "dependencies": { - "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.0.0-alpha.48", - "@typescript-eslint/type-utils": "8.0.0-alpha.48", - "@typescript-eslint/utils": "8.0.0-alpha.48", - "@typescript-eslint/visitor-keys": "8.0.0-alpha.48", - "graphemer": "^1.4.0", - "ignore": "^5.3.1", - "natural-compare": "^1.4.0", - "ts-api-utils": "^1.3.0" + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", - "eslint": "^8.57.0 || ^9.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@typescript-eslint/parser": { - "version": "8.0.0-alpha.48", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.0.0-alpha.48.tgz", - "integrity": "sha512-ZogOkp/zvS14zGfQlpbjGvaoxJZArfZ9u40Gz+LG+1nu/W7Pw093oKp0RbF0exUceMvLarFdcP7T8T15uGG4vA==", + "node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", "dev": true, - "license": "BSD-2-Clause", + "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.0.0-alpha.48", - "@typescript-eslint/types": "8.0.0-alpha.48", - "@typescript-eslint/typescript-estree": "8.0.0-alpha.48", - "@typescript-eslint/visitor-keys": "8.0.0-alpha.48", - "debug": "^4.3.4" + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "8.0.0-alpha.48", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.0.0-alpha.48.tgz", - "integrity": "sha512-7xX6vkXGr/w0dLaaf5t4qIZlhNpex9I2/b66iTajm9hazWMTnWRgHiGB12Bi6QbwCcWMDU44zPdJdeZzCubTmQ==", + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.0.0-alpha.48", - "@typescript-eslint/visitor-keys": "8.0.0-alpha.48" + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "node": ">=6.0.0" } }, - "node_modules/@typescript-eslint/type-utils": { - "version": "8.0.0-alpha.48", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.0.0-alpha.48.tgz", - "integrity": "sha512-KIE8a6QvolS6OkszTHIS/fz0OReuh09NxiR5Cfy8ajYfaA3j3bDiOzM1BPr30PQxXhO/2hIEQngpdcn156+RNw==", + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "dev": true, "license": "MIT", - "dependencies": { - "@typescript-eslint/typescript-estree": "8.0.0-alpha.48", - "@typescript-eslint/utils": "8.0.0-alpha.48", - "debug": "^4.3.4", - "ts-api-utils": "^1.3.0" - }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "node": ">=6.0.0" } }, - "node_modules/@typescript-eslint/types": { - "version": "8.0.0-alpha.48", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.0.0-alpha.48.tgz", - "integrity": "sha512-dEgLs9NHLAp2/fb32lq7wQ33saY2gklXuAoNJtygg1z+yC3IhCdDZcAOcQEW7jRXSlgCEKiLNKEL2opAXOnCBQ==", + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", "dev": true, "license": "MIT", "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "node": ">=6.0.0" } }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.0.0-alpha.48", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.0.0-alpha.48.tgz", - "integrity": "sha512-BE2wi1Dn58vcKp211lTFhtS7MKNbaSaf/u6WKGvr190KrZrfOgPbLl1HsSmM1sHMcDKkCOpWsSOAsiV25/m6MQ==", + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", "dev": true, - "license": "BSD-2-Clause", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.0.0-alpha.48", - "@typescript-eslint/visitor-keys": "8.0.0-alpha.48", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "minimatch": "^9.0.4", - "semver": "^7.6.0", - "ts-api-utils": "^1.3.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "node_modules/@leichtgewicht/ip-codec": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", + "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==", + "license": "MIT" + }, + "node_modules/@lukeed/csprng": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@lukeed/csprng/-/csprng-1.1.0.tgz", + "integrity": "sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA==", "dev": true, "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" + "engines": { + "node": ">=8" } }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "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, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, + "license": "MIT", "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">=8" } }, - "node_modules/@typescript-eslint/utils": { - "version": "8.0.0-alpha.48", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.0.0-alpha.48.tgz", - "integrity": "sha512-SJGwf8VJ82FVkd+LlkIj6bRpkHAiypxHXOu5KYNlql0W8IGe5TE7x3oHMnQfvpGXxT3jjD+a8REFtoMVKTlbbA==", + "node_modules/@microsoft/tsdoc": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.15.0.tgz", + "integrity": "sha512-HZpPoABogPvjeJOdzCOSJsXeL/SMCBgBZMVC3X3d7YYp2gf31MfxhUoYUNwf1ERPJOnQc0wkFn9trqI6ZEdZuA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@nestjs/axios": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@nestjs/axios/-/axios-3.0.3.tgz", + "integrity": "sha512-h6TCn3yJwD6OKqqqfmtRS5Zo4E46Ip2n+gK1sqwzNBC+qxQ9xpCu+ODVRFur6V3alHSCSBxb3nNtt73VEdluyA==", "dev": true, "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.0.0-alpha.48", - "@typescript-eslint/types": "8.0.0-alpha.48", - "@typescript-eslint/typescript-estree": "8.0.0-alpha.48" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0" + "@nestjs/common": "^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0", + "axios": "^1.3.1", + "rxjs": "^6.0.0 || ^7.0.0" } }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.0.0-alpha.48", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.0.0-alpha.48.tgz", - "integrity": "sha512-jLJ7sPwGvL17b7k2HxfPy6T8WtZizEvCNICg33xp9viATYeOOyP2rg386AzXf5EZ5YeH/SrxojTMurYJp4PA4w==", + "node_modules/@nestjs/common": { + "version": "10.4.1", + "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-10.4.1.tgz", + "integrity": "sha512-4CkrDx0s4XuWqFjX8WvOFV7Y6RGJd0P2OBblkhZS7nwoctoSuW5pyEa8SWak6YHNGrHRpFb6ymm5Ai4LncwRVA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.0.0-alpha.48", - "eslint-visitor-keys": "^3.4.3" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "iterare": "1.2.1", + "tslib": "2.6.3", + "uid": "2.0.2" }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "url": "https://opencollective.com/nest" }, - "funding": { - "url": "https://opencollective.com/eslint" + "peerDependencies": { + "class-transformer": "*", + "class-validator": "*", + "reflect-metadata": "^0.1.12 || ^0.2.0", + "rxjs": "^7.1.0" + }, + "peerDependenciesMeta": { + "class-transformer": { + "optional": true + }, + "class-validator": { + "optional": true + } } }, - "node_modules/abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "node_modules/@nestjs/common/node_modules/tslib": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", "dev": true, - "license": "ISC" + "license": "0BSD" }, - "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==", + "node_modules/@nestjs/core": { + "version": "10.4.1", + "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-10.4.1.tgz", + "integrity": "sha512-9I1WdfOBCCHdUm+ClBJupOuZQS6UxzIWHIq6Vp1brAA5ZKl/Wq6BVwSsbnUJGBy3J3PM2XHmR0EQ4fwX3nR7lA==", "dev": true, + "hasInstallScript": true, "license": "MIT", "dependencies": { - "event-target-shim": "^5.0.0" + "@nuxtjs/opencollective": "0.3.2", + "fast-safe-stringify": "2.1.1", + "iterare": "1.2.1", + "path-to-regexp": "3.2.0", + "tslib": "2.6.3", + "uid": "2.0.2" }, - "engines": { - "node": ">=6.5" + "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 || ^0.2.0", + "rxjs": "^7.1.0" + }, + "peerDependenciesMeta": { + "@nestjs/microservices": { + "optional": true + }, + "@nestjs/platform-express": { + "optional": true + }, + "@nestjs/websockets": { + "optional": true + } } }, - "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==", + "node_modules/@nestjs/core/node_modules/tslib": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", "dev": true, - "license": "MIT" + "license": "0BSD" }, - "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==", + "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, "license": "MIT", "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" + "@types/jsonwebtoken": "9.0.5", + "jsonwebtoken": "9.0.2" }, - "engines": { - "node": ">= 0.6" + "peerDependencies": { + "@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0" } }, - "node_modules/acorn": { - "version": "8.12.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", - "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", + "node_modules/@nestjs/mapped-types": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nestjs/mapped-types/-/mapped-types-2.0.5.tgz", + "integrity": "sha512-bSJv4pd6EY99NX9CjBIyn4TVDoSit82DUZlL4I3bqNfy5Gt+gXTa86i3I/i0iIV9P4hntcGM5GyO+FhZAhxtyg==", "dev": true, "license": "MIT", - "bin": { - "acorn": "bin/acorn" + "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 || ^0.2.0" }, - "engines": { - "node": ">=0.4.0" + "peerDependenciesMeta": { + "class-transformer": { + "optional": true + }, + "class-validator": { + "optional": true + } } }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "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, "license": "MIT", "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + "@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/acorn-walk": { - "version": "8.3.3", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.3.tgz", - "integrity": "sha512-MxXdReSRhGO7VlFe1bRG/oI7/mdLV9B9JJT0N8vZOhF7gFRR5l3M8W9G8JxmKV+JC5mGqJ0QvqfSOLsCPa4nUw==", + "node_modules/@nestjs/platform-fastify": { + "version": "10.4.1", + "resolved": "https://registry.npmjs.org/@nestjs/platform-fastify/-/platform-fastify-10.4.1.tgz", + "integrity": "sha512-e7bpAF6MAB71MxzeqLBVXSiYNNcx2vkG8tEXnY08Mr2Qp922Ya1AQta5xVQ5avYep3wy98bhJVeoEN7h65xh8w==", "dev": true, "license": "MIT", "dependencies": { - "acorn": "^8.11.0" + "@fastify/cors": "9.0.1", + "@fastify/formbody": "7.4.0", + "@fastify/middie": "8.3.1", + "fastify": "4.28.1", + "light-my-request": "5.13.0", + "path-to-regexp": "3.2.0", + "tslib": "2.6.3" }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/agent-base": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", - "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", - "license": "MIT", - "dependencies": { - "debug": "^4.3.4" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nest" }, - "engines": { - "node": ">= 14" + "peerDependencies": { + "@fastify/static": "^6.0.0 || ^7.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/agentkeepalive": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.5.0.tgz", - "integrity": "sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==", + "node_modules/@nestjs/platform-fastify/node_modules/tslib": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", "dev": true, - "license": "MIT", - "dependencies": { - "humanize-ms": "^1.2.1" - }, - "engines": { - "node": ">= 8.0.0" - } + "license": "0BSD" }, - "node_modules/aggregate-error": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", - "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "node_modules/@nestjs/platform-socket.io": { + "version": "10.4.1", + "resolved": "https://registry.npmjs.org/@nestjs/platform-socket.io/-/platform-socket.io-10.4.1.tgz", + "integrity": "sha512-cxn5vKBAbqtEVPl0qVcJpR4sC12+hzcY/mYXGW6ippOKQDBNc2OF8oZXu6V3O1MvAl+VM7eNNEsLmP9DRKQlnw==", "dev": true, "license": "MIT", "dependencies": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" + "socket.io": "4.7.5", + "tslib": "2.6.3" }, - "engines": { - "node": ">=8" + "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/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "node_modules/@nestjs/platform-socket.io/node_modules/tslib": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "dev": true, + "license": "0BSD" + }, + "node_modules/@nestjs/swagger": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@nestjs/swagger/-/swagger-7.4.0.tgz", + "integrity": "sha512-dCiwKkRxcR7dZs5jtrGspBAe/nqJd1AYzOBTzw9iCdbq3BGrLpwokelk6lFZPe4twpTsPQqzNKBwKzVbI6AR/g==", "dev": true, "license": "MIT", "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" + "@microsoft/tsdoc": "^0.15.0", + "@nestjs/mapped-types": "2.0.5", + "js-yaml": "4.1.0", + "lodash": "4.17.21", + "path-to-regexp": "3.2.0", + "swagger-ui-dist": "5.17.14" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "peerDependencies": { + "@fastify/static": "^6.0.0 || ^7.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 || ^0.2.0" + }, + "peerDependenciesMeta": { + "@fastify/static": { + "optional": true + }, + "class-transformer": { + "optional": true + }, + "class-validator": { + "optional": true + } } }, - "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==", + "node_modules/@nestjs/websockets": { + "version": "10.4.1", + "resolved": "https://registry.npmjs.org/@nestjs/websockets/-/websockets-10.4.1.tgz", + "integrity": "sha512-p0Eq94WneczV2bnLEu9hl24iCIfH5eUCGgBuYOkVDySBwvya5L+gD4wUoqIqGoX1c6rkhQa+pMR7pi1EY4t93w==", "dev": true, "license": "MIT", "dependencies": { - "ajv": "^8.0.0" + "iterare": "1.2.1", + "object-hash": "3.0.0", + "tslib": "2.6.3" }, "peerDependencies": { - "ajv": "^8.0.0" + "@nestjs/common": "^10.0.0", + "@nestjs/core": "^10.0.0", + "@nestjs/platform-socket.io": "^10.0.0", + "reflect-metadata": "^0.1.12 || ^0.2.0", + "rxjs": "^7.1.0" }, "peerDependenciesMeta": { - "ajv": { + "@nestjs/platform-socket.io": { "optional": true } } }, - "node_modules/ajv-formats/node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "node_modules/@nestjs/websockets/node_modules/tslib": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "dev": true, + "license": "0BSD" + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", "dev": true, "license": "MIT", "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "engines": { + "node": ">= 8" } }, - "node_modules/ajv-formats/node_modules/fast-uri": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.1.tgz", - "integrity": "sha512-MWipKbbYiYI0UC7cl8m/i/IWTqfC8YXsqjzybjddLsFjStroQzsHXkc73JutMvBiXmOvapk+axIl79ig5t55Bw==", + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">= 8" + } }, - "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==", + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", "dev": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } }, - "node_modules/ansi-align": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", - "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", + "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, - "license": "ISC", + "license": "MIT", "dependencies": { - "string-width": "^4.1.0" + "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/ansi-align/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "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, "license": "MIT" }, - "node_modules/ansi-align/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "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, "license": "MIT", "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" + "@otplib/core": "^12.0.1" } }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "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, "license": "MIT", - "engines": { - "node": ">=8" + "dependencies": { + "@otplib/core": "^12.0.1", + "thirty-two": "^1.0.2" } }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "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, "license": "MIT", "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "@otplib/core": "^12.0.1", + "@otplib/plugin-crypto": "^12.0.1", + "@otplib/plugin-thirty-two": "^12.0.1" } }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "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, - "license": "ISC", + "license": "MIT", "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" + "@otplib/core": "^12.0.1", + "@otplib/plugin-crypto": "^12.0.1", + "@otplib/plugin-thirty-two": "^12.0.1" } }, - "node_modules/anymatch/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", "dev": true, "license": "MIT", + "optional": true, "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "node": ">=14" } }, - "node_modules/aproba": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", - "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", - "dev": true, - "license": "ISC" - }, - "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", - "integrity": "sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==", - "deprecated": "This package is no longer supported.", + "node_modules/@pkgr/core": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz", + "integrity": "sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==", "dev": true, - "license": "ISC", - "dependencies": { - "delegates": "^1.0.0", - "readable-stream": "^3.6.0" - }, + "license": "MIT", "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" } }, - "node_modules/arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.21.2.tgz", + "integrity": "sha512-fSuPrt0ZO8uXeS+xP3b+yYTCBUd05MoSp2N/MFOgjhhUhMmchXlpTQrTpI8T+YAwAQuK7MafsCOxW7VrPMrJcg==", + "cpu": [ + "arm" + ], "dev": true, - "license": "MIT" - }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.21.2.tgz", + "integrity": "sha512-xGU5ZQmPlsjQS6tzTTGwMsnKUtu0WVbl0hYpTPauvbRAnmIvpInhJtgjj3mcuJpEiuUw4v1s4BimkdfDWlh7gA==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "Python-2.0" + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.21.2.tgz", + "integrity": "sha512-99AhQ3/ZMxU7jw34Sq8brzXqWH/bMnf7ZVhvLk9QU2cOepbQSVTns6qoErJmSiAvU3InRqC2RRZ5ovh1KN0d0Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.21.2.tgz", + "integrity": "sha512-ZbRaUvw2iN/y37x6dY50D8m2BnDbBjlnMPotDi/qITMJ4sIxNY33HArjikDyakhSv0+ybdUxhWxE6kTI4oX26w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.21.2.tgz", + "integrity": "sha512-ztRJJMiE8nnU1YFcdbd9BcH6bGWG1z+jP+IPW2oDUAPxPjo9dverIOyXz76m6IPA6udEL12reYeLojzW2cYL7w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.21.2.tgz", + "integrity": "sha512-flOcGHDZajGKYpLV0JNc0VFH361M7rnV1ee+NTeC/BQQ1/0pllYcFmxpagltANYt8FYf9+kL6RSk80Ziwyhr7w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.21.2.tgz", + "integrity": "sha512-69CF19Kp3TdMopyteO/LJbWufOzqqXzkrv4L2sP8kfMaAQ6iwky7NoXTp7bD6/irKgknDKM0P9E/1l5XxVQAhw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.21.2.tgz", + "integrity": "sha512-48pD/fJkTiHAZTnZwR0VzHrao70/4MlzJrq0ZsILjLW/Ab/1XlVUStYyGt7tdyIiVSlGZbnliqmult/QGA2O2w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.21.2.tgz", + "integrity": "sha512-cZdyuInj0ofc7mAQpKcPR2a2iu4YM4FQfuUzCVA2u4HI95lCwzjoPtdWjdpDKyHxI0UO82bLDoOaLfpZ/wviyQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.21.2.tgz", + "integrity": "sha512-RL56JMT6NwQ0lXIQmMIWr1SW28z4E4pOhRRNqwWZeXpRlykRIlEpSWdsgNWJbYBEWD84eocjSGDu/XxbYeCmwg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.21.2.tgz", + "integrity": "sha512-PMxkrWS9z38bCr3rWvDFVGD6sFeZJw4iQlhrup7ReGmfn7Oukrr/zweLhYX6v2/8J6Cep9IEA/SmjXjCmSbrMQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.21.2.tgz", + "integrity": "sha512-B90tYAUoLhU22olrafY3JQCFLnT3NglazdwkHyxNDYF/zAxJt5fJUB/yBoWFoIQ7SQj+KLe3iL4BhOMa9fzgpw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.21.2.tgz", + "integrity": "sha512-7twFizNXudESmC9oneLGIUmoHiiLppz/Xs5uJQ4ShvE6234K0VB1/aJYU3f/4g7PhssLGKBVCC37uRkkOi8wjg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.21.2.tgz", + "integrity": "sha512-9rRero0E7qTeYf6+rFh3AErTNU1VCQg2mn7CQcI44vNUWM9Ze7MSRS/9RFuSsox+vstRt97+x3sOhEey024FRQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.21.2.tgz", + "integrity": "sha512-5rA4vjlqgrpbFVVHX3qkrCo/fZTj1q0Xxpg+Z7yIo3J2AilW7t2+n6Q8Jrx+4MrYpAnjttTYF8rr7bP46BPzRw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.21.2.tgz", + "integrity": "sha512-6UUxd0+SKomjdzuAcp+HAmxw1FlGBnl1v2yEPSabtx4lBfdXHDVsW7+lQkgz9cNFJGY3AWR7+V8P5BqkD9L9nA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@serialport/binding-mock": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@serialport/binding-mock/-/binding-mock-10.2.2.tgz", + "integrity": "sha512-HAFzGhk9OuFMpuor7aT5G1ChPgn5qSsklTFOTUX72Rl6p0xwcSVsRtG/xaGp6bxpN7fI9D/S8THLBWbBgS6ldw==", + "license": "MIT", + "optional": true, + "dependencies": { + "@serialport/bindings-interface": "^1.2.1", + "debug": "^4.3.3" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@serialport/bindings-cpp": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/@serialport/bindings-cpp/-/bindings-cpp-12.0.1.tgz", + "integrity": "sha512-r2XOwY2dDvbW7dKqSPIk2gzsr6M6Qpe9+/Ngs94fNaNlcTRCV02PfaoDmRgcubpNVVcLATlxSxPTIDw12dbKOg==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@serialport/bindings-interface": "1.2.2", + "@serialport/parser-readline": "11.0.0", + "debug": "4.3.4", + "node-addon-api": "7.0.0", + "node-gyp-build": "4.6.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/@serialport/bindings-cpp/node_modules/@serialport/parser-delimiter": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-delimiter/-/parser-delimiter-11.0.0.tgz", + "integrity": "sha512-aZLJhlRTjSmEwllLG7S4J8s8ctRAS0cbvCpO87smLvl3e4BgzbVgF6Z6zaJd3Aji2uSiYgfedCdNc4L6W+1E2g==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/@serialport/bindings-cpp/node_modules/@serialport/parser-readline": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-readline/-/parser-readline-11.0.0.tgz", + "integrity": "sha512-rRAivhRkT3YO28WjmmG4FQX6L+KMb5/ikhyylRfzWPw0nSXy97+u07peS9CbHqaNvJkMhH1locp2H36aGMOEIA==", + "license": "MIT", + "optional": true, + "dependencies": { + "@serialport/parser-delimiter": "11.0.0" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/@serialport/bindings-cpp/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@serialport/bindings-cpp/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "license": "MIT", + "optional": true + }, + "node_modules/@serialport/bindings-cpp/node_modules/node-addon-api": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.0.0.tgz", + "integrity": "sha512-vgbBJTS4m5/KkE16t5Ly0WW9hz46swAstv0hYYwMtbG7AznRhNyfLRe8HZAiWIpcHzoO7HxhLuBQj9rJ/Ho0ZA==", + "license": "MIT", + "optional": true + }, + "node_modules/@serialport/bindings-cpp/node_modules/node-gyp-build": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.6.0.tgz", + "integrity": "sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ==", + "license": "MIT", + "optional": true, + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, + "node_modules/@serialport/bindings-interface": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@serialport/bindings-interface/-/bindings-interface-1.2.2.tgz", + "integrity": "sha512-CJaUd5bLvtM9c5dmO9rPBHPXTa9R2UwpkJ0wdh9JCYcbrPWsKz+ErvR0hBLeo7NPeiFdjFO4sonRljiw4d2XiA==", + "license": "MIT", + "optional": true, + "engines": { + "node": "^12.22 || ^14.13 || >=16" + } + }, + "node_modules/@serialport/parser-byte-length": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-byte-length/-/parser-byte-length-12.0.0.tgz", + "integrity": "sha512-0ei0txFAj+s6FTiCJFBJ1T2hpKkX8Md0Pu6dqMrYoirjPskDLJRgZGLqoy3/lnU1bkvHpnJO+9oJ3PB9v8rNlg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/@serialport/parser-cctalk": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-cctalk/-/parser-cctalk-12.0.0.tgz", + "integrity": "sha512-0PfLzO9t2X5ufKuBO34DQKLXrCCqS9xz2D0pfuaLNeTkyGUBv426zxoMf3rsMRodDOZNbFblu3Ae84MOQXjnZw==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/@serialport/parser-delimiter": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-delimiter/-/parser-delimiter-12.0.0.tgz", + "integrity": "sha512-gu26tVt5lQoybhorLTPsH2j2LnX3AOP2x/34+DUSTNaUTzu2fBXw+isVjQJpUBFWu6aeQRZw5bJol5X9Gxjblw==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/@serialport/parser-inter-byte-timeout": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-inter-byte-timeout/-/parser-inter-byte-timeout-12.0.0.tgz", + "integrity": "sha512-GnCh8K0NAESfhCuXAt+FfBRz1Cf9CzIgXfp7SdMgXwrtuUnCC/yuRTUFWRvuzhYKoAo1TL0hhUo77SFHUH1T/w==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/@serialport/parser-packet-length": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-packet-length/-/parser-packet-length-12.0.0.tgz", + "integrity": "sha512-p1hiCRqvGHHLCN/8ZiPUY/G0zrxd7gtZs251n+cfNTn+87rwcdUeu9Dps3Aadx30/sOGGFL6brIRGK4l/t7MuQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/@serialport/parser-readline": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-readline/-/parser-readline-12.0.0.tgz", + "integrity": "sha512-O7cywCWC8PiOMvo/gglEBfAkLjp/SENEML46BXDykfKP5mTPM46XMaX1L0waWU6DXJpBgjaL7+yX6VriVPbN4w==", + "license": "MIT", + "optional": true, + "dependencies": { + "@serialport/parser-delimiter": "12.0.0" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/@serialport/parser-ready": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-ready/-/parser-ready-12.0.0.tgz", + "integrity": "sha512-ygDwj3O4SDpZlbrRUraoXIoIqb8sM7aMKryGjYTIF0JRnKeB1ys8+wIp0RFMdFbO62YriUDextHB5Um5cKFSWg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/@serialport/parser-regex": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-regex/-/parser-regex-12.0.0.tgz", + "integrity": "sha512-dCAVh4P/pZrLcPv9NJ2mvPRBg64L5jXuiRxIlyxxdZGH4WubwXVXY/kBTihQmiAMPxbT3yshSX8f2+feqWsxqA==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/@serialport/parser-slip-encoder": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-slip-encoder/-/parser-slip-encoder-12.0.0.tgz", + "integrity": "sha512-0APxDGR9YvJXTRfY+uRGhzOhTpU5akSH183RUcwzN7QXh8/1jwFsFLCu0grmAUfi+fItCkR+Xr1TcNJLR13VNA==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/@serialport/parser-spacepacket": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-spacepacket/-/parser-spacepacket-12.0.0.tgz", + "integrity": "sha512-dozONxhPC/78pntuxpz/NOtVps8qIc/UZzdc/LuPvVsqCoJXiRxOg6ZtCP/W58iibJDKPZPAWPGYeZt9DJxI+Q==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/@serialport/stream": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@serialport/stream/-/stream-12.0.0.tgz", + "integrity": "sha512-9On64rhzuqKdOQyiYLYv2lQOh3TZU/D3+IWCR5gk0alPel2nwpp4YwDEGiUBfrQZEdQ6xww0PWkzqth4wqwX3Q==", + "license": "MIT", + "optional": true, + "dependencies": { + "@serialport/bindings-interface": "1.2.2", + "debug": "4.3.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/@serialport/stream/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@serialport/stream/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "license": "MIT", + "optional": true + }, + "node_modules/@shikijs/core": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-1.17.0.tgz", + "integrity": "sha512-Mkk4Mp4bNnW1kytU8I7S5PK5teNSe0iKlfqxPss4sdwnlcU8a2N62Z3te2gVmZfU9t1HF6L3wyWuM43IvEeEsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/engine-javascript": "1.17.0", + "@shikijs/engine-oniguruma": "1.17.0", + "@shikijs/types": "1.17.0", + "@shikijs/vscode-textmate": "^9.2.2", + "@types/hast": "^3.0.4", + "hast-util-to-html": "^9.0.2" + } + }, + "node_modules/@shikijs/engine-javascript": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-1.17.0.tgz", + "integrity": "sha512-EiBVlxmzJZdC2ypzn8k+vxLngbBNgHLS4RilwrFOABGRc72kUZubbD/6Chrq2RcVtD3yq1GtiiIdFMGd9BTX3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/types": "1.17.0", + "oniguruma-to-js": "0.3.3", + "regex": "4.3.2" + } + }, + "node_modules/@shikijs/engine-oniguruma": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-1.17.0.tgz", + "integrity": "sha512-nsXzJGLQ0fhKmA4Gwt1cF7vC8VuZ1HSDrTRuj48h/qDeX/TzmOlTDXQ3uPtyuhyg/2rbZRzNhN8UFU4fSnQfXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/types": "1.17.0", + "@shikijs/vscode-textmate": "^9.2.2" + } + }, + "node_modules/@shikijs/types": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-1.17.0.tgz", + "integrity": "sha512-Tvu2pA69lbpXB+MmgIaROP1tio8y0uYvKb5Foh3q0TJBTAJuaoa5eDEtS/0LquyveacsiVrYF4uEZILju+7Ybg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/vscode-textmate": "^9.2.2", + "@types/hast": "^3.0.4" + } + }, + "node_modules/@shikijs/vscode-textmate": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-9.2.2.tgz", + "integrity": "sha512-TMp15K+GGYrWlZM8+Lnj9EaHEFmOen0WJBrfa17hF7taDOYthuPPV0GWzfd/9iMij0akS/8Yw2ikquH7uVi/fg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.0" + } + }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", + "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@stoprocent/bluetooth-hci-socket": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@stoprocent/bluetooth-hci-socket/-/bluetooth-hci-socket-1.2.1.tgz", + "integrity": "sha512-s2yiocTcYyRpoOwrOZ5GeR7mGy6/SFXYdwSgK6Dy/uIqQnjCx1ZBhDPYnbnBdbkajRHKI4dDnz/LL0TesNQnEg==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "linux", + "android", + "freebsd", + "win32", + "darwin" + ], + "dependencies": { + "async": "^3.2.4", + "debug": "^4.3.4", + "nan": "^2.17.0", + "node-addon-api": "^4.3.0", + "node-gyp-build": "^4.6.1", + "serialport": "^12.0.0" + }, + "optionalDependencies": { + "usb": "^1.9.2" + } + }, + "node_modules/@stoprocent/noble": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@stoprocent/noble/-/noble-1.14.1.tgz", + "integrity": "sha512-m+ktaIV4EEtBIvAaPZKD8Z48PlCwKgdvP/nYvxkW6xfaJ6XPimxqsA02gXyobEUFUeXrvaS1JBy3qDzWQhXL0Q==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.4", + "napi-thread-safe-callback": "^0.0.6", + "node-addon-api": "^4.3.0", + "node-gyp-build": "^4.6.1" + }, + "engines": { + "node": ">=6" + }, + "optionalDependencies": { + "@stoprocent/bluetooth-hci-socket": "^1.2.1" + } + }, + "node_modules/@stylistic/eslint-plugin": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin/-/eslint-plugin-2.8.0.tgz", + "integrity": "sha512-Ufvk7hP+bf+pD35R/QfunF793XlSRIC7USr3/EdgduK9j13i2JjmsM0LUz3/foS+jDYp2fzyWZA9N44CPur0Ow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/utils": "^8.4.0", + "eslint-visitor-keys": "^4.0.0", + "espree": "^10.1.0", + "estraverse": "^5.3.0", + "picomatch": "^4.0.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "peerDependencies": { + "eslint": ">=8.40.0" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", + "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/aes-js": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@types/aes-js/-/aes-js-3.1.4.tgz", + "integrity": "sha512-v3D66IptpUqh+pHKVNRxY8yvp2ESSZXe0rTzsGdzUhEwag7ljVfgCllkWv2YgiYXDhWFBrEywll4A5JToyTNFA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.6.8", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz", + "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.20.6", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.6.tgz", + "integrity": "sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.20.7" + } + }, + "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, + "license": "MIT" + }, + "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, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/debug": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", + "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/fs-extra": { + "version": "11.0.4", + "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-11.0.4.tgz", + "integrity": "sha512-yTbItCNreRooED33qjunPthRcSjERP1r4MqCZc7wv0u2sUkzTFp45tgUfS5+r7FrZPdmCCNflLhVSP/o+SemsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/jsonfile": "*", + "@types/node": "*" + } + }, + "node_modules/@types/graceful-fs": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", + "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/jest": { + "version": "29.5.12", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.12.tgz", + "integrity": "sha512-eDC8bTvT/QhYdxJAulQikueigY5AsdBRH2yDKW3yveW7svY3+DzN84/2NUgkw10RTiJbWqZrTtoGVdYlvFJdLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "expect": "^29.0.0", + "pretty-format": "^29.0.0" + } + }, + "node_modules/@types/jsonfile": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/@types/jsonfile/-/jsonfile-6.1.4.tgz", + "integrity": "sha512-D5qGUYwjvnNNextdU59/+fI+spnwtTFmyQP0h+PfIOSkNfpU6AOICUOkm4i0OnSk+NyjdPJrxCDro0sJsWlRpQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "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, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/ms": { + "version": "0.7.34", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.34.tgz", + "integrity": "sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.5.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.5.4.tgz", + "integrity": "sha512-FDuKUJQm/ju9fT/SeX/6+gBzoPzlVCzfzmGkwKvRHQVxi4BntVbyIwf6a4Xn62mrvndLiml6z/UBXIdEVjQLXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.19.2" + } + }, + "node_modules/@types/normalize-package-data": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz", + "integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/semver": { + "version": "7.5.8", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz", + "integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/source-map-support": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/@types/source-map-support/-/source-map-support-0.5.10.tgz", + "integrity": "sha512-tgVP2H469x9zq34Z0m/fgPewGhg/MLClalNOiPIzQlXrSS2YrKu/xCdSCKnEDwkFha51VKEKB6A9wW26/ZNwzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "source-map": "^0.6.0" + } + }, + "node_modules/@types/stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/validator": { + "version": "13.12.1", + "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.12.1.tgz", + "integrity": "sha512-w0URwf7BQb0rD/EuiG12KP0bailHKHP5YVviJG9zw3ykAokL0TuxU2TUqMB7EwZ59bDHYdeTIvjI5m0S7qHfOA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/yargs": { + "version": "17.0.33", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", + "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.5.0.tgz", + "integrity": "sha512-lHS5hvz33iUFQKuPFGheAB84LwcJ60G8vKnEhnfcK1l8kGVLro2SFYW6K0/tj8FUhRJ0VHyg1oAfg50QGbPPHw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.5.0", + "@typescript-eslint/type-utils": "8.5.0", + "@typescript-eslint/utils": "8.5.0", + "@typescript-eslint/visitor-keys": "8.5.0", + "graphemer": "^1.4.0", + "ignore": "^5.3.1", + "natural-compare": "^1.4.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", + "eslint": "^8.57.0 || ^9.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.5.0.tgz", + "integrity": "sha512-gF77eNv0Xz2UJg/NbpWJ0kqAm35UMsvZf1GHj8D9MRFTj/V3tAciIWXfmPLsAAF/vUlpWPvUDyH1jjsr0cMVWw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/scope-manager": "8.5.0", + "@typescript-eslint/types": "8.5.0", + "@typescript-eslint/typescript-estree": "8.5.0", + "@typescript-eslint/visitor-keys": "8.5.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.5.0.tgz", + "integrity": "sha512-06JOQ9Qgj33yvBEx6tpC8ecP9o860rsR22hWMEd12WcTRrfaFgHr2RB/CA/B+7BMhHkXT4chg2MyboGdFGawYg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.5.0", + "@typescript-eslint/visitor-keys": "8.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.5.0.tgz", + "integrity": "sha512-N1K8Ix+lUM+cIDhL2uekVn/ZD7TZW+9/rwz8DclQpcQ9rk4sIL5CAlBC0CugWKREmDjBzI/kQqU4wkg46jWLYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/typescript-estree": "8.5.0", + "@typescript-eslint/utils": "8.5.0", + "debug": "^4.3.4", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.5.0.tgz", + "integrity": "sha512-qjkormnQS5wF9pjSi6q60bKUHH44j2APxfh9TQRXK8wbYVeDYYdYJGIROL87LGZZ2gz3Rbmjc736qyL8deVtdw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.5.0.tgz", + "integrity": "sha512-vEG2Sf9P8BPQ+d0pxdfndw3xIXaoSjliG0/Ejk7UggByZPKXmJmw3GW5jV2gHNQNawBUyfahoSiCFVov0Ruf7Q==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/types": "8.5.0", + "@typescript-eslint/visitor-keys": "8.5.0", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.5.0.tgz", + "integrity": "sha512-6yyGYVL0e+VzGYp60wvkBHiqDWOpT63pdMV2CVG4LVDd5uR6q1qQN/7LafBZtAtNIn/mqXjsSeS5ggv/P0iECw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@typescript-eslint/scope-manager": "8.5.0", + "@typescript-eslint/types": "8.5.0", + "@typescript-eslint/typescript-estree": "8.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.5.0.tgz", + "integrity": "sha512-yTPqMnbAZJNy2Xq2XU8AdtOW9tJIr+UQb64aXB9f3B1498Zx9JorVgFJcZpEc9UBuCCrdzKID2RGAMkYcDtZOw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.5.0", + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/@vitest/coverage-v8": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-2.0.5.tgz", + "integrity": "sha512-qeFcySCg5FLO2bHHSa0tAZAOnAUbp4L6/A5JDuj9+bt53JREl8hpLjLHEWF0e/gWc8INVpJaqA7+Ene2rclpZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.3.0", + "@bcoe/v8-coverage": "^0.2.3", + "debug": "^4.3.5", + "istanbul-lib-coverage": "^3.2.2", + "istanbul-lib-report": "^3.0.1", + "istanbul-lib-source-maps": "^5.0.6", + "istanbul-reports": "^3.1.7", + "magic-string": "^0.30.10", + "magicast": "^0.3.4", + "std-env": "^3.7.0", + "test-exclude": "^7.0.1", + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "vitest": "2.0.5" + } + }, + "node_modules/@vitest/eslint-plugin": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@vitest/eslint-plugin/-/eslint-plugin-1.1.0.tgz", + "integrity": "sha512-Ur80Y27Wbw8gFHJ3cv6vypcjXmrx6QHfw+q435h6Q2L+tf+h4Xf5pJTCL4YU/Jps9EVeggQxS85OcUZU7sdXRw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@typescript-eslint/utils": ">= 8.0", + "eslint": ">= 8.57.0", + "typescript": ">= 5.0.0", + "vitest": "*" + }, + "peerDependenciesMeta": { + "@typescript-eslint/utils": { + "optional": true + }, + "typescript": { + "optional": true + }, + "vitest": { + "optional": true + } + } + }, + "node_modules/@vitest/expect": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.0.5.tgz", + "integrity": "sha512-yHZtwuP7JZivj65Gxoi8upUN2OzHTi3zVfjwdpu2WrvCZPLwsJ2Ey5ILIPccoW23dd/zQBlJ4/dhi7DWNyXCpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "2.0.5", + "@vitest/utils": "2.0.5", + "chai": "^5.1.1", + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/pretty-format": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.0.5.tgz", + "integrity": "sha512-h8k+1oWHfwTkyTkb9egzwNMfJAEx4veaPSnMeKbVSjp4euqGSbQlm5+6VHwTr7u4FJslVVsUG5nopCaAYdOmSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.0.5.tgz", + "integrity": "sha512-TfRfZa6Bkk9ky4tW0z20WKXFEwwvWhRY+84CnSEtq4+3ZvDlJyY32oNTJtM7AW9ihW90tX/1Q78cb6FjoAs+ig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "2.0.5", + "pathe": "^1.1.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.0.5.tgz", + "integrity": "sha512-SgCPUeDFLaM0mIUHfaArq8fD2WbaXG/zVXjRupthYfYGzc8ztbFbu6dUNOblBG7XLMR1kEhS/DNnfCZ2IhdDew==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "2.0.5", + "magic-string": "^0.30.10", + "pathe": "^1.1.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.0.5.tgz", + "integrity": "sha512-c/jdthAhvJdpfVuaexSrnawxZz6pywlTPe84LUB2m/4t3rl2fTo9NFGBG4oWgaD+FTgDDV8hJ/nibT7IfH3JfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^3.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.0.5.tgz", + "integrity": "sha512-d8HKbqIcya+GR67mkZbrzhS5kKhtp8dQLcmRZLGTscGVg7yImT82cIrhtn2L8+VujWcy6KZweApgNmPsTAO/UQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "2.0.5", + "estree-walker": "^3.0.3", + "loupe": "^3.1.1", + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils/node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.4.tgz", + "integrity": "sha512-oNwn+BAt3n9dK9uAYvI+XGlutwuTq/wfj4xCBaZCqwwVIGtD7D6ViihEbyYZrDHIHTDE3Q6oL3/hqmAyFEy9DQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/parser": "^7.25.3", + "@vue/shared": "3.5.4", + "entities": "^4.5.0", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.0" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.4.tgz", + "integrity": "sha512-yP9RRs4BDLOLfldn6ah+AGCNovGjMbL9uHvhDHf5wan4dAHLnFGOkqtfE7PPe4HTXIqE7l/NILdYw53bo1C8jw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@vue/compiler-core": "3.5.4", + "@vue/shared": "3.5.4" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.4.tgz", + "integrity": "sha512-P+yiPhL+NYH7m0ZgCq7AQR2q7OIE+mpAEgtkqEeH9oHSdIRvUO+4X6MPvblJIWcoe4YC5a2Gdf/RsoyP8FFiPQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/parser": "^7.25.3", + "@vue/compiler-core": "3.5.4", + "@vue/compiler-dom": "3.5.4", + "@vue/compiler-ssr": "3.5.4", + "@vue/shared": "3.5.4", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.11", + "postcss": "^8.4.44", + "source-map-js": "^1.2.0" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.4.tgz", + "integrity": "sha512-acESdTXsxPnYr2C4Blv0ggx5zIFMgOzZmYU2UgvIff9POdRGbRNBHRyzHAnizcItvpgerSKQbllUc9USp3V7eg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@vue/compiler-dom": "3.5.4", + "@vue/shared": "3.5.4" + } + }, + "node_modules/@vue/shared": { + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.4.tgz", + "integrity": "sha512-L2MCDD8l7yC62Te5UUyPVpmexhL9ipVnYRw9CsWfm/BGRL5FwDX4a25bcJ/OJSD3+Hx+k/a8LDKcG2AFdJV3BA==", + "dev": true, + "license": "MIT", + "peer": 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, + "license": "MIT", + "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, + "license": "MIT" + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.12.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", + "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/agent-base": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-formats/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats/node_modules/fast-uri": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.1.tgz", + "integrity": "sha512-MWipKbbYiYI0UC7cl8m/i/IWTqfC8YXsqjzybjddLsFjStroQzsHXkc73JutMvBiXmOvapk+axIl79ig5t55Bw==", + "dev": true, + "license": "MIT" + }, + "node_modules/ajv-formats/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/anymatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/are-docs-informative": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/are-docs-informative/-/are-docs-informative-0.0.2.tgz", + "integrity": "sha512-ixiS0nLNNG5jNQzgZJNoUpBKdo9yTYZMGJ+QgT2jmjR7G7+QHRCc4v6LQ3NgE7EBJq+o0ams3waJwkrlBom8Ig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true, + "license": "MIT" + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz", + "integrity": "sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.5", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-flatten": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-3.0.0.tgz", + "integrity": "sha512-zPMVc3ZYlGLNk4mpK1NzP2wg0ml9t7fUgDsayR5Y5rSzxQilzR9FGu/EH2jQOcKSAeAfWeylyW8juy3OkWRvNA==", + "license": "MIT" + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "license": "MIT", + "optional": true + }, + "node_modules/async-mqtt": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/async-mqtt/-/async-mqtt-2.6.3.tgz", + "integrity": "sha512-mFGTtlEpOugOoLOf9H5AJyJaZUNtOVXLGGOnPaPZDPQex6W6iIOgtV+fAgam0GQbgnLfgX+Wn/QzS6d+PYfFAQ==", + "license": "MIT", + "dependencies": { + "mqtt": "^4.3.7" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/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, + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/avvio": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/avvio/-/avvio-8.4.0.tgz", + "integrity": "sha512-CDSwaxINFy59iNwhYnkvALBwZiTydGkOecZyPkqBpABYR1KqGEsET0VOOYDwtleZSUIdeY36DC2bSZ24CO1igA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@fastify/error": "^3.3.0", + "fastq": "^1.17.1" + } + }, + "node_modules/axios": { + "version": "1.7.7", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz", + "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/babel-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", + "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/transform": "^29.7.0", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.6.3", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.8.0" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-istanbul/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/babel-plugin-istanbul/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/babel-plugin-istanbul/node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-istanbul/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/babel-plugin-istanbul/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/babel-plugin-istanbul/node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-jest-hoist": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", + "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/babel-preset-current-node-syntax": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.1.0.tgz", + "integrity": "sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-import-attributes": "^7.24.7", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/babel-preset-jest": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", + "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", + "dev": true, + "license": "MIT", + "dependencies": { + "babel-plugin-jest-hoist": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/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, + "license": "MIT", + "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, + "license": "MIT" + }, + "node_modules/bignumber.js": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.2.tgz", + "integrity": "sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/bonjour-hap": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/bonjour-hap/-/bonjour-hap-3.8.0.tgz", + "integrity": "sha512-l/Ptvrt/pjN2pCgiVyyA0EkE0uVoXXYZ4DW4xhL4kDVBaw0w54/3Jhdhzn5EyT1Z8YhNXiNhSX0uW6xz2zSxqQ==", + "license": "MIT", + "dependencies": { + "array-flatten": "^3.0.0", + "deep-equal": "^2.2.3", + "multicast-dns": "^7.2.5", + "multicast-dns-service-types": "^1.1.0" + } + }, + "node_modules/bonjour-service": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.2.1.tgz", + "integrity": "sha512-oSzCS2zV14bh2kji6vNe7vrpJYCHGvcZnlffFQ1MEoX/WOeQ/teD8SYWKR942OI3INjq8OMNJlbPK5LLLUxFDw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "multicast-dns": "^7.2.5" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "dev": true, + "license": "ISC" + }, + "node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.3.tgz", + "integrity": "sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "caniuse-lite": "^1.0.30001646", + "electron-to-chromium": "^1.5.4", + "node-releases": "^2.0.18", + "update-browserslist-db": "^1.1.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "license": "MIT" + }, + "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, + "license": "MIT" + }, + "node_modules/builtin-modules": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", + "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/call-bind": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001660", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001660.tgz", + "integrity": "sha512-GacvNTTuATm26qC74pt+ad1fW15mlQ/zuTzzY1ZoIzECTP8HURDfF43kNxPgf7H1jmelCBQTTbBNxdSXOA7Bqg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/chai": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.1.1.tgz", + "integrity": "sha512-pT1ZgP8rPNqUgieVaEY+ryQr6Q4HXNg8Ei9UnLUrjN4IA7dvQC5JB+/kxVcPNDHyBcc/26CXPkbNzq3qwrOEKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/check-error": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", + "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/ci-info": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.0.0.tgz", + "integrity": "sha512-TdHqgGf9odd8SXNuxtUBVx8Nv+qZOejE6qyqiy5NtbYYQOeFa6zmHkxlPzmaLxWWHsU6nJmB7AETdVPi+2NBUg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cjs-module-lexer": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.1.tgz", + "integrity": "sha512-cuSVIHi9/9E/+821Qjdvngor+xpnlwnuwIyZOaLmHBVdXL+gP+I6QQB9VkO7RI77YIcTV+S1W9AreJ5eN63JBA==", + "dev": true, + "license": "MIT" + }, + "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, + "license": "MIT" + }, + "node_modules/class-validator": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.14.1.tgz", + "integrity": "sha512-2VEG9JICxIqTpoK1eMzZqaV+u/EiwEJkMGzTrZf6sU/fwsnOITVgYJ8yojSy6CaXtO9V0Cc6ZQZ8h8m4UBuLwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/validator": "^13.11.8", + "libphonenumber-js": "^1.10.53", + "validator": "^13.9.0" + } + }, + "node_modules/clean-regexp": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/clean-regexp/-/clean-regexp-1.0.0.tgz", + "integrity": "sha512-GfisEZEJvzKrmGWkvfhgzcz/BllN1USeqD2V6tg14OAOgaCD2Z/PUEuxnAZ/nPvmaHRG7a8y77p1T/IRQ4D1Hw==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^1.0.5" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/clean-regexp/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/cliui/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/collect-v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", + "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/comment-parser": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.4.1.tgz", + "integrity": "sha512-buhp5kePrmda3vhc5B9t7pUQXAb2Tnd0qgpkIhPhkHXxJpiPJ11H0ZEU0oBpJ2QztSbzG/ZxMj/CHsYJqRHmyg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/commist": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/commist/-/commist-1.1.0.tgz", + "integrity": "sha512-rraC8NXWOEjhADbZe9QBNzLAN5Q3fsTPQtBV+fEVj6xKIgDgNiEVE6ZNfHpZOqfQ21YUzfVNUXLOEZquYvQPPg==", + "license": "MIT", + "dependencies": { + "leven": "^2.1.0", + "minimist": "^1.1.0" + } + }, + "node_modules/commist/node_modules/leven": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-2.1.0.tgz", + "integrity": "sha512-nvVPLpIHUxCUoRLrFqTgSxXJ614d8AgQoWl7zPe/2VadE8+1dpU3LBhowRuBAcuwruWtOdD8oYC9jDNJjXDPyA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "license": "MIT" + }, + "node_modules/concat-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", + "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==", + "engines": [ + "node >= 6.0" + ], + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.0.2", + "typedarray": "^0.0.6" + } + }, + "node_modules/confbox": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.7.tgz", + "integrity": "sha512-uJcB/FKZtBMCJpK8MQji6bJHgu1tixKPxRLeGkNzBoOZzpnZUJm0jm2/sBDWcuBx1dYgxV4JU+g5hmNxCyAmdA==", + "dev": true, + "license": "MIT" + }, + "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, + "license": "MIT" + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/core-js-compat": { + "version": "3.38.1", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.38.1.tgz", + "integrity": "sha512-JRH6gfXxGmrzF3tZ57lFx97YARxCXPaMzPo6jELZhv88pBH5VXpQ+y0znKGlFnzuaihqhLbefxSJxWJMPtfDzw==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserslist": "^4.23.3" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } }, - "node_modules/array-buffer-byte-length": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz", - "integrity": "sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==", + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/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, + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/create-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", + "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "prompts": "^2.0.1" + }, + "bin": { + "create-jest": "bin/create-jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "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, + "license": "MIT" + }, + "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, + "license": "MIT", + "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", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/dayjs": { + "version": "1.11.13", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", + "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decamelize": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-5.0.1.tgz", + "integrity": "sha512-VfxadyCECXgQlkoEAjeghAr5gY3Hf+IKjKb+X8tGVDtveCjN+USwprd2q3QXBR9T1+x2DG0XZF5/w+7HAtSaXA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decode-named-character-reference": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.0.2.tgz", + "integrity": "sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/dedent": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.3.tgz", + "integrity": "sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/deep-equal": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.3.tgz", + "integrity": "sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA==", + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.0", + "call-bind": "^1.0.5", + "es-get-iterator": "^1.1.3", + "get-intrinsic": "^1.2.2", + "is-arguments": "^1.1.1", + "is-array-buffer": "^3.0.2", + "is-date-object": "^1.0.5", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "isarray": "^2.0.5", + "object-is": "^1.1.5", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.5.1", + "side-channel": "^1.0.4", + "which-boxed-primitive": "^1.0.2", + "which-collection": "^1.0.1", + "which-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/defaults": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "clone": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/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, + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", "license": "MIT", "dependencies": { - "call-bind": "^1.0.5", - "is-array-buffer": "^3.0.4" + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" }, "engines": { "node": ">= 0.4" @@ -2799,2324 +5897,2724 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/array-flatten": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-3.0.0.tgz", - "integrity": "sha512-zPMVc3ZYlGLNk4mpK1NzP2wg0ml9t7fUgDsayR5Y5rSzxQilzR9FGu/EH2jQOcKSAeAfWeylyW8juy3OkWRvNA==", - "license": "MIT" + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } }, - "node_modules/array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", "dev": true, "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/detect-libc": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", + "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", + "dev": true, + "license": "Apache-2.0", "engines": { "node": ">=8" } }, - "node_modules/async": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", - "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==", + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true, "license": "MIT", - "optional": true + "engines": { + "node": ">=8" + } }, - "node_modules/async-mqtt": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/async-mqtt/-/async-mqtt-2.6.3.tgz", - "integrity": "sha512-mFGTtlEpOugOoLOf9H5AJyJaZUNtOVXLGGOnPaPZDPQex6W6iIOgtV+fAgam0GQbgnLfgX+Wn/QzS6d+PYfFAQ==", + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "dev": true, "license": "MIT", "dependencies": { - "mqtt": "^4.3.7" + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", "dev": true, - "license": "MIT" + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } }, - "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==", + "node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", "dev": true, "license": "MIT", "engines": { - "node": ">=8.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/available-typed-arrays": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", - "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "node_modules/dns-packet": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz", + "integrity": "sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==", "license": "MIT", "dependencies": { - "possible-typed-array-names": "^1.0.0" + "@leichtgewicht/ip-codec": "^2.0.1" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=6" } }, - "node_modules/avvio": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/avvio/-/avvio-8.3.2.tgz", - "integrity": "sha512-st8e519GWHa/azv8S87mcJvZs4WsgTBjOw/Ih1CP6u+8SZvcOeAYNG6JbsIrAUUJJ7JfmrnOkR8ipDS+u9SIRQ==", + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "@fastify/error": "^3.3.0", - "fastq": "^1.17.1" + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" } }, - "node_modules/axios": { - "version": "1.6.8", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.8.tgz", - "integrity": "sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ==", + "node_modules/duplexer": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", + "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", "dev": true, - "license": "MIT", + "license": "MIT" + }, + "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, + "license": "BSD-3-Clause", "dependencies": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.0", - "proxy-from-env": "^1.1.0" + "readable-stream": "^2.0.2" } }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "node_modules/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, "license": "MIT" }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], + "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, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/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, "license": "MIT" }, - "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==", + "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, "license": "MIT", - "engines": { - "node": "^4.5.0 || >= 5.9" + "dependencies": { + "safe-buffer": "~5.1.0" } }, - "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==", + "node_modules/duplexify": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.3.tgz", + "integrity": "sha512-M3BmBhwJRZsSx38lZyhE53Csddgzl5R7xGJNk7CVddZD6CcmwMCH8J+7AprIrQKH7TonKxaCjcv27Qmf+sQ+oA==", + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.4.1", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1", + "stream-shift": "^1.0.2" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", "dev": true, "license": "MIT" }, - "node_modules/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, - "license": "Unlicense", - "engines": { - "node": ">=0.6" + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" } }, - "node_modules/bignumber.js": { - "version": "9.1.2", - "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.2.tgz", - "integrity": "sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==", + "node_modules/electron-to-chromium": { + "version": "1.5.19", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.19.tgz", + "integrity": "sha512-kpLJJi3zxTR1U828P+LIUDZ5ohixyo68/IcYOHLqnbTPr/wdgn4i1ECvmALN9E16JPA6cvCG5UG79gVwVdEK5w==", + "dev": true, + "license": "ISC" + }, + "node_modules/emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "dev": true, "license": "MIT", "engines": { - "node": "*" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" } }, - "node_modules/binary": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/binary/-/binary-0.3.0.tgz", - "integrity": "sha512-D4H1y5KYwpJgK8wk1Cue5LLPgmwHKYSChkbspQg5JtVuR5ulGckxfR62H3AE9UDkdMC8yyXlqYihuz3Aqg2XZg==", + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", "dev": true, + "license": "MIT" + }, + "node_modules/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==", "license": "MIT", "dependencies": { - "buffers": "~0.1.1", - "chainsaw": "~0.1.0" - }, - "engines": { - "node": "*" + "once": "^1.4.0" } }, - "node_modules/binary-extensions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", - "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "node_modules/engine.io": { + "version": "6.5.5", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.5.5.tgz", + "integrity": "sha512-C5Pn8Wk+1vKBoHghJODM63yk8MvrO9EWZUfkAt5HAqIgPE4/8FF0PEGHXtEd40l223+cE5ABWuPzm38PHFXfMA==", "dev": true, "license": "MIT", - "engines": { - "node": ">=8" + "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.17.1" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">=10.2.0" } }, - "node_modules/bl": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", - "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "node_modules/engine.io-parser": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", + "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", + "dev": true, "license": "MIT", - "dependencies": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" + "engines": { + "node": ">=10.0.0" } }, - "node_modules/bluebird": { - "version": "3.4.7", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.4.7.tgz", - "integrity": "sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA==", + "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, - "license": "MIT" - }, - "node_modules/bonjour-hap": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/bonjour-hap/-/bonjour-hap-3.8.0.tgz", - "integrity": "sha512-l/Ptvrt/pjN2pCgiVyyA0EkE0uVoXXYZ4DW4xhL4kDVBaw0w54/3Jhdhzn5EyT1Z8YhNXiNhSX0uW6xz2zSxqQ==", "license": "MIT", - "dependencies": { - "array-flatten": "^3.0.0", - "deep-equal": "^2.2.3", - "multicast-dns": "^7.2.5", - "multicast-dns-service-types": "^1.1.0" + "engines": { + "node": ">= 0.6" } }, - "node_modules/bonjour-service": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.2.1.tgz", - "integrity": "sha512-oSzCS2zV14bh2kji6vNe7vrpJYCHGvcZnlffFQ1MEoX/WOeQ/teD8SYWKR942OI3INjq8OMNJlbPK5LLLUxFDw==", + "node_modules/engine.io/node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", "dev": true, "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "multicast-dns": "^7.2.5" + "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/boxen": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/boxen/-/boxen-7.1.1.tgz", - "integrity": "sha512-2hCgjEmP8YLWQ130n2FerGv7rYpfBmnmp9Uy2Le1vge6X3gZIfSmEzP5QTDElFxcvVcXlEn8Aq6MU/PZygIOog==", + "node_modules/enhanced-resolve": { + "version": "5.17.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz", + "integrity": "sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==", "dev": true, "license": "MIT", "dependencies": { - "ansi-align": "^3.0.1", - "camelcase": "^7.0.1", - "chalk": "^5.2.0", - "cli-boxes": "^3.0.0", - "string-width": "^5.1.2", - "type-fest": "^2.13.0", - "widest-line": "^4.0.1", - "wrap-ansi": "^8.1.0" + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" }, "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=10.13.0" } }, - "node_modules/boxen/node_modules/chalk": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", - "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", "dev": true, - "license": "MIT", + "license": "BSD-2-Clause", "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" + "node": ">=0.12" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "url": "https://github.com/fb55/entities?sponsor=1" } }, - "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==", + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "is-arrayish": "^0.2.1" } }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", "license": "MIT", "dependencies": { - "fill-range": "^7.1.1" + "get-intrinsic": "^1.2.4" }, "engines": { - "node": ">=8" + "node": ">= 0.4" } }, - "node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "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/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", "license": "MIT", - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" + "engines": { + "node": ">= 0.4" } }, - "node_modules/buffer-equal-constant-time": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", - "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", - "license": "BSD-3-Clause" + "node_modules/es-get-iterator": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz", + "integrity": "sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "has-symbols": "^1.0.3", + "is-arguments": "^1.1.1", + "is-map": "^2.0.2", + "is-set": "^2.0.2", + "is-string": "^1.0.7", + "isarray": "^2.0.5", + "stop-iteration-iterator": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "node_modules/es-module-lexer": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.4.tgz", + "integrity": "sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==", + "dev": true, "license": "MIT" }, - "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==", + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", "dev": true, + "hasInstallScript": true, "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, "engines": { - "node": ">=0.10" + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" } }, - "node_modules/buffer-shims": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/buffer-shims/-/buffer-shims-1.0.0.tgz", - "integrity": "sha512-Zy8ZXMyxIT6RMTeY7OP/bDndfj6bwCan7SS98CEndS6deHwWPpseeHlwarNcBim+etXnF9HBc1non5JgDaJU1g==", + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", "dev": true, "license": "MIT" }, - "node_modules/buffers": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/buffers/-/buffers-0.1.1.tgz", - "integrity": "sha512-9q/rDEGSb/Qsvv2qvzIzdluL5k7AaJOTrw23z9reQthrbF7is4CtlT0DXyO1oei2DCp4uojjzQ7igaSHp1kAEQ==", + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true, + "license": "MIT", "engines": { - "node": ">=0.2.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/cacache": { - "version": "17.1.4", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-17.1.4.tgz", - "integrity": "sha512-/aJwG2l3ZMJ1xNAnqbMpA40of9dj/pIH3QfiuQSqjfPJF747VR0J/bHn+/KdNnHKc6XQcWt/AfRSBft82W1d2A==", + "node_modules/eslint": { + "version": "9.10.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.10.0.tgz", + "integrity": "sha512-Y4D0IgtBZfOcOUAIQTSXBKoNGfY0REGqHJG6+Q81vNippW5YlKjHFj4soMxamKK1NXHUWuBZTLdU3Km+L/pcHw==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "@npmcli/fs": "^3.1.0", - "fs-minipass": "^3.0.0", - "glob": "^10.2.2", - "lru-cache": "^7.7.1", - "minipass": "^7.0.3", - "minipass-collect": "^1.0.2", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "p-map": "^4.0.0", - "ssri": "^10.0.0", - "tar": "^6.1.11", - "unique-filename": "^3.0.0" + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.11.0", + "@eslint/config-array": "^0.18.0", + "@eslint/eslintrc": "^3.1.0", + "@eslint/js": "9.10.0", + "@eslint/plugin-kit": "^0.1.0", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.3.0", + "@nodelib/fs.walk": "^1.2.8", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.0.2", + "eslint-visitor-keys": "^4.0.0", + "espree": "^10.1.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } } }, - "node_modules/cacache/node_modules/lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "node_modules/eslint-compat-utils": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/eslint-compat-utils/-/eslint-compat-utils-0.5.1.tgz", + "integrity": "sha512-3z3vFexKIEnjHE3zCMRo6fn/e44U7T1khUjg+Hp0ZQMCigh28rALD0nPFBcGZuiLC5rLZa2ubQHDRln09JfU2Q==", "dev": true, - "license": "ISC", + "license": "MIT", + "dependencies": { + "semver": "^7.5.4" + }, "engines": { "node": ">=12" + }, + "peerDependencies": { + "eslint": ">=6.0.0" } }, - "node_modules/cacheable-lookup": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-7.0.0.tgz", - "integrity": "sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w==", + "node_modules/eslint-config-flat-gitignore": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/eslint-config-flat-gitignore/-/eslint-config-flat-gitignore-0.3.0.tgz", + "integrity": "sha512-0Ndxo4qGhcewjTzw52TK06Mc00aDtHNTdeeW2JfONgDcLkRO/n/BteMRzNVpLQYxdCC/dFEilfM9fjjpGIJ9Og==", "dev": true, "license": "MIT", - "engines": { - "node": ">=14.16" + "dependencies": { + "@eslint/compat": "^1.1.1", + "find-up-simple": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "eslint": "^9.5.0" } }, - "node_modules/cacheable-request": { - "version": "10.2.14", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-10.2.14.tgz", - "integrity": "sha512-zkDT5WAF4hSSoUgyfg5tFIxz8XQK+25W/TLVojJTMKBaxevLBBtLxgqguAuVQB8PVW79FVjHcU+GJ9tVbDZ9mQ==", + "node_modules/eslint-flat-config-utils": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/eslint-flat-config-utils/-/eslint-flat-config-utils-0.4.0.tgz", + "integrity": "sha512-kfd5kQZC+BMO0YwTol6zxjKX1zAsk8JfSAopbKjKqmENTJcew+yBejuvccAg37cvOrN0Mh+DVbeyznuNWEjt4A==", "dev": true, "license": "MIT", "dependencies": { - "@types/http-cache-semantics": "^4.0.2", - "get-stream": "^6.0.1", - "http-cache-semantics": "^4.1.1", - "keyv": "^4.5.3", - "mimic-response": "^4.0.0", - "normalize-url": "^8.0.0", - "responselike": "^3.0.0" + "pathe": "^1.1.2" }, - "engines": { - "node": ">=14.16" + "funding": { + "url": "https://github.com/sponsors/antfu" } }, - "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==", + "node_modules/eslint-formatting-reporter": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/eslint-formatting-reporter/-/eslint-formatting-reporter-0.0.0.tgz", + "integrity": "sha512-k9RdyTqxqN/wNYVaTk/ds5B5rA8lgoAmvceYN7bcZMBwU7TuXx5ntewJv81eF3pIL/CiJE+pJZm36llG8yhyyw==", "dev": true, "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "dependencies": { + "prettier-linter-helpers": "^1.0.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "eslint": ">=8.40.0" } }, - "node_modules/call-bind": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", - "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "node_modules/eslint-import-resolver-node": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", + "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", + "dev": true, "license": "MIT", "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "debug": "^3.2.7", + "is-core-module": "^2.13.0", + "resolve": "^1.22.4" } }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "dev": true, "license": "MIT", - "engines": { - "node": ">=6" + "dependencies": { + "ms": "^2.1.1" } }, - "node_modules/camelcase": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-7.0.1.tgz", - "integrity": "sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==", + "node_modules/eslint-merge-processors": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/eslint-merge-processors/-/eslint-merge-processors-0.1.0.tgz", + "integrity": "sha512-IvRXXtEajLeyssvW4wJcZ2etxkR9mUf4zpNwgI+m/Uac9RfXHskuJefkHUcawVzePnd6xp24enp5jfgdHzjRdQ==", "dev": true, "license": "MIT", - "engines": { - "node": ">=14.16" - }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "eslint": "*" } }, - "node_modules/chainsaw": { + "node_modules/eslint-parser-plain": { "version": "0.1.0", - "resolved": "https://registry.npmjs.org/chainsaw/-/chainsaw-0.1.0.tgz", - "integrity": "sha512-75kWfWt6MEKNC8xYXIdRpDehRYY/tNSgwKaJq+dbbDcxORuVrrQ+SEHoWsniVn9XPYfP4gmdWIeDk/4YNp1rNQ==", + "resolved": "https://registry.npmjs.org/eslint-parser-plain/-/eslint-parser-plain-0.1.0.tgz", + "integrity": "sha512-oOeA6FWU0UJT/Rxc3XF5Cq0nbIZbylm7j8+plqq0CZoE6m4u32OXJrR+9iy4srGMmF6v6pmgvP1zPxSRIGh3sg==", "dev": true, - "license": "MIT/X11", - "dependencies": { - "traverse": ">=0.3.0 <0.4" - }, - "engines": { - "node": "*" - } + "license": "MIT" }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/eslint-plugin-antfu": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-antfu/-/eslint-plugin-antfu-2.6.0.tgz", + "integrity": "sha512-4dz0VgWGpZ6jUSEUPSI6OGFqBc+P8c7zFFXht5t+YwzIvBsruqVX7Hjl3I8KNNEyJmA4fL3+GIc+EWU1woTp1A==", "dev": true, "license": "MIT", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" + "@antfu/utils": "^0.7.10" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "eslint": "*" } }, - "node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "node_modules/eslint-plugin-command": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/eslint-plugin-command/-/eslint-plugin-command-0.2.4.tgz", + "integrity": "sha512-IbZnQY21pOanbcCh/bAWWl+1BynV2HuDE75URMmk/28Tdn+PM7CoKeibXtPGrL7KQdIEHMgUEnRwwI8qmggVMA==", "dev": true, "license": "MIT", "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" + "@es-joy/jsdoccomment": "^0.48.0" }, "funding": { - "url": "https://paulmillr.com/funding/" + "url": "https://github.com/sponsors/antfu" }, - "optionalDependencies": { - "fsevents": "~2.3.2" + "peerDependencies": { + "eslint": "*" } }, - "node_modules/chokidar/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "node_modules/eslint-plugin-es-x": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-es-x/-/eslint-plugin-es-x-7.8.0.tgz", + "integrity": "sha512-7Ds8+wAAoV3T+LAKeu39Y5BzXCrGKrcISfgKEqTS4BDN8SFEDQd0S43jiQ8vIa3wUKD07qitZdfzlenSi8/0qQ==", "dev": true, - "license": "ISC", + "funding": [ + "https://github.com/sponsors/ota-meshi", + "https://opencollective.com/eslint" + ], + "license": "MIT", "dependencies": { - "is-glob": "^4.0.1" + "@eslint-community/eslint-utils": "^4.1.2", + "@eslint-community/regexpp": "^4.11.0", + "eslint-compat-utils": "^0.5.1" }, "engines": { - "node": ">= 6" + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": ">=8" } }, - "node_modules/chownr": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", - "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "node_modules/eslint-plugin-format": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-format/-/eslint-plugin-format-0.1.2.tgz", + "integrity": "sha512-ZrcO3aiumgJ6ENAv65IWkPjtW77ML/5mp0YrRK0jdvvaZJb+4kKWbaQTMr/XbJo6CtELRmCApAziEKh7L2NbdQ==", "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" + "license": "MIT", + "dependencies": { + "@dprint/formatter": "^0.3.0", + "@dprint/markdown": "^0.17.1", + "@dprint/toml": "^0.6.2", + "eslint-formatting-reporter": "^0.0.0", + "eslint-parser-plain": "^0.1.0", + "prettier": "^3.3.2", + "synckit": "^0.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "eslint": "^8.40.0 || ^9.0.0" } }, - "node_modules/ci-info": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", - "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "node_modules/eslint-plugin-import-x": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-import-x/-/eslint-plugin-import-x-4.2.1.tgz", + "integrity": "sha512-WWi2GedccIJa0zXxx3WDnTgouGQTtdYK1nhXMwywbqqAgB0Ov+p1pYBsWh3VaB0bvBOwLse6OfVII7jZD9xo5Q==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } - ], "license": "MIT", + "dependencies": { + "@typescript-eslint/utils": "^8.1.0", + "debug": "^4.3.4", + "doctrine": "^3.0.0", + "eslint-import-resolver-node": "^0.3.9", + "get-tsconfig": "^4.7.3", + "is-glob": "^4.0.3", + "minimatch": "^9.0.3", + "semver": "^7.6.3", + "stable-hash": "^0.0.4", + "tslib": "^2.6.3" + }, "engines": { - "node": ">=8" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0" } }, - "node_modules/class-transformer": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz", - "integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==", + "node_modules/eslint-plugin-jsdoc": { + "version": "50.2.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-50.2.2.tgz", + "integrity": "sha512-i0ZMWA199DG7sjxlzXn5AeYZxpRfMJjDPUl7lL9eJJX8TPRoIaxJU4ys/joP5faM5AXE1eqW/dslCj3uj4Nqpg==", "dev": true, - "license": "MIT" + "license": "BSD-3-Clause", + "dependencies": { + "@es-joy/jsdoccomment": "~0.48.0", + "are-docs-informative": "^0.0.2", + "comment-parser": "1.4.1", + "debug": "^4.3.6", + "escape-string-regexp": "^4.0.0", + "espree": "^10.1.0", + "esquery": "^1.6.0", + "parse-imports": "^2.1.1", + "semver": "^7.6.3", + "spdx-expression-parse": "^4.0.0", + "synckit": "^0.9.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0" + } }, - "node_modules/class-validator": { - "version": "0.14.1", - "resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.14.1.tgz", - "integrity": "sha512-2VEG9JICxIqTpoK1eMzZqaV+u/EiwEJkMGzTrZf6sU/fwsnOITVgYJ8yojSy6CaXtO9V0Cc6ZQZ8h8m4UBuLwQ==", + "node_modules/eslint-plugin-jsonc": { + "version": "2.16.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsonc/-/eslint-plugin-jsonc-2.16.0.tgz", + "integrity": "sha512-Af/ZL5mgfb8FFNleH6KlO4/VdmDuTqmM+SPnWcdoWywTetv7kq+vQe99UyQb9XO3b0OWLVuTH7H0d/PXYCMdSg==", "dev": true, "license": "MIT", "dependencies": { - "@types/validator": "^13.11.8", - "libphonenumber-js": "^1.10.53", - "validator": "^13.9.0" + "@eslint-community/eslint-utils": "^4.2.0", + "eslint-compat-utils": "^0.5.0", + "espree": "^9.6.1", + "graphemer": "^1.4.0", + "jsonc-eslint-parser": "^2.0.4", + "natural-compare": "^1.4.0", + "synckit": "^0.6.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ota-meshi" + }, + "peerDependencies": { + "eslint": ">=6.0.0" } }, - "node_modules/clean-stack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "node_modules/eslint-plugin-jsonc/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "engines": { - "node": ">=6" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/cli-boxes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz", - "integrity": "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==", + "node_modules/eslint-plugin-jsonc/node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", "dev": true, - "license": "MIT", + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, "engines": { - "node": ">=10" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://opencollective.com/eslint" } }, - "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==", + "node_modules/eslint-plugin-jsonc/node_modules/synckit": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.6.2.tgz", + "integrity": "sha512-Vhf+bUa//YSTYKseDiiEuQmhGCoIF3CVBhunm3r/DQnYiGT4JssmnKQc44BIyOZRK2pKjXXAgbhfmbeoC9CJpA==", "dev": true, "license": "MIT", "dependencies": { - "restore-cursor": "^3.1.0" + "tslib": "^2.3.1" }, "engines": { - "node": ">=8" + "node": ">=12.20" } }, - "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==", + "node_modules/eslint-plugin-n": { + "version": "17.10.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-17.10.2.tgz", + "integrity": "sha512-e+s4eAf5NtJaxPhTNu3qMO0Iz40WANS93w9LQgYcvuljgvDmWi/a3rh+OrNyMHeng6aOWGJO0rCg5lH4zi8yTw==", "dev": true, "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "enhanced-resolve": "^5.17.0", + "eslint-plugin-es-x": "^7.5.0", + "get-tsconfig": "^4.7.0", + "globals": "^15.8.0", + "ignore": "^5.2.4", + "minimatch": "^9.0.5", + "semver": "^7.5.3" + }, "engines": { - "node": ">=6" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": ">=8.23.0" } }, - "node_modules/cli-table3": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.5.tgz", - "integrity": "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==", + "node_modules/eslint-plugin-no-only-tests": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-no-only-tests/-/eslint-plugin-no-only-tests-3.3.0.tgz", + "integrity": "sha512-brcKcxGnISN2CcVhXJ/kEQlNa0MEfGRtwKtWA16SkqXHKitaKIMrfemJKLKX1YqDU5C/5JY3PvZXd5jEW04e0Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=5.0.0" + } + }, + "node_modules/eslint-plugin-perfectionist": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-perfectionist/-/eslint-plugin-perfectionist-3.5.0.tgz", + "integrity": "sha512-vwDNuxlAlbZJ3DjHo6GnfZrmMlJBLFrkOLBV/rYvVnLFD+x54u9VyJcGOfJ2DK9d1cd3a/C/vtBrbBNgAC6Mrg==", "dev": true, "license": "MIT", "dependencies": { - "string-width": "^4.2.0" + "@typescript-eslint/types": "^8.4.0", + "@typescript-eslint/utils": "^8.4.0", + "minimatch": "^9.0.5", + "natural-compare-lite": "^1.4.0" }, "engines": { - "node": "10.* || >= 12.*" + "node": "^18.0.0 || >=20.0.0" }, - "optionalDependencies": { - "@colors/colors": "1.5.0" + "peerDependencies": { + "astro-eslint-parser": "^1.0.2", + "eslint": ">=8.0.0", + "svelte": ">=3.0.0", + "svelte-eslint-parser": "^0.41.0", + "vue-eslint-parser": ">=9.0.0" + }, + "peerDependenciesMeta": { + "astro-eslint-parser": { + "optional": true + }, + "svelte": { + "optional": true + }, + "svelte-eslint-parser": { + "optional": true + }, + "vue-eslint-parser": { + "optional": true + } } }, - "node_modules/cli-table3/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/cli-table3/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "node_modules/eslint-plugin-regexp": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-regexp/-/eslint-plugin-regexp-2.6.0.tgz", + "integrity": "sha512-FCL851+kislsTEQEMioAlpDuK5+E5vs0hi1bF8cFlPlHcEjeRhuAzEsGikXRreE+0j4WhW2uO54MqTjXtYOi3A==", "dev": true, "license": "MIT", "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.9.1", + "comment-parser": "^1.4.0", + "jsdoc-type-pratt-parser": "^4.0.0", + "refa": "^0.12.1", + "regexp-ast-analysis": "^0.7.1", + "scslre": "^0.3.0" }, "engines": { - "node": ">=8" + "node": "^18 || >=20" + }, + "peerDependencies": { + "eslint": ">=8.44.0" } }, - "node_modules/clone": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", - "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==", + "node_modules/eslint-plugin-toml": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-toml/-/eslint-plugin-toml-0.11.1.tgz", + "integrity": "sha512-Y1WuMSzfZpeMIrmlP1nUh3kT8p96mThIq4NnHrYUhg10IKQgGfBZjAWnrg9fBqguiX4iFps/x/3Hb5TxBisfdw==", "dev": true, "license": "MIT", + "dependencies": { + "debug": "^4.1.1", + "eslint-compat-utils": "^0.5.0", + "lodash": "^4.17.19", + "toml-eslint-parser": "^0.10.0" + }, "engines": { - "node": ">=0.8" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ota-meshi" + }, + "peerDependencies": { + "eslint": ">=6.0.0" } }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/eslint-plugin-unicorn": { + "version": "55.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-unicorn/-/eslint-plugin-unicorn-55.0.0.tgz", + "integrity": "sha512-n3AKiVpY2/uDcGrS3+QsYDkjPfaOrNrsfQxU9nt5nitd9KuvVXrfAvgCO9DYPSfap+Gqjw9EOrXIsBp5tlHZjA==", "dev": true, "license": "MIT", "dependencies": { - "color-name": "~1.1.4" + "@babel/helper-validator-identifier": "^7.24.5", + "@eslint-community/eslint-utils": "^4.4.0", + "ci-info": "^4.0.0", + "clean-regexp": "^1.0.0", + "core-js-compat": "^3.37.0", + "esquery": "^1.5.0", + "globals": "^15.7.0", + "indent-string": "^4.0.0", + "is-builtin-module": "^3.2.1", + "jsesc": "^3.0.2", + "pluralize": "^8.0.0", + "read-pkg-up": "^7.0.1", + "regexp-tree": "^0.1.27", + "regjsparser": "^0.10.0", + "semver": "^7.6.1", + "strip-indent": "^3.0.0" }, "engines": { - "node": ">=7.0.0" + "node": ">=18.18" + }, + "funding": { + "url": "https://github.com/sindresorhus/eslint-plugin-unicorn?sponsor=1" + }, + "peerDependencies": { + "eslint": ">=8.56.0" } }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, - "node_modules/color-support": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", - "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "node_modules/eslint-plugin-unused-imports": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-unused-imports/-/eslint-plugin-unused-imports-4.1.3.tgz", + "integrity": "sha512-lqrNZIZjFMUr7P06eoKtQLwyVRibvG7N+LtfKtObYGizAAGrcqLkc3tDx+iAik2z7q0j/XI3ihjupIqxhFabFA==", "dev": true, - "license": "ISC", - "bin": { - "color-support": "bin.js" + "license": "MIT", + "peerDependencies": { + "@typescript-eslint/eslint-plugin": "^8.0.0-0 || ^7.0.0 || ^6.0.0 || ^5.0.0", + "eslint": "^9.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@typescript-eslint/eslint-plugin": { + "optional": true + } } }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "node_modules/eslint-plugin-vue": { + "version": "9.28.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.28.0.tgz", + "integrity": "sha512-ShrihdjIhOTxs+MfWun6oJWuk+g/LAhN+CiuOl/jjkG3l0F2AuK5NMTaWqyvBgkFtpYmyks6P4603mLmhNJW8g==", "dev": true, "license": "MIT", "dependencies": { - "delayed-stream": "~1.0.0" + "@eslint-community/eslint-utils": "^4.4.0", + "globals": "^13.24.0", + "natural-compare": "^1.4.0", + "nth-check": "^2.1.1", + "postcss-selector-parser": "^6.0.15", + "semver": "^7.6.3", + "vue-eslint-parser": "^9.4.3", + "xml-name-validator": "^4.0.0" }, "engines": { - "node": ">= 0.8" + "node": "^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.2.0 || ^7.0.0 || ^8.0.0 || ^9.0.0" } }, - "node_modules/commander": { - "version": "12.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", - "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "node_modules/eslint-plugin-vue/node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", "dev": true, "license": "MIT", + "dependencies": { + "type-fest": "^0.20.2" + }, "engines": { - "node": ">=18" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/commist": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/commist/-/commist-1.1.0.tgz", - "integrity": "sha512-rraC8NXWOEjhADbZe9QBNzLAN5Q3fsTPQtBV+fEVj6xKIgDgNiEVE6ZNfHpZOqfQ21YUzfVNUXLOEZquYvQPPg==", - "license": "MIT", - "dependencies": { - "leven": "^2.1.0", - "minimist": "^1.1.0" + "node_modules/eslint-plugin-vue/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "license": "MIT" - }, - "node_modules/concat-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", - "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==", - "engines": [ - "node >= 6.0" - ], + "node_modules/eslint-plugin-yml": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-yml/-/eslint-plugin-yml-1.14.0.tgz", + "integrity": "sha512-ESUpgYPOcAYQO9czugcX5OqRvn/ydDVwGCPXY4YjPqc09rHaUVUA6IE6HLQys4rXk/S+qx3EwTd1wHCwam/OWQ==", + "dev": true, "license": "MIT", "dependencies": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.0.2", - "typedarray": "^0.0.6" + "debug": "^4.3.2", + "eslint-compat-utils": "^0.5.0", + "lodash": "^4.17.21", + "natural-compare": "^1.4.0", + "yaml-eslint-parser": "^1.2.1" + }, + "engines": { + "node": "^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ota-meshi" + }, + "peerDependencies": { + "eslint": ">=6.0.0" } }, - "node_modules/config-chain": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", - "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", + "node_modules/eslint-processor-vue-blocks": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/eslint-processor-vue-blocks/-/eslint-processor-vue-blocks-0.1.2.tgz", + "integrity": "sha512-PfpJ4uKHnqeL/fXUnzYkOax3aIenlwewXRX8jFinA1a2yCFnLgMuiH3xvCgvHHUlV2xJWQHbCTdiJWGwb3NqpQ==", "dev": true, "license": "MIT", - "dependencies": { - "ini": "^1.3.4", - "proto-list": "~1.2.1" + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/compiler-sfc": "^3.3.0", + "eslint": "^8.50.0 || ^9.0.0" } }, - "node_modules/config-chain/node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "dev": true, - "license": "ISC" - }, - "node_modules/configstore": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/configstore/-/configstore-6.0.0.tgz", - "integrity": "sha512-cD31W1v3GqUlQvbBCGcXmd2Nj9SvLDOP1oQ0YFuLETufzSPaKp11rYBsSOm7rCsW3OnIRAFM3OxRhceaXNYHkA==", + "node_modules/eslint-scope": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.0.2.tgz", + "integrity": "sha512-6E4xmrTw5wtxnLA5wYL3WDfhZ/1bUBGOXV0zQvVRDOtrR8D0p6W7fs3JweNYhwRYeGvd/1CKX2se0/2s7Q/nJA==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "dot-prop": "^6.0.1", - "graceful-fs": "^4.2.6", - "unique-string": "^3.0.0", - "write-file-atomic": "^3.0.3", - "xdg-basedir": "^5.0.1" + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" }, "engines": { - "node": ">=12" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://github.com/yeoman/configstore?sponsor=1" + "url": "https://opencollective.com/eslint" } }, - "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, - "license": "MIT" - }, - "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==", + "node_modules/eslint-visitor-keys": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz", + "integrity": "sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==", "dev": true, - "license": "ISC" + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } }, - "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==", + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, "license": "MIT", "dependencies": { - "safe-buffer": "5.2.1" - }, - "engines": { - "node": ">= 0.6" + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, - "node_modules/cookie": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", - "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, - "license": "MIT", + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, "engines": { - "node": ">= 0.6" + "node": "*" } }, - "node_modules/core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/cors": { - "version": "2.8.5", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", - "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "node_modules/espree": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.1.0.tgz", + "integrity": "sha512-M1M6CpiE6ffoigIOWYO9UDP8TMUw9kqb21tf+08IgDYjCsOvCuDt4jQcZmoYxx+w7zlKw9/N0KXfto+I8/FrXA==", "dev": true, - "license": "MIT", + "license": "BSD-2-Clause", "dependencies": { - "object-assign": "^4", - "vary": "^1" + "acorn": "^8.12.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.0.0" }, "engines": { - "node": ">= 0.10" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "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==", + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", "dev": true, - "license": "MIT" + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } }, - "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==", + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", "dev": true, - "license": "MIT", + "license": "BSD-3-Clause", "dependencies": { - "luxon": "^3.2.1" + "estraverse": "^5.1.0" }, "engines": { - "node": ">=12.0.0" + "node": ">=0.10" } }, - "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "dev": true, - "license": "MIT", + "license": "BSD-2-Clause", "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" + "estraverse": "^5.2.0" }, "engines": { - "node": ">= 8" + "node": ">=4.0" } }, - "node_modules/crypto-random-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-4.0.0.tgz", - "integrity": "sha512-x8dy3RnvYdlUcPOjkEHqozhiwzKNSq7GcPuXFbnyMOCHxX8V3OgIg/pYuabl2sbUPfIJaeAQB7PMOK8DFIdoRA==", + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true, - "license": "MIT", - "dependencies": { - "type-fest": "^1.0.1" - }, + "license": "BSD-2-Clause", "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=4.0" } }, - "node_modules/crypto-random-string/node_modules/type-fest": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", - "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true, - "license": "(MIT OR CC0-1.0)", + "license": "BSD-2-Clause", "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=0.10.0" } }, - "node_modules/dayjs": { - "version": "1.11.10", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.10.tgz", - "integrity": "sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==", + "node_modules/event-stream": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-4.0.1.tgz", + "integrity": "sha512-qACXdu/9VHPBzcyhdOWR5/IahhGMf0roTeZJfzz077GwylcDd90yOHLouhmv7GJ5XzPi6ekaQWd8AvPP2nOvpA==", "dev": true, - "license": "MIT" - }, - "node_modules/debug": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", - "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", "license": "MIT", "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "duplexer": "^0.1.1", + "from": "^0.1.7", + "map-stream": "0.0.7", + "pause-stream": "^0.0.11", + "split": "^1.0.1", + "stream-combiner": "^0.2.2", + "through": "^2.3.8" } }, - "node_modules/decamelize": { + "node_modules/event-target-shim": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-5.0.1.tgz", - "integrity": "sha512-VfxadyCECXgQlkoEAjeghAr5gY3Hf+IKjKb+X8tGVDtveCjN+USwprd2q3QXBR9T1+x2DG0XZF5/w+7HAtSaXA==", + "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, "license": "MIT", "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=6" } }, - "node_modules/decompress-response": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", - "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", "dev": true, "license": "MIT", - "dependencies": { - "mimic-response": "^3.1.0" - }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=0.8.x" } }, - "node_modules/deep-equal": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.3.tgz", - "integrity": "sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA==", + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, "license": "MIT", "dependencies": { - "array-buffer-byte-length": "^1.0.0", - "call-bind": "^1.0.5", - "es-get-iterator": "^1.1.3", - "get-intrinsic": "^1.2.2", - "is-arguments": "^1.1.1", - "is-array-buffer": "^3.0.2", - "is-date-object": "^1.0.5", - "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.2", - "isarray": "^2.0.5", - "object-is": "^1.1.5", - "object-keys": "^1.1.1", - "object.assign": "^4.1.4", - "regexp.prototype.flags": "^1.5.1", - "side-channel": "^1.0.4", - "which-boxed-primitive": "^1.0.2", - "which-collection": "^1.0.1", - "which-typed-array": "^1.1.13" + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" }, "engines": { - "node": ">= 0.4" + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4.0.0" + "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "node_modules/execa/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "dev": true, - "license": "MIT" + "license": "ISC" }, - "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==", + "node_modules/exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", "dev": true, - "license": "MIT", - "dependencies": { - "clone": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">= 0.8.0" } }, - "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==", + "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, - "license": "MIT", + "license": "(MIT OR WTFPL)", "engines": { - "node": ">=0.8" + "node": ">=6" } }, - "node_modules/defer-to-connect": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", - "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", + "node_modules/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", "dev": true, "license": "MIT", + "dependencies": { + "@jest/expect-utils": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0" + }, "engines": { - "node": ">=10" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/define-data-property": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", - "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, + "node_modules/fakegato-history": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/fakegato-history/-/fakegato-history-0.6.5.tgz", + "integrity": "sha512-MVam/dRAeXiMBOMY+qUN8w+6Hosx9UClUsC8dVKMzRxcHkVAN4r0ptdbf7mb78fAYbmDLcEZHHNbDBitkELPmg==", "license": "MIT", "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "gopd": "^1.0.1" + "debug": "^2.2.0", + "googleapis": ">39.1.0" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "homebridge": ">=0.4.0", + "node": ">=4.3.2" } }, - "node_modules/define-properties": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", - "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "node_modules/fakegato-history/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "license": "MIT", "dependencies": { - "define-data-property": "^1.0.1", - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "ms": "2.0.0" } }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "node_modules/fakegato-history/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/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, - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } + "license": "MIT" }, - "node_modules/delegates": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", + "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, "license": "MIT" }, - "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==", + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } + "license": "MIT" }, - "node_modules/detect-libc": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", - "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", + "node_modules/fast-diff": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", + "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", "dev": true, - "license": "Apache-2.0", + "license": "Apache-2.0" + }, + "node_modules/fast-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, "engines": { - "node": ">=8" + "node": ">=8.6.0" } }, - "node_modules/diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "dev": true, - "license": "BSD-3-Clause", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, "engines": { - "node": ">=0.3.1" + "node": ">= 6" } }, - "node_modules/dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-json-stringify": { + "version": "5.16.1", + "resolved": "https://registry.npmjs.org/fast-json-stringify/-/fast-json-stringify-5.16.1.tgz", + "integrity": "sha512-KAdnLvy1yu/XrRtP+LJnxbBGrhN+xXu+gt3EUvZhYGKCr3lFHq/7UFJHHFgmJKoqlh6B40bZLEv7w46B0mqn1g==", "dev": true, "license": "MIT", "dependencies": { - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=8" + "@fastify/merge-json-schemas": "^0.1.0", + "ajv": "^8.10.0", + "ajv-formats": "^3.0.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/dns-packet": { - "version": "5.6.1", - "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz", - "integrity": "sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==", + "node_modules/fast-json-stringify/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dev": true, "license": "MIT", "dependencies": { - "@leichtgewicht/ip-codec": "^2.0.1" + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" }, - "engines": { - "node": ">=6" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/dot-prop": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-6.0.1.tgz", - "integrity": "sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==", + "node_modules/fast-json-stringify/node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", "dev": true, "license": "MIT", "dependencies": { - "is-obj": "^2.0.0" + "ajv": "^8.0.0" }, - "engines": { - "node": ">=10" + "peerDependencies": { + "ajv": "^8.0.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependenciesMeta": { + "ajv": { + "optional": true + } } }, - "node_modules/duplexer": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", - "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", + "node_modules/fast-json-stringify/node_modules/ajv/node_modules/fast-uri": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.1.tgz", + "integrity": "sha512-MWipKbbYiYI0UC7cl8m/i/IWTqfC8YXsqjzybjddLsFjStroQzsHXkc73JutMvBiXmOvapk+axIl79ig5t55Bw==", "dev": true, "license": "MIT" }, - "node_modules/duplexer2": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", - "integrity": "sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==", + "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, - "license": "BSD-3-Clause", - "dependencies": { - "readable-stream": "^2.0.2" - } + "license": "MIT" }, - "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==", + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true, "license": "MIT" }, - "node_modules/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==", + "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, "license": "MIT", "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" + "fast-decode-uri-component": "^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==", + "node_modules/fast-redact": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-3.5.0.tgz", + "integrity": "sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A==", + "dev": true, + "license": "MIT", + "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, "license": "MIT" }, - "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==", + "node_modules/fast-srp-hap": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/fast-srp-hap/-/fast-srp-hap-2.0.4.tgz", + "integrity": "sha512-lHRYYaaIbMrhZtsdGTwPN82UbqD9Bv8QfOlKs+Dz6YRnByZifOh93EYmf2iEWFtkOEIqR2IK8cFD0UN5wLIWBQ==", "dev": true, "license": "MIT", - "dependencies": { - "safe-buffer": "~5.1.0" + "engines": { + "node": ">=10.17.0" } }, - "node_modules/duplexify": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.3.tgz", - "integrity": "sha512-M3BmBhwJRZsSx38lZyhE53Csddgzl5R7xGJNk7CVddZD6CcmwMCH8J+7AprIrQKH7TonKxaCjcv27Qmf+sQ+oA==", + "node_modules/fast-uri": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-2.4.0.tgz", + "integrity": "sha512-ypuAmmMKInk5q7XcepxlnUWDLWv4GFtaJqAzWKqn62IpQ3pejtr5dTVbt3vwqVaMKmkNR55sTT+CqUKIaT21BA==", + "dev": true, + "license": "MIT" + }, + "node_modules/fastify": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/fastify/-/fastify-4.28.1.tgz", + "integrity": "sha512-kFWUtpNr4i7t5vY2EJPCN2KgMVpuqfU4NjnJNCgiNB900oiDeYqaNDRcAfeBbOF5hGixixxcKnOU4KN9z6QncQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], "license": "MIT", "dependencies": { - "end-of-stream": "^1.4.1", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1", - "stream-shift": "^1.0.2" + "@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.3.0", + "fast-content-type-parse": "^1.1.0", + "fast-json-stringify": "^5.8.0", + "find-my-way": "^8.0.0", + "light-my-request": "^5.11.0", + "pino": "^9.0.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/eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "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, "license": "MIT" }, - "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==", + "node_modules/fastq": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "dev": true, "license": "Apache-2.0", "dependencies": { - "safe-buffer": "^5.0.1" + "bser": "2.1.1" } }, - "node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } }, - "node_modules/encoding": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", - "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, "license": "MIT", - "optional": true, "dependencies": { - "iconv-lite": "^0.6.2" + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" } }, - "node_modules/end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "node_modules/find-my-way": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/find-my-way/-/find-my-way-8.2.0.tgz", + "integrity": "sha512-HdWXgFYc6b1BJcOBDBwjqWuHJj1WYiqrxSh25qtU4DabpMFdj/gSunNBQb83t+8Zt67D7CXEzJWTkxaShMTMOA==", + "dev": true, "license": "MIT", "dependencies": { - "once": "^1.4.0" + "fast-deep-equal": "^3.1.3", + "fast-querystring": "^1.0.0", + "safe-regex2": "^3.1.0" + }, + "engines": { + "node": ">=14" } }, - "node_modules/engine.io": { - "version": "6.5.5", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.5.5.tgz", - "integrity": "sha512-C5Pn8Wk+1vKBoHghJODM63yk8MvrO9EWZUfkAt5HAqIgPE4/8FF0PEGHXtEd40l223+cE5ABWuPzm38PHFXfMA==", + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, "license": "MIT", "dependencies": { - "@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.17.1" + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" }, "engines": { - "node": ">=10.2.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/engine.io-parser": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", - "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", + "node_modules/find-up-simple": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/find-up-simple/-/find-up-simple-1.0.0.tgz", + "integrity": "sha512-q7Us7kcjj2VMePAa02hDAF6d+MzsdsAWEwYyOpwUtlerRBkOEPBCRZrAV4XfcSN8fHAgaD0hP7miwoay6DCprw==", "dev": true, "license": "MIT", "engines": { - "node": ">=10.0.0" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "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==", + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", "dev": true, "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, "engines": { - "node": ">= 0.6" + "node": ">=16" } }, - "node_modules/engine.io/node_modules/ws": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", - "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "node_modules/flatted": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", + "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", + "dev": true, + "license": "ISC" + }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], "license": "MIT", "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" + "node": ">=4.0" }, "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { + "debug": { "optional": true } } }, - "node_modules/entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "node_modules/for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "license": "MIT", + "dependencies": { + "is-callable": "^1.1.3" + } + }, + "node_modules/foreground-child": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", + "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", "dev": true, - "license": "BSD-2-Clause", + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, "engines": { - "node": ">=0.12" + "node": ">=14" }, "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/env-paths": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", - "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "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, "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, "engines": { - "node": ">=6" + "node": ">= 6" } }, - "node_modules/err-code": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", - "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/from": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz", + "integrity": "sha512-twe20eF1OxVxp/ML/kq2p1uc6KvFK/+vs8WjEbeKmV2He22MKm7YF2ANIt+EOqhJ5L3K/SuuPhk0hWQDjOM23g==", "dev": true, "license": "MIT" }, - "node_modules/es-define-property": { + "node_modules/fs-constants": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", - "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "dev": true, + "license": "MIT" + }, + "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, "license": "MIT", "dependencies": { - "get-intrinsic": "^1.2.4" + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" }, "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "license": "MIT", - "engines": { - "node": ">= 0.4" + "node": ">=14.14" } }, - "node_modules/es-get-iterator": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz", - "integrity": "sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==", - "license": "MIT", + "node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dev": true, + "license": "ISC", "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.3", - "has-symbols": "^1.0.3", - "is-arguments": "^1.1.1", - "is-map": "^2.0.2", - "is-set": "^2.0.2", - "is-string": "^1.0.7", - "isarray": "^2.0.5", - "stop-iteration-iterator": "^1.0.0" + "minipass": "^3.0.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": ">= 8" } }, - "node_modules/escape-goat": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-4.0.0.tgz", - "integrity": "sha512-2Sd4ShcWxbx6OY1IHyla/CVNwvg7XwZVoXZHcSu9w9SReNP1EzzD5T8NWKIR38fIqEns9kDWKUQTXXAmlDrdPg==", + "node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">=8" } }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "dev": true, - "license": "MIT" - }, - "node_modules/escape-string-regexp": { + "node_modules/fs-minipass/node_modules/yallist": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } + "license": "ISC" }, - "node_modules/eslint": { - "version": "9.7.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.7.0.tgz", - "integrity": "sha512-FzJ9D/0nGiCGBf8UXO/IGLTgLVzIxze1zpfA8Ton2mjLovXdAPlYDv+MQDcqj3TmrhAGYfOpz9RfR+ent0AgAw==", + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, + "hasInstallScript": true, "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.11.0", - "@eslint/config-array": "^0.17.0", - "@eslint/eslintrc": "^3.1.0", - "@eslint/js": "9.7.0", - "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.3.0", - "@nodelib/fs.walk": "^1.2.8", - "ajv": "^6.12.4", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.3.2", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.0.2", - "eslint-visitor-keys": "^4.0.0", - "espree": "^10.1.0", - "esquery": "^1.5.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^8.0.0", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3", - "strip-ansi": "^6.0.1", - "text-table": "^0.2.0" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://eslint.org/donate" + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "node_modules/eslint-scope": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.0.2.tgz", - "integrity": "sha512-6E4xmrTw5wtxnLA5wYL3WDfhZ/1bUBGOXV0zQvVRDOtrR8D0p6W7fs3JweNYhwRYeGvd/1CKX2se0/2s7Q/nJA==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/eslint-visitor-keys": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz", - "integrity": "sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "license": "MIT", "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/espree": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.1.0.tgz", - "integrity": "sha512-M1M6CpiE6ffoigIOWYO9UDP8TMUw9kqb21tf+08IgDYjCsOvCuDt4jQcZmoYxx+w7zlKw9/N0KXfto+I8/FrXA==", + "node_modules/futoin-hkdf": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/futoin-hkdf/-/futoin-hkdf-1.5.3.tgz", + "integrity": "sha512-SewY5KdMpaoCeh7jachEWFsh1nNlaDjNHZXWqL5IGwtpEYHTgkr2+AMCgNwKWkcc0wpSYrZfR7he4WdmHFtDxQ==", "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "acorn": "^8.12.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.0.0" - }, + "license": "Apache-2.0", "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" + "node": ">=8" } }, - "node_modules/esquery": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", - "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", - "dev": true, - "license": "BSD-3-Clause", + "node_modules/gaxios": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.7.1.tgz", + "integrity": "sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ==", + "license": "Apache-2.0", "dependencies": { - "estraverse": "^5.1.0" + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "is-stream": "^2.0.0", + "node-fetch": "^2.6.9", + "uuid": "^9.0.1" }, "engines": { - "node": ">=0.10" + "node": ">=14" } }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "license": "BSD-2-Clause", + "node_modules/gcp-metadata": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.1.0.tgz", + "integrity": "sha512-Jh/AIwwgaxan+7ZUUmRLCjtchyDiqh4KjBJ5tW3plBZb5iL/BPcso8A5DlzeD9qlw0duCamnNdpFjxwaT0KyKg==", + "license": "Apache-2.0", "dependencies": { - "estraverse": "^5.2.0" + "gaxios": "^6.0.0", + "json-bigint": "^1.0.0" }, "engines": { - "node": ">=4.0" + "node": ">=14" } }, - "node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", "dev": true, - "license": "BSD-2-Clause", + "license": "MIT", "engines": { - "node": ">=4.0" + "node": ">=6.9.0" } }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true, - "license": "BSD-2-Clause", + "license": "ISC", "engines": { - "node": ">=0.10.0" + "node": "6.* || 8.* || >= 10.*" } }, - "node_modules/event-stream": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-4.0.1.tgz", - "integrity": "sha512-qACXdu/9VHPBzcyhdOWR5/IahhGMf0roTeZJfzz077GwylcDd90yOHLouhmv7GJ5XzPi6ekaQWd8AvPP2nOvpA==", + "node_modules/get-func-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", + "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", "dev": true, "license": "MIT", - "dependencies": { - "duplexer": "^0.1.1", - "from": "^0.1.7", - "map-stream": "0.0.7", - "pause-stream": "^0.0.11", - "split": "^1.0.1", - "stream-combiner": "^0.2.2", - "through": "^2.3.8" + "engines": { + "node": "*" } }, - "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, + "node_modules/get-intrinsic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, "engines": { - "node": ">=6" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", "dev": true, "license": "MIT", "engines": { - "node": ">=0.8.x" + "node": ">=8.0.0" } }, - "node_modules/expand-template": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", - "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", "dev": true, - "license": "(MIT OR WTFPL)", + "license": "MIT", "engines": { - "node": ">=6" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "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==", + "node_modules/get-tsconfig": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.8.0.tgz", + "integrity": "sha512-Pgba6TExTZ0FJAn1qkJAjIeKoDJ3CsI2ChuLohJnZl/tTU8MVrq3b+2t5UOPfRa4RMsorClBjJALkJUMjG1PAw==", "dev": true, - "license": "Apache-2.0" - }, - "node_modules/extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "license": "MIT" - }, - "node_modules/fakegato-history": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/fakegato-history/-/fakegato-history-0.6.4.tgz", - "integrity": "sha512-O+YFikmYutK1xqjyQOn8/ZE2ez04Sech35++CGvFMoCoycPrQTN+W2We10UNXhsorKIngSPmHPzjCQVz0ILriw==", "license": "MIT", "dependencies": { - "debug": "^2.2.0", - "googleapis": ">39.1.0" + "resolve-pkg-maps": "^1.0.0" }, - "engines": { - "homebridge": ">=0.4.0", - "node": ">=4.3.2" - } - }, - "node_modules/fakegato-history/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" } }, - "node_modules/fakegato-history/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, - "node_modules/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==", + "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, "license": "MIT" }, - "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==", + "node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", "dev": true, - "license": "MIT" + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "dev": true, - "license": "MIT" + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } }, - "node_modules/fast-glob": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", - "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "node_modules/globals": { + "version": "15.9.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-15.9.0.tgz", + "integrity": "sha512-SmSKyLLKFbSr6rptvP8izbyxJL4ILwqO9Jg23UA0sDlGlu58V59D1//I3vlc0KJphVdUR7vMjHIplYnzBxorQA==", "dev": true, "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - }, "engines": { - "node": ">=8.6.0" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/fast-glob/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "license": "ISC", + "node_modules/google-auth-library": { + "version": "9.14.1", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-9.14.1.tgz", + "integrity": "sha512-Rj+PMjoNFGFTmtItH7gHfbHpGVSb3vmnGK3nwNBqxQF9NoBpttSZI/rc0WiM63ma2uGDQtYEkMHkK9U6937NiA==", + "license": "Apache-2.0", "dependencies": { - "is-glob": "^4.0.1" + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "gaxios": "^6.1.1", + "gcp-metadata": "^6.1.0", + "gtoken": "^7.0.0", + "jws": "^4.0.0" }, "engines": { - "node": ">= 6" + "node": ">=14" } }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-json-stringify": { - "version": "5.16.1", - "resolved": "https://registry.npmjs.org/fast-json-stringify/-/fast-json-stringify-5.16.1.tgz", - "integrity": "sha512-KAdnLvy1yu/XrRtP+LJnxbBGrhN+xXu+gt3EUvZhYGKCr3lFHq/7UFJHHFgmJKoqlh6B40bZLEv7w46B0mqn1g==", - "dev": true, - "license": "MIT", + "node_modules/googleapis": { + "version": "144.0.0", + "resolved": "https://registry.npmjs.org/googleapis/-/googleapis-144.0.0.tgz", + "integrity": "sha512-ELcWOXtJxjPX4vsKMh+7V+jZvgPwYMlEhQFiu2sa9Qmt5veX8nwXPksOWGGN6Zk4xCiLygUyaz7xGtcMO+Onxw==", + "license": "Apache-2.0", "dependencies": { - "@fastify/merge-json-schemas": "^0.1.0", - "ajv": "^8.10.0", - "ajv-formats": "^3.0.1", - "fast-deep-equal": "^3.1.3", - "fast-uri": "^2.1.0", - "json-schema-ref-resolver": "^1.0.1", - "rfdc": "^1.2.0" + "google-auth-library": "^9.0.0", + "googleapis-common": "^7.0.0" + }, + "engines": { + "node": ">=14.0.0" } }, - "node_modules/fast-json-stringify/node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", - "dev": true, - "license": "MIT", + "node_modules/googleapis-common": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/googleapis-common/-/googleapis-common-7.2.0.tgz", + "integrity": "sha512-/fhDZEJZvOV3X5jmD+fKxMqma5q2Q9nZNSF3kn1F18tpxmA86BcTxAGBQdM0N89Z3bEaIs+HVznSmFJEAmMTjA==", + "license": "Apache-2.0", "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" + "extend": "^3.0.2", + "gaxios": "^6.0.3", + "google-auth-library": "^9.7.0", + "qs": "^6.7.0", + "url-template": "^2.0.8", + "uuid": "^9.0.0" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "engines": { + "node": ">=14.0.0" } }, - "node_modules/fast-json-stringify/node_modules/ajv-formats": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", - "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", - "dev": true, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", "license": "MIT", "dependencies": { - "ajv": "^8.0.0" - }, - "peerDependencies": { - "ajv": "^8.0.0" + "get-intrinsic": "^1.1.3" }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/fast-json-stringify/node_modules/ajv/node_modules/fast-uri": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.1.tgz", - "integrity": "sha512-MWipKbbYiYI0UC7cl8m/i/IWTqfC8YXsqjzybjddLsFjStroQzsHXkc73JutMvBiXmOvapk+axIl79ig5t55Bw==", - "dev": true, - "license": "MIT" - }, - "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, - "license": "MIT" - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "dev": true, - "license": "MIT" + "license": "ISC" }, - "node_modules/fast-memoize": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/fast-memoize/-/fast-memoize-2.5.2.tgz", - "integrity": "sha512-Ue0LwpDYErFbmNnZSF0UH6eImUwDmogUO1jyE+JbN2gsQz/jICm1Ve7t9QT0rNSsfJt+Hs4/S3GnsDVjL4HVrw==", + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", "dev": true, "license": "MIT" }, - "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, + "node_modules/gtoken": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-7.1.0.tgz", + "integrity": "sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==", "license": "MIT", "dependencies": { - "fast-decode-uri-component": "^1.0.1" + "gaxios": "^6.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=14.0.0" } }, - "node_modules/fast-redact": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-3.5.0.tgz", - "integrity": "sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A==", + "node_modules/hap-nodejs": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/hap-nodejs/-/hap-nodejs-0.12.2.tgz", + "integrity": "sha512-EAgcBOxxMaWKkRfuGc8FBVJO5/OCFM2jMt5+szkqazWLKANkRLrvf/GtQnPsl6GUuLiWYddLZ/zwasayU8ljQQ==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", + "dependencies": { + "@homebridge/ciao": "^1.2.0", + "@homebridge/dbus-native": "^0.6.0", + "bonjour-hap": "^3.7.2", + "debug": "^4.3.5", + "fast-srp-hap": "^2.0.4", + "futoin-hkdf": "^1.5.3", + "node-persist": "^0.0.12", + "source-map-support": "^0.5.21", + "tslib": "^2.6.2", + "tweetnacl": "^1.0.3" + }, "engines": { - "node": ">=6" + "node": "^18 || ^20" } }, - "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, - "license": "MIT" + "node_modules/has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, - "node_modules/fast-srp-hap": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/fast-srp-hap/-/fast-srp-hap-2.0.4.tgz", - "integrity": "sha512-lHRYYaaIbMrhZtsdGTwPN82UbqD9Bv8QfOlKs+Dz6YRnByZifOh93EYmf2iEWFtkOEIqR2IK8cFD0UN5wLIWBQ==", + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, "license": "MIT", "engines": { - "node": ">=10.17.0" + "node": ">=8" } }, - "node_modules/fast-uri": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-2.4.0.tgz", - "integrity": "sha512-ypuAmmMKInk5q7XcepxlnUWDLWv4GFtaJqAzWKqn62IpQ3pejtr5dTVbt3vwqVaMKmkNR55sTT+CqUKIaT21BA==", - "dev": true, - "license": "MIT" - }, - "node_modules/fastify": { - "version": "4.26.2", - "resolved": "https://registry.npmjs.org/fastify/-/fastify-4.26.2.tgz", - "integrity": "sha512-90pjTuPGrfVKtdpLeLzND5nyC4woXZN5VadiNQCicj/iJU4viNHKhsAnb7jmv1vu2IzkLXyBiCzdWuzeXgQ5Ug==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", "license": "MIT", "dependencies": { - "@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.3.0", - "fast-content-type-parse": "^1.1.0", - "fast-json-stringify": "^5.8.0", - "find-my-way": "^8.0.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" + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "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, - "license": "MIT" + "node_modules/has-proto": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, - "node_modules/fastq": { - "version": "1.17.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", - "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", - "dev": true, - "license": "ISC", - "dependencies": { - "reusify": "^1.0.4" + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/file-entry-cache": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", - "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", - "dev": true, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", "license": "MIT", "dependencies": { - "flat-cache": "^4.0.0" + "has-symbols": "^1.0.3" }, "engines": { - "node": ">=16.0.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", "license": "MIT", "dependencies": { - "to-regex-range": "^5.0.1" + "function-bind": "^1.1.2" }, "engines": { - "node": ">=8" + "node": ">= 0.4" } }, - "node_modules/find-my-way": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/find-my-way/-/find-my-way-8.2.0.tgz", - "integrity": "sha512-HdWXgFYc6b1BJcOBDBwjqWuHJj1WYiqrxSh25qtU4DabpMFdj/gSunNBQb83t+8Zt67D7CXEzJWTkxaShMTMOA==", + "node_modules/hast-util-to-html": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.2.tgz", + "integrity": "sha512-RP5wNpj5nm1Z8cloDv4Sl4RS8jH5HYa0v93YB6Wb4poEzgMo/dAAL0KcT4974dCjcNG5pkLqTImeFHHCwwfY3g==", "dev": true, "license": "MIT", "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-querystring": "^1.0.0", - "safe-regex2": "^3.1.0" + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-whitespace": "^3.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0", + "stringify-entities": "^4.0.0", + "zwitch": "^2.0.4" }, - "engines": { - "node": ">=14" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "node_modules/hast-util-whitespace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", "dev": true, "license": "MIT", "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" + "@types/hast": "^3.0.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/flat-cache": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", - "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", - "dev": true, - "license": "MIT", + "node_modules/hb-lib-tools": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/hb-lib-tools/-/hb-lib-tools-2.0.5.tgz", + "integrity": "sha512-xEzix/lo0at/z99o2FeRvSz3agCe4U0iBMHu5REl6TSKiGfJKS7oDRqAatlIf93RqntkrVkIa2UAj/hfWylDHw==", + "license": "Apache-2.0", "dependencies": { - "flatted": "^3.2.9", - "keyv": "^4.5.4" + "bonjour-hap": "^3.8.0", + "chalk": "^5.3.0", + "semver": "^7.6.3" + }, + "bin": { + "hap": "cli/hap.js", + "json": "cli/json.js", + "sysinfo": "cli/sysinfo.js", + "upnp": "cli/upnp.js" }, "engines": { - "node": ">=16" + "node": "20.17.0||^20||^18" } }, - "node_modules/flatted": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", - "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", - "dev": true, - "license": "ISC" + "node_modules/hb-lib-tools/node_modules/chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } }, - "node_modules/follow-redirects": { - "version": "1.15.6", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", - "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", + "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, - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], "license": "MIT", "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } + "node": ">=16.0.0" } }, - "node_modules/for-each": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", - "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "node_modules/help-me": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/help-me/-/help-me-3.0.0.tgz", + "integrity": "sha512-hx73jClhyk910sidBB7ERlnhMlFsJJIBqSVMFDwPN8o2v9nmp5KgLq1Xz1Bf1fCMMZ6mPrX159iG0VLy/fPMtQ==", "license": "MIT", "dependencies": { - "is-callable": "^1.1.3" + "glob": "^7.1.6", + "readable-stream": "^3.6.0" } }, - "node_modules/foreground-child": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.2.1.tgz", - "integrity": "sha512-PXUUyLqrR2XCWICfv6ukppP96sdFwWbNEnfEMt7jNsISjMsvaLNinAHNDYyvkyU+SZG2BTSbT5NjG+vZslfGTA==", - "dev": true, + "node_modules/help-me/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==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/help-me/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", "license": "ISC", "dependencies": { - "cross-spawn": "^7.0.0", - "signal-exit": "^4.0.1" + "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": ">=14" + "node": "*" }, "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==", + "node_modules/help-me/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/hexy": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/hexy/-/hexy-0.3.5.tgz", + "integrity": "sha512-UCP7TIZPXz5kxYJnNOym+9xaenxCLor/JyhKieo8y8/bJWunGh9xbhy3YrgYJUQ87WwfXGm05X330DszOfINZw==", "dev": true, "license": "MIT", + "bin": { + "hexy": "bin/hexy_cmd.js" + }, + "engines": { + "node": ">=10.4" + } + }, + "node_modules/homebridge": { + "version": "1.8.4", + "resolved": "https://registry.npmjs.org/homebridge/-/homebridge-1.8.4.tgz", + "integrity": "sha512-Wr5QdI/OCpxCM3rdXyuPUxNlbuwiFngnuj+ZsrCbg5cMwKRMfN+RRGiW2LUSY2BDGQC0cSUt/9TF6tIlov16Qw==", + "dev": true, + "license": "Apache-2.0", "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" + "chalk": "4.1.2", + "commander": "12.1.0", + "fs-extra": "11.2.0", + "hap-nodejs": "0.12.2", + "qrcode-terminal": "0.12.0", + "semver": "7.6.3", + "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.58.0", + "resolved": "https://registry.npmjs.org/homebridge-config-ui-x/-/homebridge-config-ui-x-4.58.0.tgz", + "integrity": "sha512-Pby2/avNWItUHWeMuyXmqjmfrwGP/xeqHvgnwR+VcrQnGYQx67QqgeB3+C0/Pu6/A3DOWmhciRDEkFt7r0DDiA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/oznu" + }, + { + "type": "paypal", + "url": "https://paypal.me/oznu" + } + ], + "license": "MIT", + "dependencies": { + "@fastify/helmet": "11.1.1", + "@fastify/multipart": "8.3.0", + "@fastify/static": "7.0.4", + "@homebridge/hap-client": "1.10.2", + "@homebridge/node-pty-prebuilt-multiarch": "0.11.14", + "@nestjs/axios": "3.0.3", + "@nestjs/common": "10.4.1", + "@nestjs/core": "10.4.1", + "@nestjs/jwt": "10.2.0", + "@nestjs/passport": "10.0.3", + "@nestjs/platform-fastify": "10.4.1", + "@nestjs/platform-socket.io": "10.4.1", + "@nestjs/swagger": "7.4.0", + "@nestjs/websockets": "10.4.1", + "axios": "1.7.7", + "bash-color": "0.0.4", + "buffer-shims": "1.0.0", + "class-transformer": "0.5.1", + "class-validator": "0.14.1", + "commander": "12.1.0", + "dayjs": "1.11.13", + "fastify": "4.28.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.2.2", + "rxjs": "7.8.1", + "semver": "7.6.3", + "systeminformation": "5.23.5", + "tail": "2.2.6", + "tar": "6.2.1", + "tcp-port-used": "1.0.2", + "unzipper": "0.12.3" + }, + "bin": { + "hb-service": "dist/bin/hb-service.js", + "homebridge-config-ui-x": "dist/bin/standalone.js" + }, + "engines": { + "homebridge": "^1.8.0 || ^2.0.0-beta.0", + "node": "^18 || ^20 || ^22" + } + }, + "node_modules/homebridge-lib": { + "version": "7.0.8", + "resolved": "https://registry.npmjs.org/homebridge-lib/-/homebridge-lib-7.0.8.tgz", + "integrity": "sha512-Q0eA6HnDIT6M1zcjhPWmmoKAN+q9laEdPIMLvl8sDYhVoJvjyh/KgsXScTANZ2hd20bZFaa6lcqrCJ+oWVmlZQ==", + "license": "Apache-2.0", + "dependencies": { + "@homebridge/plugin-ui-utils": "~1.0.3", + "hb-lib-tools": "~2.0.5" + }, + "bin": { + "hap": "cli/hap.js", + "json": "cli/json.js", + "sysinfo": "cli/sysinfo.js", + "upnp": "cli/upnp.js" }, "engines": { - "node": ">= 6" + "homebridge": "^1.8.4||^2.0.0-beta", + "node": "20.17.0||^20||^18" } }, - "node_modules/form-data-encoder": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-2.1.4.tgz", - "integrity": "sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw==", + "node_modules/hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14.17" - } + "license": "ISC" }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, + "node_modules/html-void-elements": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", + "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==", "dev": true, "license": "MIT", - "engines": { - "node": ">= 0.6" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/fp-and-or": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/fp-and-or/-/fp-and-or-0.1.4.tgz", - "integrity": "sha512-+yRYRhpnFPWXSly/6V4Lw9IfOV26uu30kynGJ03PW+MnjOEQe45RZ141QcS0aJehYBYA50GfCDnsRbFJdhssRw==", + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", "dev": true, - "license": "ISC", + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, "engines": { - "node": ">=10" + "node": ">= 0.8" } }, - "node_modules/from": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz", - "integrity": "sha512-twe20eF1OxVxp/ML/kq2p1uc6KvFK/+vs8WjEbeKmV2He22MKm7YF2ANIt+EOqhJ5L3K/SuuPhk0hWQDjOM23g==", - "dev": true, - "license": "MIT" - }, - "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, - "license": "MIT" - }, - "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, + "node_modules/https-proxy-agent": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", + "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", "license": "MIT", "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" + "agent-base": "^7.0.2", + "debug": "4" }, "engines": { - "node": ">=14.14" + "node": ">= 14" } }, - "node_modules/fs-minipass": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.3.tgz", - "integrity": "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==", + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", "dev": true, - "license": "ISC", - "dependencies": { - "minipass": "^7.0.3" - }, + "license": "Apache-2.0", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": ">=10.17.0" } }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "license": "ISC" + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true, - "hasInstallScript": true, "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + "node": ">= 4" } }, - "node_modules/fstream": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz", - "integrity": "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==", - "deprecated": "This package is no longer supported.", + "node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", "dev": true, - "license": "ISC", - "dependencies": { - "graceful-fs": "^4.1.2", - "inherits": "~2.0.0", - "mkdirp": ">=0.5 0", - "rimraf": "2" - }, - "engines": { - "node": ">=0.6" - } + "license": "ISC" }, - "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==", - "deprecated": "Glob versions prior to v9 are no longer supported", + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", "dev": true, - "license": "ISC", + "license": "MIT", "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" + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" }, "engines": { - "node": "*" + "node": ">=6" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "url": "https://github.com/sponsors/sindresorhus" } }, - "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==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", + "node_modules/import-local": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", + "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "glob": "^7.1.3" + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" }, "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", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "license": "MIT", + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/functions-have-names": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", - "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": ">=0.8.19" } }, - "node_modules/futoin-hkdf": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/futoin-hkdf/-/futoin-hkdf-1.5.3.tgz", - "integrity": "sha512-SewY5KdMpaoCeh7jachEWFsh1nNlaDjNHZXWqL5IGwtpEYHTgkr2+AMCgNwKWkcc0wpSYrZfR7he4WdmHFtDxQ==", + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "engines": { "node": ">=8" } }, - "node_modules/gauge": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz", - "integrity": "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==", - "deprecated": "This package is no longer supported.", + "node_modules/inflection": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/inflection/-/inflection-3.0.0.tgz", + "integrity": "sha512-1zEJU1l19SgJlmwqsEyFTbScw/tkMHFenUo//Y0i+XEP83gDFdMvPizAD/WGcE+l1ku12PcTVHQhO6g5E0UCMw==", "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", "license": "ISC", "dependencies": { - "aproba": "^1.0.3 || ^2.0.0", - "color-support": "^1.1.3", - "console-control-strings": "^1.1.0", - "has-unicode": "^2.0.1", - "signal-exit": "^3.0.7", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1", - "wide-align": "^1.1.5" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "once": "^1.3.0", + "wrappy": "1" } }, - "node_modules/gauge/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" }, - "node_modules/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==", + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", "dev": true, "license": "ISC" }, - "node_modules/gauge/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, + "node_modules/internal-slot": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", + "integrity": "sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==", "license": "MIT", "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" + "es-errors": "^1.3.0", + "hasown": "^2.0.0", + "side-channel": "^1.0.4" }, "engines": { - "node": ">=8" + "node": ">= 0.4" } }, - "node_modules/gaxios": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.7.0.tgz", - "integrity": "sha512-DSrkyMTfAnAm4ks9Go20QGOcXEyW/NmZhvTYBU2rb4afBB393WIMQPWPEDMl/k8xqiNN9HYq2zao3oWXsdl2Tg==", - "license": "Apache-2.0", - "dependencies": { - "extend": "^3.0.2", - "https-proxy-agent": "^7.0.1", - "is-stream": "^2.0.0", - "node-fetch": "^2.6.9", - "uuid": "^10.0.0" - }, + "node_modules/interpret": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", + "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "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, + "license": "MIT", "engines": { - "node": ">=14" + "node": ">=8" } }, - "node_modules/gcp-metadata": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.1.0.tgz", - "integrity": "sha512-Jh/AIwwgaxan+7ZUUmRLCjtchyDiqh4KjBJ5tW3plBZb5iL/BPcso8A5DlzeD9qlw0duCamnNdpFjxwaT0KyKg==", - "license": "Apache-2.0", - "dependencies": { - "gaxios": "^6.0.0", - "json-bigint": "^1.0.0" - }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=14" + "node": ">= 0.10" } }, - "node_modules/get-intrinsic": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", - "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "node_modules/is-arguments": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", + "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", "license": "MIT", "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" }, "engines": { "node": ">= 0.4" @@ -5125,341 +8623,249 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/get-stdin": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-8.0.0.tgz", - "integrity": "sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg==", - "dev": true, + "node_modules/is-array-buffer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz", + "integrity": "sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==", "license": "MIT", - "engines": { - "node": ">=10" + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.1" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true, - "license": "MIT", "engines": { - "node": ">=10" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, - "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==", + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", "dev": true, "license": "MIT" }, - "node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", - "dev": true, - "license": "ISC", + "node_modules/is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "license": "MIT", "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" + "has-bigints": "^1.0.1" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "is-glob": "^4.0.3" + "binary-extensions": "^2.0.0" }, "engines": { - "node": ">=10.13.0" + "node": ">=8" } }, - "node_modules/glob/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, + "node_modules/is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/glob/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/global-dirs": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.1.tgz", - "integrity": "sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==", + "node_modules/is-builtin-module": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.2.1.tgz", + "integrity": "sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==", "dev": true, "license": "MIT", "dependencies": { - "ini": "2.0.0" + "builtin-modules": "^3.3.0" }, "engines": { - "node": ">=10" + "node": ">=6" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/global-dirs/node_modules/ini": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz", - "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/globals": { - "version": "15.8.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-15.8.0.tgz", - "integrity": "sha512-VZAJ4cewHTExBWDHR6yptdIBlx9YSSZuwojj9Nt5mBRXQzrKakDsVKQ1J63sklLvzAJm0X5+RpO4i3Y2hcOnFw==", - "dev": true, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", "license": "MIT", "engines": { - "node": ">=18" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "node_modules/is-core-module": { + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", + "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==", "dev": true, "license": "MIT", "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" + "hasown": "^2.0.2" }, "engines": { - "node": ">=10" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/google-auth-library": { - "version": "9.11.0", - "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-9.11.0.tgz", - "integrity": "sha512-epX3ww/mNnhl6tL45EQ/oixsY8JLEgUFoT4A5E/5iAR4esld9Kqv6IJGk7EmGuOgDvaarwF95hU2+v7Irql9lw==", - "license": "Apache-2.0", + "node_modules/is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "license": "MIT", "dependencies": { - "base64-js": "^1.3.0", - "ecdsa-sig-formatter": "^1.0.11", - "gaxios": "^6.1.1", - "gcp-metadata": "^6.1.0", - "gtoken": "^7.0.0", - "jws": "^4.0.0" + "has-tostringtag": "^1.0.0" }, "engines": { - "node": ">=14" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/googleapis": { - "version": "140.0.1", - "resolved": "https://registry.npmjs.org/googleapis/-/googleapis-140.0.1.tgz", - "integrity": "sha512-ZGvBX4mQcFXO9ACnVNg6Aqy3KtBPB5zTuue43YVLxwn8HSv8jB7w+uDKoIPSoWuxGROgnj2kbng6acXncOQRNA==", - "license": "Apache-2.0", - "dependencies": { - "google-auth-library": "^9.0.0", - "googleapis-common": "^7.0.0" - }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=14.0.0" + "node": ">=0.10.0" } }, - "node_modules/googleapis-common": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/googleapis-common/-/googleapis-common-7.2.0.tgz", - "integrity": "sha512-/fhDZEJZvOV3X5jmD+fKxMqma5q2Q9nZNSF3kn1F18tpxmA86BcTxAGBQdM0N89Z3bEaIs+HVznSmFJEAmMTjA==", - "license": "Apache-2.0", - "dependencies": { - "extend": "^3.0.2", - "gaxios": "^6.0.3", - "google-auth-library": "^9.7.0", - "qs": "^6.7.0", - "url-template": "^2.0.8", - "uuid": "^9.0.0" - }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=14.0.0" + "node": ">=8" } }, - "node_modules/googleapis-common/node_modules/uuid": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", - "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], + "node_modules/is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true, "license": "MIT", - "bin": { - "uuid": "dist/bin/uuid" + "engines": { + "node": ">=6" } }, - "node_modules/gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, "license": "MIT", "dependencies": { - "get-intrinsic": "^1.1.3" + "is-extglob": "^2.1.1" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": ">=0.10.0" } }, - "node_modules/got": { - "version": "12.6.1", - "resolved": "https://registry.npmjs.org/got/-/got-12.6.1.tgz", - "integrity": "sha512-mThBblvlAF1d4O5oqyvN+ZxLAYwIJK7bpMxgYqPD9okW0C3qm5FFn7k811QrcuEBwaogR3ngOFoCfs6mRv7teQ==", + "node_modules/is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", "dev": true, "license": "MIT", - "dependencies": { - "@sindresorhus/is": "^5.2.0", - "@szmarczak/http-timer": "^5.0.1", - "cacheable-lookup": "^7.0.0", - "cacheable-request": "^10.2.8", - "decompress-response": "^6.0.0", - "form-data-encoder": "^2.1.2", - "get-stream": "^6.0.1", - "http2-wrapper": "^2.1.10", - "lowercase-keys": "^3.0.0", - "p-cancelable": "^3.0.0", - "responselike": "^3.0.0" - }, "engines": { - "node": ">=14.16" + "node": ">=8" + } + }, + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sindresorhus/got?sponsor=1" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/graphemer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true, - "license": "MIT" - }, - "node_modules/gtoken": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-7.1.0.tgz", - "integrity": "sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==", "license": "MIT", - "dependencies": { - "gaxios": "^6.0.0", - "jws": "^4.0.0" - }, "engines": { - "node": ">=14.0.0" + "node": ">=0.12.0" } }, - "node_modules/hap-nodejs": { - "version": "0.12.2", - "resolved": "https://registry.npmjs.org/hap-nodejs/-/hap-nodejs-0.12.2.tgz", - "integrity": "sha512-EAgcBOxxMaWKkRfuGc8FBVJO5/OCFM2jMt5+szkqazWLKANkRLrvf/GtQnPsl6GUuLiWYddLZ/zwasayU8ljQQ==", - "dev": true, - "license": "Apache-2.0", + "node_modules/is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "license": "MIT", "dependencies": { - "@homebridge/ciao": "^1.2.0", - "@homebridge/dbus-native": "^0.6.0", - "bonjour-hap": "^3.7.2", - "debug": "^4.3.5", - "fast-srp-hap": "^2.0.4", - "futoin-hkdf": "^1.5.3", - "node-persist": "^0.0.12", - "source-map-support": "^0.5.21", - "tslib": "^2.6.2", - "tweetnacl": "^1.0.3" + "has-tostringtag": "^1.0.0" }, "engines": { - "node": "^18 || ^20" - } - }, - "node_modules/has-bigints": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", - "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", - "license": "MIT", + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", "dev": true, "license": "MIT", "engines": { "node": ">=8" } }, - "node_modules/has-property-descriptors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", - "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "node_modules/is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", "license": "MIT", "dependencies": { - "es-define-property": "^1.0.0" + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has-proto": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", - "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -5468,11 +8874,14 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has-symbols": { + "node_modules/is-shared-array-buffer": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz", + "integrity": "sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==", "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7" + }, "engines": { "node": ">= 0.4" }, @@ -5480,13 +8889,25 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has-tostringtag": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", - "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", "license": "MIT", "dependencies": { - "has-symbols": "^1.0.3" + "has-tostringtag": "^1.0.0" }, "engines": { "node": ">= 0.4" @@ -5495,963 +8916,1354 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has-unicode": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", - "dev": true, - "license": "ISC" + "node_modules/is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, - "node_modules/has-yarn": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-yarn/-/has-yarn-3.0.0.tgz", - "integrity": "sha512-IrsVwUHhEULx3R8f/aA8AHuEzAorplsab/v8HBzEiIukwq5i/EC+xmOW+HfP1OaDP+2JkgT1yILHN2O3UFIbcA==", + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", "dev": true, "license": "MIT", "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/hasown": { + "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, + "license": "MIT" + }, + "node_modules/is-weakmap": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/hb-lib-tools": { + "node_modules/is-weakset": { "version": "2.0.3", - "resolved": "https://registry.npmjs.org/hb-lib-tools/-/hb-lib-tools-2.0.3.tgz", - "integrity": "sha512-9LOqDbaiuLAtbMki209OiLWOOs3HjajBfMDH3OkDtSM8l3FuH8WsF06xfWbTigL6x7pQoQ4nfE2fEQHcVT7RHg==", - "license": "Apache-2.0", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.3.tgz", + "integrity": "sha512-LvIm3/KWzS9oRFHugab7d+M/GcBXuXX5xZkzPmN+NxihdQlZUQ4dWuSV1xR/sq6upL1TJEDrfBgRepHFdBtSNQ==", + "license": "MIT", "dependencies": { - "bonjour-hap": "^3.7.2", - "chalk": "^5.3.0", - "semver": "^7.6.2" - }, - "bin": { - "hap": "cli/hap.js", - "json": "cli/json.js", - "sysinfo": "cli/sysinfo.js", - "upnp": "cli/upnp.js" + "call-bind": "^1.0.7", + "get-intrinsic": "^1.2.4" }, "engines": { - "node": "20.15.0||^20||^18" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/hb-lib-tools/node_modules/chalk": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", - "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "node_modules/is2": { + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/is2/-/is2-2.0.9.tgz", + "integrity": "sha512-rZkHeBn9Zzq52sd9IUIV3a5mfwBY+o2HePMh0wkGBM4z4qjvy2GwVxQ6nNXSfw6MmVP6gf1QIlWjiOavhM3x5g==", + "dev": true, "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "ip-regex": "^4.1.0", + "is-url": "^1.2.4" + }, "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" + "node": ">=v0.10.0" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", + "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "engines": { + "node": ">=10" } }, - "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==", + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", "dev": true, - "license": "MIT", + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, "engines": { - "node": ">=16.0.0" + "node": ">=10" } }, - "node_modules/help-me": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/help-me/-/help-me-3.0.0.tgz", - "integrity": "sha512-hx73jClhyk910sidBB7ERlnhMlFsJJIBqSVMFDwPN8o2v9nmp5KgLq1Xz1Bf1fCMMZ6mPrX159iG0VLy/fPMtQ==", - "license": "MIT", + "node_modules/istanbul-lib-source-maps": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", + "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", + "dev": true, + "license": "BSD-3-Clause", "dependencies": { - "glob": "^7.1.6", - "readable-stream": "^3.6.0" + "@jridgewell/trace-mapping": "^0.3.23", + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0" + }, + "engines": { + "node": ">=10" } }, - "node_modules/help-me/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "license": "ISC", + "node_modules/istanbul-reports": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", + "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", + "dev": true, + "license": "BSD-3-Clause", "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" + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" }, "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">=8" } }, - "node_modules/hexy": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/hexy/-/hexy-0.3.5.tgz", - "integrity": "sha512-UCP7TIZPXz5kxYJnNOym+9xaenxCLor/JyhKieo8y8/bJWunGh9xbhy3YrgYJUQ87WwfXGm05X330DszOfINZw==", + "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, - "license": "MIT", - "bin": { - "hexy": "bin/hexy_cmd.js" - }, + "license": "ISC", "engines": { - "node": ">=10.4" + "node": ">=6" } }, - "node_modules/homebridge": { - "version": "1.8.4", - "resolved": "https://registry.npmjs.org/homebridge/-/homebridge-1.8.4.tgz", - "integrity": "sha512-Wr5QdI/OCpxCM3rdXyuPUxNlbuwiFngnuj+ZsrCbg5cMwKRMfN+RRGiW2LUSY2BDGQC0cSUt/9TF6tIlov16Qw==", + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", "dev": true, - "license": "Apache-2.0", + "license": "BlueOak-1.0.0", "dependencies": { - "chalk": "4.1.2", - "commander": "12.1.0", - "fs-extra": "11.2.0", - "hap-nodejs": "0.12.2", - "qrcode-terminal": "0.12.0", - "semver": "7.6.3", - "source-map-support": "0.5.21" + "@isaacs/cliui": "^8.0.2" }, - "bin": { - "homebridge": "bin/homebridge" + "funding": { + "url": "https://github.com/sponsors/isaacs" }, - "engines": { - "node": "^18.15.0 || ^20.7.0" + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" } }, - "node_modules/homebridge-config-ui-x": { - "version": "4.56.4", - "resolved": "https://registry.npmjs.org/homebridge-config-ui-x/-/homebridge-config-ui-x-4.56.4.tgz", - "integrity": "sha512-zZQ505E8HtNRaC0IQAkbDIEKp21f3lZJP2f9ijD3zOZDHO9GS0Rp3U9iR5BRGY2Vt1JUZMdIHZmHcrVcxE3T9Q==", + "node_modules/jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", + "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/oznu" - }, - { - "type": "paypal", - "url": "https://paypal.me/oznu" - } - ], "license": "MIT", "dependencies": { - "@fastify/helmet": "11.1.1", - "@fastify/multipart": "8.2.0", - "@fastify/static": "7.0.2", - "@homebridge/hap-client": "1.10.0", - "@homebridge/node-pty-prebuilt-multiarch": "0.11.12", - "@nestjs/axios": "3.0.2", - "@nestjs/common": "10.3.7", - "@nestjs/core": "10.3.7", - "@nestjs/jwt": "10.2.0", - "@nestjs/passport": "10.0.3", - "@nestjs/platform-fastify": "10.3.7", - "@nestjs/platform-socket.io": "10.3.7", - "@nestjs/swagger": "7.3.1", - "@nestjs/websockets": "10.3.7", - "axios": "1.6.8", - "bash-color": "0.0.4", - "buffer-shims": "1.0.0", - "class-transformer": "0.5.1", - "class-validator": "0.14.1", - "commander": "12.0.0", - "dayjs": "1.11.10", - "fastify": "4.26.2", - "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.2.2", - "rxjs": "7.8.1", - "semver": "7.6.0", - "systeminformation": "5.22.7", - "tail": "2.2.6", - "tar": "6.2.1", - "tcp-port-used": "1.0.2", - "unzipper": "0.10.14" + "@jest/core": "^29.7.0", + "@jest/types": "^29.6.3", + "import-local": "^3.0.2", + "jest-cli": "^29.7.0" }, "bin": { - "hb-service": "dist/bin/hb-service.js", - "homebridge-config-ui-x": "dist/bin/standalone.js" + "jest": "bin/jest.js" }, "engines": { - "homebridge": "^1.6.0", - "node": "^18 || ^20" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } } }, - "node_modules/homebridge-config-ui-x/node_modules/commander": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-12.0.0.tgz", - "integrity": "sha512-MwVNWlYjDTtOjX5PiD7o5pK0UrFU/OYgcJfjjK4RaHZETNtjJqrZa9Y9ds88+A+f+d5lv+561eZ+yCKoS3gbAA==", + "node_modules/jest-changed-files": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", + "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", "dev": true, "license": "MIT", + "dependencies": { + "execa": "^5.0.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0" + }, "engines": { - "node": ">=18" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/homebridge-config-ui-x/node_modules/semver": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", - "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "node_modules/jest-circus": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", + "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^1.0.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^29.7.0", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0", + "pretty-format": "^29.7.0", + "pure-rand": "^6.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" }, "engines": { - "node": ">=10" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/homebridge-lib": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/homebridge-lib/-/homebridge-lib-7.0.4.tgz", - "integrity": "sha512-gWocWr9grLyKGRIvPrJmh53NW8DMh3oxtOdNs12zRSoDYnQARgQ0C0hkPakz0hCfID052IbET41BWDR9Q6BfGg==", - "license": "Apache-2.0", + "node_modules/jest-cli": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", + "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", + "dev": true, + "license": "MIT", "dependencies": { - "@homebridge/plugin-ui-utils": "~1.0.3", - "hb-lib-tools": "~2.0.3" + "@jest/core": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "create-jest": "^29.7.0", + "exit": "^0.1.2", + "import-local": "^3.0.2", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "yargs": "^17.3.1" }, "bin": { - "hap": "cli/hap.js", - "json": "cli/json.js", - "sysinfo": "cli/sysinfo.js", - "upnp": "cli/upnp.js" + "jest": "bin/jest.js" }, "engines": { - "homebridge": "^1.8.3", - "node": "20.15.0||^20||^18" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } } }, - "node_modules/hosted-git-info": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-5.2.1.tgz", - "integrity": "sha512-xIcQYMnhcx2Nr4JTjsFmwwnr9vldugPy9uVm0o87bjqqWMv9GaqsTeT+i99wTl0mk1uLxJtHxLb8kymqTENQsw==", + "node_modules/jest-config": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", + "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "lru-cache": "^7.5.1" + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-jest": "^29.7.0", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@types/node": "*", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "ts-node": { + "optional": true + } } }, - "node_modules/hosted-git-info/node_modules/lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "node_modules/jest-config/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, - "license": "ISC", - "engines": { - "node": ">=12" + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, - "node_modules/http-cache-semantics": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", - "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", - "dev": true, - "license": "BSD-2-Clause" - }, - "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==", + "node_modules/jest-config/node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], "license": "MIT", - "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - }, "engines": { - "node": ">= 0.8" + "node": ">=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", - "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "node_modules/jest-config/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "@tootallnate/once": "2", - "agent-base": "6", - "debug": "4" + "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": ">= 6" + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/http-proxy-agent/node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "node_modules/jest-config/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "debug": "4" + "brace-expansion": "^1.1.7" }, "engines": { - "node": ">= 6.0.0" + "node": "*" } }, - "node_modules/http2-wrapper": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-2.2.1.tgz", - "integrity": "sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ==", + "node_modules/jest-diff": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", "dev": true, "license": "MIT", "dependencies": { - "quick-lru": "^5.1.1", - "resolve-alpn": "^1.2.0" + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" }, "engines": { - "node": ">=10.19.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/https-proxy-agent": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", - "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", + "node_modules/jest-docblock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", + "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", + "dev": true, "license": "MIT", "dependencies": { - "agent-base": "^7.0.2", - "debug": "4" + "detect-newline": "^3.0.0" }, "engines": { - "node": ">= 14" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/humanize-ms": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", - "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", + "node_modules/jest-each": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", + "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", "dev": true, "license": "MIT", "dependencies": { - "ms": "^2.0.0" + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "jest-util": "^29.7.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "node_modules/jest-environment-node": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", + "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", + "dev": true, "license": "MIT", - "optional": true, "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "BSD-3-Clause" + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } }, - "node_modules/ignore": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", - "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", + "node_modules/jest-get-type": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", "dev": true, "license": "MIT", "engines": { - "node": ">= 4" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/ignore-by-default": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", - "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + "node_modules/jest-haste-map": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", + "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", "dev": true, - "license": "ISC" + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } }, - "node_modules/ignore-walk": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-6.0.5.tgz", - "integrity": "sha512-VuuG0wCnjhnylG1ABXT3dAuIpTNDs/G8jlpmwXY03fXoXy/8ZK8/T+hMzt8L4WnrLCJgdybqgPagnF/f97cg3A==", + "node_modules/jest-leak-detector": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", + "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "minimatch": "^9.0.0" + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/ignore-walk/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "node_modules/jest-matcher-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", + "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0" + "chalk": "^4.0.0", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/ignore-walk/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "node_modules/jest-message-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "brace-expansion": "^2.0.1" + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" }, "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "node_modules/jest-mock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", + "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", "dev": true, "license": "MIT", "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-util": "^29.7.0" }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "dev": true, + "license": "MIT", "engines": { "node": ">=6" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } } }, - "node_modules/import-lazy": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-4.0.0.tgz", - "integrity": "sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==", + "node_modules/jest-regex-util": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", + "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", "dev": true, "license": "MIT", "engines": { - "node": ">=8" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "node_modules/jest-resolve": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", + "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", "dev": true, "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "resolve": "^1.20.0", + "resolve.exports": "^2.0.0", + "slash": "^3.0.0" + }, "engines": { - "node": ">=0.8.19" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "node_modules/jest-resolve-dependencies": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", + "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", "dev": true, "license": "MIT", + "dependencies": { + "jest-regex-util": "^29.6.3", + "jest-snapshot": "^29.7.0" + }, "engines": { - "node": ">=8" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/infer-owner": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", - "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", + "node_modules/jest-runner": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", + "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", "dev": true, - "license": "ISC" + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/environment": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "graceful-fs": "^4.2.9", + "jest-docblock": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-leak-detector": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-resolve": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-util": "^29.7.0", + "jest-watcher": "^29.7.0", + "jest-worker": "^29.7.0", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } }, - "node_modules/inflection": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/inflection/-/inflection-3.0.0.tgz", - "integrity": "sha512-1zEJU1l19SgJlmwqsEyFTbScw/tkMHFenUo//Y0i+XEP83gDFdMvPizAD/WGcE+l1ku12PcTVHQhO6g5E0UCMw==", + "node_modules/jest-runner/node_modules/source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/jest-runtime": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", + "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", "dev": true, "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/globals": "^29.7.0", + "@jest/source-map": "^29.6.3", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + }, "engines": { - "node": ">=18.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "node_modules/jest-runtime/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/jest-runtime/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, "license": "ISC", "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", "once": "^1.3.0", - "wrappy": "1" + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "license": "ISC" - }, - "node_modules/ini": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.3.tgz", - "integrity": "sha512-X7rqawQBvfdjS10YU1y1YVreA3SsLrW9dX2CewP2EbBJM4ypVNLDkO5y04gejPwKIY9lR+7r9gn3rFPt/kmWFg==", + "node_modules/jest-runtime/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "*" } }, - "node_modules/internal-slot": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", - "integrity": "sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==", + "node_modules/jest-snapshot": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", + "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", + "dev": true, "license": "MIT", "dependencies": { - "es-errors": "^1.3.0", - "hasown": "^2.0.0", - "side-channel": "^1.0.4" + "@babel/core": "^7.11.6", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-jsx": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "natural-compare": "^1.4.0", + "pretty-format": "^29.7.0", + "semver": "^7.5.3" }, "engines": { - "node": ">= 0.4" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/ip-address": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", - "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", + "node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", "dev": true, "license": "MIT", "dependencies": { - "jsbn": "1.1.0", - "sprintf-js": "^1.1.3" + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" }, "engines": { - "node": ">= 12" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "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==", + "node_modules/jest-util/node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], "license": "MIT", "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==", + "node_modules/jest-util/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "dev": true, "license": "MIT", "engines": { - "node": ">= 0.10" + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/is-arguments": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", - "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", + "node_modules/jest-validate": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", + "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", + "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" + "@jest/types": "^29.6.3", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "leven": "^3.1.0", + "pretty-format": "^29.7.0" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/is-array-buffer": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz", - "integrity": "sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==", + "node_modules/jest-validate/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.1" - }, "engines": { - "node": ">= 0.4" + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-bigint": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", - "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "node_modules/jest-watcher": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", + "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", + "dev": true, "license": "MIT", "dependencies": { - "has-bigints": "^1.0.1" + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "jest-util": "^29.7.0", + "string-length": "^4.0.1" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "node_modules/jest-worker": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", "dev": true, "license": "MIT", "dependencies": { - "binary-extensions": "^2.0.0" + "@types/node": "*", + "jest-util": "^29.7.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" }, "engines": { - "node": ">=8" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/is-boolean-object": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", - "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" + "has-flag": "^4.0.0" }, "engines": { - "node": ">= 0.4" + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/is-callable": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", - "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "node_modules/js-sdsl": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.3.0.tgz", + "integrity": "sha512-mifzlm2+5nZ+lEcLJMoBK0/IH/bDg8XnJfd/Wq6IP+xoCjLZsTOnV2QpxlVbX9bMnkl5PdEjNtBJ9Cj1NjifhQ==", "license": "MIT", - "engines": { - "node": ">= 0.4" - }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "type": "opencollective", + "url": "https://opencollective.com/js-sdsl" } }, - "node_modules/is-ci": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.1.tgz", - "integrity": "sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==", + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "dev": true, "license": "MIT", "dependencies": { - "ci-info": "^3.2.0" + "argparse": "^2.0.1" }, "bin": { - "is-ci": "bin.js" + "js-yaml": "bin/js-yaml.js" } }, - "node_modules/is-core-module": { - "version": "2.15.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.0.tgz", - "integrity": "sha512-Dd+Lb2/zvk9SKy1TGCt1wFJFo/MWBPMX5x7KcvLajWTGuomczdQX61PvY5yK6SVACwpoexWo81IfFyoKY2QnTA==", + "node_modules/jsdoc-type-pratt-parser": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-4.1.0.tgz", + "integrity": "sha512-Hicd6JK5Njt2QB6XYFS7ok9e37O8AYk3jTcppG4YVQnYjOemymvTcmc7OWsmq/Qqj5TdRFO5/x/tIPmBeRtGHg==", "dev": true, "license": "MIT", - "dependencies": { - "hasown": "^2.0.2" - }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=12.0.0" } }, - "node_modules/is-date-object": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", - "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "node_modules/jsesc": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", + "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", + "dev": true, "license": "MIT", - "dependencies": { - "has-tostringtag": "^1.0.0" + "bin": { + "jsesc": "bin/jsesc" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=6" } }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, + "node_modules/json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", "license": "MIT", - "engines": { - "node": ">=0.10.0" + "dependencies": { + "bignumber.js": "^9.0.0" } }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-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, "license": "MIT", - "engines": { - "node": ">=8" + "dependencies": { + "fast-deep-equal": "^3.1.3" } }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true, "license": "MIT", - "dependencies": { - "is-extglob": "^2.1.1" + "bin": { + "json5": "lib/cli.js" }, "engines": { - "node": ">=0.10.0" + "node": ">=6" } }, - "node_modules/is-installed-globally": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz", - "integrity": "sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==", + "node_modules/jsonc-eslint-parser": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/jsonc-eslint-parser/-/jsonc-eslint-parser-2.4.0.tgz", + "integrity": "sha512-WYDyuc/uFcGp6YtM2H0uKmUwieOuzeE/5YocFJLnLfclZ4inf3mRn8ZVy1s7Hxji7Jxm6Ss8gqpexD/GlKoGgg==", "dev": true, "license": "MIT", "dependencies": { - "global-dirs": "^3.0.0", - "is-path-inside": "^3.0.2" + "acorn": "^8.5.0", + "eslint-visitor-keys": "^3.0.0", + "espree": "^9.0.0", + "semver": "^7.3.5" }, "engines": { - "node": ">=10" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ota-meshi" } }, - "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==", + "node_modules/jsonc-eslint-parser/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "engines": { - "node": ">=8" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/is-lambda": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", - "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==", + "node_modules/jsonc-eslint-parser/node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", "dev": true, - "license": "MIT" - }, - "node_modules/is-map": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", - "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", - "license": "MIT", + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, "engines": { - "node": ">= 0.4" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://opencollective.com/eslint" } }, - "node_modules/is-npm": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-6.0.0.tgz", - "integrity": "sha512-JEjxbSmtPSt1c8XTkVrlujcXdKV1/tvuQ7GwKcAlyiVLeYFQ2VHat8xfrDJsIkhCdF/tZ7CiIR3sy141c6+gPQ==", + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", "dev": true, "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "dependencies": { + "universalify": "^2.0.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "optionalDependencies": { + "graceful-fs": "^4.1.6" } }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "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, "license": "MIT", + "dependencies": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, "engines": { - "node": ">=0.12.0" + "node": ">=12", + "npm": ">=6" } }, - "node_modules/is-number-object": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", - "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "node_modules/jsonwebtoken/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, "license": "MIT", "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" } }, - "node_modules/is-obj": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", - "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "node_modules/jsonwebtoken/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, "license": "MIT", - "engines": { - "node": ">=8" + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" } }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true, + "node_modules/jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", "license": "MIT", - "engines": { - "node": ">=8" + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" } }, - "node_modules/is-regex": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", - "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "node_modules/jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - }, + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true, + "license": "MIT", "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=6" } }, - "node_modules/is-set": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", - "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, "license": "MIT", "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=6" } }, - "node_modules/is-shared-array-buffer": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz", - "integrity": "sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==", + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.7" + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">= 0.8.0" } }, - "node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "node_modules/libphonenumber-js": { + "version": "1.11.8", + "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.11.8.tgz", + "integrity": "sha512-0fv/YKpJBAgXKy0kaS3fnqoUVN8901vUYAKIGD/MWZaDfhJt1nZjPL3ZzdZBt/G8G8Hw2J1xOIrXWdNHFHPAvg==", + "dev": true, + "license": "MIT" + }, + "node_modules/light-my-request": { + "version": "5.13.0", + "resolved": "https://registry.npmjs.org/light-my-request/-/light-my-request-5.13.0.tgz", + "integrity": "sha512-9IjUN9ZyCS9pTG+KqTDEQo68Sui2lHsYBrfMyVUTTZ3XhH8PMZq7xO94Kr+eP9dhi/kcKsx4N41p2IXEBil1pQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "cookie": "^0.6.0", + "process-warning": "^3.0.0", + "set-cookie-parser": "^2.4.1" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", + "dev": true, "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "dependencies": { + "uc.micro": "^2.0.0" } }, - "node_modules/is-string": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", - "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "node_modules/local-pkg": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.5.0.tgz", + "integrity": "sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==", + "dev": true, "license": "MIT", "dependencies": { - "has-tostringtag": "^1.0.0" + "mlly": "^1.4.2", + "pkg-types": "^1.0.3" }, "engines": { - "node": ">= 0.4" + "node": ">=14" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/antfu" } }, - "node_modules/is-symbol": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", - "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, "license": "MIT", "dependencies": { - "has-symbols": "^1.0.2" + "p-locate": "^5.0.0" }, "engines": { - "node": ">= 0.4" + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "dev": true, "license": "MIT" }, - "node_modules/is-unicode-supported": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "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, + "license": "MIT" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "dev": true, + "license": "MIT" + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", "dev": true, "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, "engines": { "node": ">=10" }, @@ -6459,679 +10271,1005 @@ "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==", + "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, "license": "MIT" }, - "node_modules/is-weakmap": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", - "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "node_modules/longest-streak": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", + "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", + "dev": true, "license": "MIT", - "engines": { - "node": ">= 0.4" - }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/is-weakset": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.3.tgz", - "integrity": "sha512-LvIm3/KWzS9oRFHugab7d+M/GcBXuXX5xZkzPmN+NxihdQlZUQ4dWuSV1xR/sq6upL1TJEDrfBgRepHFdBtSNQ==", + "node_modules/loupe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.1.tgz", + "integrity": "sha512-edNu/8D5MKVfGVFRhFf8aAxiTM6Wumfz5XsaatSxlD3w4R1d/WEKUTydCdPGbl9K7QG/Ca3GnDV2sIKIpXRQcw==", + "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", - "get-intrinsic": "^1.2.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "get-func-name": "^2.0.1" } }, - "node_modules/is-yarn-global": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.4.1.tgz", - "integrity": "sha512-/kppl+R+LO5VmhYSEWARUFjodS25D68gvj8W7z0I7OWhUla5xWu8KL6CtB2V0R6yqhnRgbcaREMr4EEM6htLPQ==", + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/lunr": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz", + "integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==", + "dev": true, + "license": "MIT" + }, + "node_modules/luxon": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.5.0.tgz", + "integrity": "sha512-rh+Zjr6DNfUYR3bPwJEnuwDdqMbxZW7LOQfUN4B54+Cl+0o5zaU9RJ6bcidfDtC1cWCZXQ+nvX8bf6bAji37QQ==", "dev": true, "license": "MIT", "engines": { "node": ">=12" } }, - "node_modules/is2": { - "version": "2.0.9", - "resolved": "https://registry.npmjs.org/is2/-/is2-2.0.9.tgz", - "integrity": "sha512-rZkHeBn9Zzq52sd9IUIV3a5mfwBY+o2HePMh0wkGBM4z4qjvy2GwVxQ6nNXSfw6MmVP6gf1QIlWjiOavhM3x5g==", + "node_modules/magic-string": { + "version": "0.30.11", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.11.tgz", + "integrity": "sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==", "dev": true, "license": "MIT", "dependencies": { - "deep-is": "^0.1.3", - "ip-regex": "^4.1.0", - "is-url": "^1.2.4" - }, - "engines": { - "node": ">=v0.10.0" + "@jridgewell/sourcemap-codec": "^1.5.0" } }, - "node_modules/isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "license": "MIT" - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "node_modules/magicast": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.5.tgz", + "integrity": "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==", "dev": true, - "license": "ISC" + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.25.4", + "@babel/types": "^7.25.4", + "source-map-js": "^1.2.0" + } }, - "node_modules/iterare": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/iterare/-/iterare-1.2.1.tgz", - "integrity": "sha512-RKYVTCjAnRthyJes037NX/IiqeidgN1xc3j1RjFfECFp28A1GVwK9nA+i0rJPaHqSZwygLzRnFlzUuHFoWWy+Q==", + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", "dev": true, - "license": "ISC", + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, "engines": { - "node": ">=6" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/jackspeak": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", - "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true, + "license": "ISC" + }, + "node_modules/makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", "dev": true, - "license": "BlueOak-1.0.0", + "license": "BSD-3-Clause", "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" + "tmpl": "1.0.5" } }, - "node_modules/jju": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/jju/-/jju-1.4.0.tgz", - "integrity": "sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==", + "node_modules/map-stream": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.0.7.tgz", + "integrity": "sha512-C0X0KQmGm3N2ftbTGBhSyuydQ+vV1LC3f3zPvT3RXHXNZrvfPZcoXp/N5DOa8vedX/rTMm2CjTtivFg2STJMRQ==", "dev": true, "license": "MIT" }, - "node_modules/js-sdsl": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.3.0.tgz", - "integrity": "sha512-mifzlm2+5nZ+lEcLJMoBK0/IH/bDg8XnJfd/Wq6IP+xoCjLZsTOnV2QpxlVbX9bMnkl5PdEjNtBJ9Cj1NjifhQ==", - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/js-sdsl" - } - }, - "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "node_modules/markdown-it": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz", + "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==", "dev": true, "license": "MIT", "dependencies": { - "argparse": "^2.0.1" + "argparse": "^2.0.1", + "entities": "^4.4.0", + "linkify-it": "^5.0.0", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.1.0" }, "bin": { - "js-yaml": "bin/js-yaml.js" + "markdown-it": "bin/markdown-it.mjs" } }, - "node_modules/jsbn": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", - "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==", + "node_modules/markdown-table": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.3.tgz", + "integrity": "sha512-Z1NL3Tb1M9wH4XESsCDEksWoKTdlUafKc4pt0GRwjUyXaCFZ+dc3g2erqB6zm3szA2IUSi7VnPI+o/9jnxh9hw==", "dev": true, - "license": "MIT" - }, - "node_modules/json-bigint": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", - "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", "license": "MIT", - "dependencies": { - "bignumber.js": "^9.0.0" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/json-buffer": { + "node_modules/mdast-util-find-and-replace": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.1.tgz", + "integrity": "sha512-SG21kZHGC3XRTSUhtofZkBzZTJNM5ecCi0SK2IMKmSXR8vO3peL+kb1O0z7Zl83jKtutG4k5Wv/W7V3/YHvzPA==", "dev": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "escape-string-regexp": "^5.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } }, - "node_modules/json-parse-even-better-errors": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.2.tgz", - "integrity": "sha512-fi0NG4bPjCHunUJffmLd0gxssIgkNmArMvis4iNah6Owg1MCJjWhEcDLmsK6iGkJq3tHwbDkTlce70/tmXN4cQ==", + "node_modules/mdast-util-find-and-replace/node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", "dev": true, "license": "MIT", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/json-parse-helpfulerror": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/json-parse-helpfulerror/-/json-parse-helpfulerror-1.0.3.tgz", - "integrity": "sha512-XgP0FGR77+QhUxjXkwOMkC94k3WtqEBfcnjWqhRd82qTat4SWKRE+9kUnynz/shm3I4ea2+qISvTIeGTNU7kJg==", + "node_modules/mdast-util-from-markdown": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.1.tgz", + "integrity": "sha512-aJEUyzZ6TzlsX2s5B4Of7lN7EQtAxvtradMMglCQDyaTFgse6CmtmdJ15ElnVRlCg1vpNyVtbem0PWzlNieZsA==", "dev": true, "license": "MIT", "dependencies": { - "jju": "^1.1.0" + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark": "^4.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "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==", + "node_modules/mdast-util-gfm": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.0.0.tgz", + "integrity": "sha512-dgQEX5Amaq+DuUqf26jJqSK9qgixgd6rYDHAv4aTBuA92cTknZlKpPfa86Z/s8Dj8xsAQpFfBmPUHWJBWqS4Bw==", "dev": true, "license": "MIT", "dependencies": { - "fast-deep-equal": "^3.1.3" + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-gfm-autolink-literal": "^2.0.0", + "mdast-util-gfm-footnote": "^2.0.0", + "mdast-util-gfm-strikethrough": "^2.0.0", + "mdast-util-gfm-table": "^2.0.0", + "mdast-util-gfm-task-list-item": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "node_modules/mdast-util-gfm-autolink-literal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz", + "integrity": "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "ccount": "^2.0.0", + "devlop": "^1.0.0", + "mdast-util-find-and-replace": "^3.0.0", + "micromark-util-character": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "node_modules/mdast-util-gfm-footnote": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.0.0.tgz", + "integrity": "sha512-5jOT2boTSVkMnQ7LTrd6n/18kqwjmuYqo7JUPe+tRCY6O7dAuTFMtTPauYYrMPpox9hlN0uOx/FL8XvEfG9/mQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "node_modules/mdast-util-gfm-strikethrough": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz", + "integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==", "dev": true, "license": "MIT", - "bin": { - "json5": "lib/cli.js" + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" }, - "engines": { - "node": ">=6" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "node_modules/mdast-util-gfm-table": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz", + "integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==", "dev": true, "license": "MIT", "dependencies": { - "universalify": "^2.0.0" + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "markdown-table": "^3.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/jsonlines": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsonlines/-/jsonlines-0.1.1.tgz", - "integrity": "sha512-ekDrAGso79Cvf+dtm+mL8OBI2bmAOt3gssYs833De/C9NmIpWDWyUO4zPgB5x2/OhY366dkhgfPMYfwZF7yOZA==", + "node_modules/mdast-util-gfm-task-list-item": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz", + "integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } }, - "node_modules/jsonparse": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", - "integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==", + "node_modules/mdast-util-phrasing": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", + "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", "dev": true, - "engines": [ - "node >= 0.2.0" - ], - "license": "MIT" + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } }, - "node_modules/jsonwebtoken": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", - "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", + "node_modules/mdast-util-to-hast": { + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz", + "integrity": "sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==", "dev": true, "license": "MIT", "dependencies": { - "jws": "^3.2.2", - "lodash.includes": "^4.3.0", - "lodash.isboolean": "^3.0.3", - "lodash.isinteger": "^4.0.4", - "lodash.isnumber": "^3.0.3", - "lodash.isplainobject": "^4.0.6", - "lodash.isstring": "^4.0.1", - "lodash.once": "^4.0.0", - "ms": "^2.1.1", - "semver": "^7.5.4" + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" }, - "engines": { - "node": ">=12", - "npm": ">=6" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/jsonwebtoken/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==", + "node_modules/mdast-util-to-markdown": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.0.tgz", + "integrity": "sha512-SR2VnIEdVNCJbP6y7kVTJgPLifdr8WEU440fQec7qHoHOUz/oJ2jmNRqdDQ3rbiStOXb2mCDGTuwsK5OPUgYlQ==", "dev": true, "license": "MIT", "dependencies": { - "buffer-equal-constant-time": "1.0.1", - "ecdsa-sig-formatter": "1.0.11", - "safe-buffer": "^5.0.1" + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "longest-streak": "^3.0.0", + "mdast-util-phrasing": "^4.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark-util-decode-string": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/jsonwebtoken/node_modules/jws": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", - "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "node_modules/mdast-util-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", "dev": true, "license": "MIT", "dependencies": { - "jwa": "^1.4.1", - "safe-buffer": "^5.0.1" + "@types/mdast": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/jwa": { + "node_modules/mdurl": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", - "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", + "dev": true, + "license": "MIT" + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, "license": "MIT", - "dependencies": { - "buffer-equal-constant-time": "1.0.1", - "ecdsa-sig-formatter": "1.0.11", - "safe-buffer": "^5.0.1" + "engines": { + "node": ">= 8" } }, - "node_modules/jws": { + "node_modules/micromark": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", - "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.0.tgz", + "integrity": "sha512-o/sd0nMof8kYff+TqcDx3VSrgBTcZpSvYcAHIfHhv5VAuNmisCxjhx6YmxS8PFEpb9z5WKWKPdzf0jM23ro3RQ==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], "license": "MIT", "dependencies": { - "jwa": "^2.0.0", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/keyv": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-core-commonmark": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.1.tgz", + "integrity": "sha512-CUQyKr1e///ZODyD1U3xit6zXwy1a8q2a1S1HKtIlmgvurrEpaw/Y9y6KSIbF8P59cn/NjzHyO+Q2fAyYLQrAA==", "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], "license": "MIT", "dependencies": { - "json-buffer": "3.0.1" - } - }, - "node_modules/kleur": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", - "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-gfm": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz", + "integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==", "dev": true, "license": "MIT", - "engines": { - "node": ">=6" + "dependencies": { + "micromark-extension-gfm-autolink-literal": "^2.0.0", + "micromark-extension-gfm-footnote": "^2.0.0", + "micromark-extension-gfm-strikethrough": "^2.0.0", + "micromark-extension-gfm-table": "^2.0.0", + "micromark-extension-gfm-tagfilter": "^2.0.0", + "micromark-extension-gfm-task-list-item": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/latest-version": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-7.0.0.tgz", - "integrity": "sha512-KvNT4XqAMzdcL6ka6Tl3i2lYeFDgXNCuIX+xNx6ZMVR1dFq+idXd9FLKNMOIx0t9mJ9/HudyX4oZWXZQ0UJHeg==", + "node_modules/micromark-extension-gfm-autolink-literal": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz", + "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==", "dev": true, "license": "MIT", "dependencies": { - "package-json": "^8.1.0" - }, - "engines": { - "node": ">=14.16" + "micromark-util-character": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/leven": { + "node_modules/micromark-extension-gfm-footnote": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-2.1.0.tgz", - "integrity": "sha512-nvVPLpIHUxCUoRLrFqTgSxXJ614d8AgQoWl7zPe/2VadE8+1dpU3LBhowRuBAcuwruWtOdD8oYC9jDNJjXDPyA==", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==", + "dev": true, "license": "MIT", - "engines": { - "node": ">=0.10.0" + "dependencies": { + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "node_modules/micromark-extension-gfm-strikethrough": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz", + "integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==", "dev": true, "license": "MIT", "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" }, - "engines": { - "node": ">= 0.8.0" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/libphonenumber-js": { - "version": "1.11.4", - "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.11.4.tgz", - "integrity": "sha512-F/R50HQuWWYcmU/esP5jrH5LiWYaN7DpN0a/99U8+mnGGtnx8kmRE+649dQh3v+CowXXZc8vpkf5AmYkO0AQ7Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/light-my-request": { - "version": "5.12.0", - "resolved": "https://registry.npmjs.org/light-my-request/-/light-my-request-5.12.0.tgz", - "integrity": "sha512-P526OX6E7aeCIfw/9UyJNsAISfcFETghysaWHQAlQYayynShT08MOj4c6fBCvTWBrHXSvqBAKDp3amUPSCQI4w==", + "node_modules/micromark-extension-gfm-table": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.0.tgz", + "integrity": "sha512-Ub2ncQv+fwD70/l4ou27b4YzfNaCJOvyX4HxXU15m7mpYY+rjuWzsLIPZHJL253Z643RpbcP1oeIJlQ/SKW67g==", "dev": true, - "license": "BSD-3-Clause", + "license": "MIT", "dependencies": { - "cookie": "^0.6.0", - "process-warning": "^3.0.0", - "set-cookie-parser": "^2.4.1" + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/linkify-it": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", - "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", + "node_modules/micromark-extension-gfm-tagfilter": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz", + "integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==", "dev": true, "license": "MIT", "dependencies": { - "uc.micro": "^2.0.0" + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "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, - "license": "ISC" - }, - "node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "node_modules/micromark-extension-gfm-task-list-item": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz", + "integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==", "dev": true, "license": "MIT", "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash.includes": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", - "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash.isboolean": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", - "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash.isinteger": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", - "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash.isnumber": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", - "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash.isplainobject": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash.isstring": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", - "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "node_modules/micromark-factory-destination": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.0.tgz", + "integrity": "sha512-j9DGrQLm/Uhl2tCzcbLhy5kXsgkHUrjJHg4fFAeoMRwJmJerT9aw4FEhIbZStWN8A3qMwOp1uzHr4UL8AInxtA==", "dev": true, - "license": "MIT" + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } }, - "node_modules/lodash.once": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", - "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "node_modules/micromark-factory-label": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.0.tgz", + "integrity": "sha512-RR3i96ohZGde//4WSe/dJsxOX6vxIg9TimLAS3i4EhBAFx8Sm5SmqVfR8E87DPSR31nEAjZfbt91OMZWcNgdZw==", "dev": true, - "license": "MIT" + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } }, - "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==", + "node_modules/micromark-factory-space": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.0.tgz", + "integrity": "sha512-TKr+LIDX2pkBJXFLzpyPyljzYK3MtmllMUMODTQJIUfDGncESaqB90db9IAUcz4AZAJFdd8U9zOp9ty1458rxg==", "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], "license": "MIT", "dependencies": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" } }, - "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==", + "node_modules/micromark-factory-title": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.0.tgz", + "integrity": "sha512-jY8CSxmpWLOxS+t8W+FG3Xigc0RDQA9bKMY/EwILvsesiRniiVMejYTE4wumNc2f4UbAa4WsHqe3J1QS1sli+A==", "dev": true, - "license": "MIT" + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } }, - "node_modules/lowercase-keys": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz", - "integrity": "sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==", + "node_modules/micromark-factory-whitespace": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.0.tgz", + "integrity": "sha512-28kbwaBjc5yAI1XadbdPYHX/eDnqaUFVikLwrO7FDnKG7lpgxnvk/XGRhX/PN0mOZ+dBSZ+LgunHS+6tYQAzhA==", "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" } }, - "node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "license": "ISC", + "node_modules/micromark-util-character": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", + "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" } }, - "node_modules/lunr": { - "version": "2.3.9", - "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz", - "integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==", + "node_modules/micromark-util-chunked": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.0.tgz", + "integrity": "sha512-anK8SWmNphkXdaKgz5hJvGa7l00qmcaUQoMYsBwDlSKFKjc6gjGXPDw3FNL3Nbwq5L8gE+RCbGqTw49FK5Qyvg==", "dev": true, - "license": "MIT" + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } }, - "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==", + "node_modules/micromark-util-classify-character": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.0.tgz", + "integrity": "sha512-S0ze2R9GH+fu41FA7pbSqNWObo/kzwf8rN/+IGlW/4tC6oACOs8B++bh+i9bVyNnwCcuksbFwsBme5OCKXCwIw==", "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], "license": "MIT", - "engines": { - "node": ">=12" + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" } }, - "node_modules/make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "node_modules/micromark-util-combine-extensions": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.0.tgz", + "integrity": "sha512-vZZio48k7ON0fVS3CUgFatWHoKbbLTK/rT7pzpJ4Bjp5JjkZeasRfrS9wsBdDJK2cJLHMckXZdzPSSr1B8a4oQ==", "dev": true, - "license": "ISC" + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } }, - "node_modules/make-fetch-happen": { - "version": "11.1.1", - "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-11.1.1.tgz", - "integrity": "sha512-rLWS7GCSTcEujjVBs2YqG7Y4643u8ucvCJeSRqiLYhesrDuzeuFIk37xREzAsfQaqzl8b9rNCE4m6J8tvX4Q8w==", + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.1.tgz", + "integrity": "sha512-bmkNc7z8Wn6kgjZmVHOX3SowGmVdhYS7yBpMnuMnPzDq/6xwVA604DuOXMZTO1lvq01g+Adfa0pE2UKGlxL1XQ==", "dev": true, - "license": "ISC", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", "dependencies": { - "agentkeepalive": "^4.2.1", - "cacache": "^17.0.0", - "http-cache-semantics": "^4.1.1", - "http-proxy-agent": "^5.0.0", - "https-proxy-agent": "^5.0.0", - "is-lambda": "^1.0.1", - "lru-cache": "^7.7.1", - "minipass": "^5.0.0", - "minipass-fetch": "^3.0.0", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "negotiator": "^0.6.3", - "promise-retry": "^2.0.1", - "socks-proxy-agent": "^7.0.0", - "ssri": "^10.0.0" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "micromark-util-symbol": "^2.0.0" } }, - "node_modules/make-fetch-happen/node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "node_modules/micromark-util-decode-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.0.tgz", + "integrity": "sha512-r4Sc6leeUTn3P6gk20aFMj2ntPwn6qpDZqWvYmAG6NgvFTIlj4WtrAudLi65qYoaGdXYViXYw2pkmn7QnIFasA==", "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], "license": "MIT", "dependencies": { - "debug": "4" - }, - "engines": { - "node": ">= 6.0.0" + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-symbol": "^2.0.0" } }, - "node_modules/make-fetch-happen/node_modules/https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "node_modules/micromark-util-encode": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.0.tgz", + "integrity": "sha512-pS+ROfCXAGLWCOc8egcBvT0kf27GoWMqtdarNfDcjb6YLuV5cM3ioG45Ys2qOVqeqSbjaKg72vU+Wby3eddPsA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-html-tag-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.0.tgz", + "integrity": "sha512-xNn4Pqkj2puRhKdKTm8t1YHC/BAjx6CEwRFXntTaRf/x16aqka6ouVoutm+QdkISTlT7e2zU7U4ZdlDLJd2Mcw==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-normalize-identifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.0.tgz", + "integrity": "sha512-2xhYT0sfo85FMrUPtHcPo2rrp1lwbDEEzpx7jiH2xXJLqBuy4H0GgXk5ToU8IEwoROtXuL8ND0ttVa4rNqYK3w==", "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], "license": "MIT", "dependencies": { - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" + "micromark-util-symbol": "^2.0.0" } }, - "node_modules/make-fetch-happen/node_modules/lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "node_modules/micromark-util-resolve-all": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.0.tgz", + "integrity": "sha512-6KU6qO7DZ7GJkaCgwBNtplXCvGkJToU86ybBAUdavvgsCiG8lSSvYxr9MhwmQ+udpzywHsl4RpGJsYWG1pDOcA==", "dev": true, - "license": "ISC", - "engines": { - "node": ">=12" + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" } }, - "node_modules/make-fetch-happen/node_modules/minipass": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", - "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.0.tgz", + "integrity": "sha512-WhYv5UEcZrbAtlsnPuChHUAsu/iBPOVaEVsntLBIdpibO0ddy8OzavZz3iL2xVvBZOpolujSliP65Kq0/7KIYw==", "dev": true, - "license": "ISC", - "engines": { - "node": ">=8" + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" } }, - "node_modules/map-stream": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.0.7.tgz", - "integrity": "sha512-C0X0KQmGm3N2ftbTGBhSyuydQ+vV1LC3f3zPvT3RXHXNZrvfPZcoXp/N5DOa8vedX/rTMm2CjTtivFg2STJMRQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/markdown-it": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz", - "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==", + "node_modules/micromark-util-subtokenize": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.0.1.tgz", + "integrity": "sha512-jZNtiFl/1aY73yS3UGQkutD0UbhTt68qnRpw2Pifmz5wV9h8gOVsN70v+Lq/f1rKaU/W8pxRe8y8Q9FX1AOe1Q==", "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], "license": "MIT", "dependencies": { - "argparse": "^2.0.1", - "entities": "^4.4.0", - "linkify-it": "^5.0.0", - "mdurl": "^2.0.0", - "punycode.js": "^2.3.1", - "uc.micro": "^2.1.0" - }, - "bin": { - "markdown-it": "bin/markdown-it.mjs" + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" } }, - "node_modules/mdurl": { + "node_modules/micromark-util-symbol": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", - "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", + "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], "license": "MIT" }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "node_modules/micromark-util-types": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.0.tgz", + "integrity": "sha512-oNh6S2WMHWRZrmutsRmDDfkzKtxF+bc2VxLC9dvtrDIRFln627VsFP6fLMgTryGDljgLPjkrzQSDcPrjPyDJ5w==", "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" }, "node_modules/micromatch": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz", - "integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, "license": "MIT", "dependencies": { @@ -7214,181 +11352,49 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/minipass-collect": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", - "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", - "dev": true, - "license": "ISC", - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/minipass-collect/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minipass-fetch": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-3.0.5.tgz", - "integrity": "sha512-2N8elDQAtSnFV0Dk7gt15KHsS0Fyz6CbYZ360h0WTYV1Ty46li3rAXVOQj1THMNLdmrD9Vt5pBPtWtVkpwGBqg==", - "dev": true, - "license": "MIT", - "dependencies": { - "minipass": "^7.0.3", - "minipass-sized": "^1.0.3", - "minizlib": "^2.1.2" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - }, - "optionalDependencies": { - "encoding": "^0.1.13" - } - }, - "node_modules/minipass-flush": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", - "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", - "dev": true, - "license": "ISC", - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/minipass-flush/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minipass-json-stream": { + "node_modules/min-indent": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minipass-json-stream/-/minipass-json-stream-1.0.1.tgz", - "integrity": "sha512-ODqY18UZt/I8k+b7rl2AENgbWE8IDYam+undIJONvigAz8KR5GWblsFTEfQs0WODsjbSXWlm+JHEv8Gr6Tfdbg==", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", "dev": true, "license": "MIT", - "dependencies": { - "jsonparse": "^1.3.1", - "minipass": "^3.0.0" - } - }, - "node_modules/minipass-json-stream/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, "engines": { - "node": ">=8" + "node": ">=4" } }, - "node_modules/minipass-pipeline": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", - "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", + "node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "dev": true, "license": "ISC", "dependencies": { - "minipass": "^3.0.0" + "brace-expansion": "^2.0.1" }, "engines": { - "node": ">=8" - } - }, - "node_modules/minipass-pipeline/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" + "node": ">=16 || 14 >=14.17" }, - "engines": { - "node": ">=8" + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/minipass-sized": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", - "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", - "dev": true, - "license": "ISC", - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">=8" + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/minipass-sized/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", "dev": true, "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, "engines": { - "node": ">=8" + "node": ">=16 || 14 >=14.17" } }, "node_modules/minizlib": { @@ -7418,6 +11424,13 @@ "node": ">=8" } }, + "node_modules/minizlib/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, "node_modules/mkdirp": { "version": "0.5.6", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", @@ -7438,6 +11451,19 @@ "dev": true, "license": "MIT" }, + "node_modules/mlly": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.7.1.tgz", + "integrity": "sha512-rrVRZRELyQzrIUAVMHxP97kv+G786pHmOKzuFII8zDYahFBS7qnHh2AlYSl1GAHhaMPCz6/oHjVMcfFYgFYHgA==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.3", + "pathe": "^1.1.2", + "pkg-types": "^1.1.1", + "ufo": "^1.5.3" + } + }, "node_modules/mnemonist": { "version": "0.39.6", "resolved": "https://registry.npmjs.org/mnemonist/-/mnemonist-0.39.6.tgz", @@ -7492,10 +11518,28 @@ "process-nextick-args": "^2.0.1" } }, + "node_modules/mqtt/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mqtt/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC" + }, "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, "node_modules/multicast-dns": { @@ -7524,6 +11568,25 @@ "devOptional": true, "license": "MIT" }, + "node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, "node_modules/napi-build-utils": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz", @@ -7535,8 +11598,7 @@ "version": "0.0.6", "resolved": "https://registry.npmjs.org/napi-thread-safe-callback/-/napi-thread-safe-callback-0.0.6.tgz", "integrity": "sha512-X7uHCOCdY4u0yamDxDrv3jF2NtYc8A1nvPzBQgvpoSX+WB3jAe2cVNsY448V1ucq7Whf9Wdy02HEUoLW5rJKWg==", - "license": "ISC", - "optional": true + "license": "ISC" }, "node_modules/natural-compare": { "version": "1.4.0", @@ -7545,6 +11607,13 @@ "dev": true, "license": "MIT" }, + "node_modules/natural-compare-lite": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", + "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", + "dev": true, + "license": "MIT" + }, "node_modules/negotiator": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", @@ -7556,9 +11625,9 @@ } }, "node_modules/node-abi": { - "version": "3.65.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.65.0.tgz", - "integrity": "sha512-ThjYBfoDNr08AWx6hGaRbfPwxKV9kVzAzOzlLKbk2CuqXE2xnCh+cbAGnwM3t8Lq4v9rUB7VfondlkBckcJrVA==", + "version": "3.67.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.67.0.tgz", + "integrity": "sha512-bLn/fU/ALVBE9wj+p4Y21ZJWYFjUXLXPi/IewyLZkx3ApxKDNBWCKdReeKOtD8dWpOdDCeMyLh6ZewzcLsG2Nw==", "dev": true, "license": "MIT", "dependencies": { @@ -7572,8 +11641,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-4.3.0.tgz", "integrity": "sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ==", - "license": "MIT", - "optional": true + "license": "MIT" }, "node_modules/node-cache": { "version": "5.1.2", @@ -7608,1623 +11676,1699 @@ } } }, - "node_modules/node-gyp": { - "version": "9.4.1", - "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-9.4.1.tgz", - "integrity": "sha512-OQkWKbjQKbGkMf/xqI1jjy3oCTgMKJac58G2+bjZb3fza6gW2YrCSdMQYaoTb70crvE//Gngr4f0AgVHmqHvBQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "env-paths": "^2.2.0", - "exponential-backoff": "^3.1.1", - "glob": "^7.1.4", - "graceful-fs": "^4.2.6", - "make-fetch-happen": "^10.0.3", - "nopt": "^6.0.0", - "npmlog": "^6.0.0", - "rimraf": "^3.0.2", - "semver": "^7.3.5", - "tar": "^6.1.2", - "which": "^2.0.2" - }, - "bin": { - "node-gyp": "bin/node-gyp.js" - }, - "engines": { - "node": "^12.13 || ^14.13 || >=16" - } - }, "node_modules/node-gyp-build": { - "version": "4.8.1", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.1.tgz", - "integrity": "sha512-OSs33Z9yWr148JZcbZd5WiAXhh/n9z8TxQcdMhIOlpN9AhWpLfvVFO73+m77bBABQMaY9XSvIa+qk0jlI7Gcaw==", + "version": "4.8.2", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.2.tgz", + "integrity": "sha512-IRUxE4BVsHWXkV/SFOut4qTlagw2aM8T5/vnTsmrHJvVoKueJHRc/JaFND7QDDc61kLYUJ6qlZM3sqTSyx2dTw==", "license": "MIT", - "optional": true, "bin": { "node-gyp-build": "bin.js", "node-gyp-build-optional": "optional.js", "node-gyp-build-test": "build-test.js" } }, - "node_modules/node-gyp/node_modules/@npmcli/fs": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-2.1.2.tgz", - "integrity": "sha512-yOJKRvohFOaLqipNtwYB9WugyZKhC/DZC4VYPmpaCzDBrA8YpK3qHZ8/HGscMnE4GqbkLNuVcCnxkeQEdGt6LQ==", + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", "dev": true, - "license": "ISC", + "license": "MIT" + }, + "node_modules/node-persist": { + "version": "0.0.12", + "resolved": "https://registry.npmjs.org/node-persist/-/node-persist-0.0.12.tgz", + "integrity": "sha512-Fbia3FYnURzaql53wLu0t19dmAwQg/tXT6O7YPmdwNwysNKEyFmgoT2BQlPD3XXQnYeiQVNvR5lfvufGwKuxhg==", + "dev": true, + "license": "MIT", "dependencies": { - "@gar/promisify": "^1.1.3", - "semver": "^7.3.5" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "mkdirp": "~0.5.1", + "q": "~1.1.1" } }, - "node_modules/node-gyp/node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "node_modules/node-releases": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", + "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", + "dev": true, + "license": "MIT" + }, + "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, "license": "MIT", "dependencies": { - "debug": "4" + "cron-parser": "^4.2.0", + "long-timeout": "0.1.1", + "sorted-array-functions": "^1.3.0" }, "engines": { - "node": ">= 6.0.0" + "node": ">=6" } }, - "node_modules/node-gyp/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, + "node_modules/node-switchbot": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/node-switchbot/-/node-switchbot-2.3.0.tgz", + "integrity": "sha512-Auqp3ejbeIbJkb2e5v5S0qSXTBNUCYig69Gh3Gain4mrMQL8Rkg4PvWxM+F41IpKa0qk9scyfTid1kV7JPpYRg==", "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0" + "@stoprocent/noble": "^1.14.1" + }, + "optionalDependencies": { + "@stoprocent/bluetooth-hci-socket": "^1.2.1" } }, - "node_modules/node-gyp/node_modules/cacache": { - "version": "16.1.3", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-16.1.3.tgz", - "integrity": "sha512-/+Emcj9DAXxX4cwlLmRI9c166RuL3w30zp4R7Joiv2cQTtTtA+jeuCAjH3ZlGnYS3tKENSrKhAzVVP9GVyzeYQ==", + "node_modules/nodemon": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.4.tgz", + "integrity": "sha512-wjPBbFhtpJwmIeY2yP7QF+UKzPfltVGtfce1g/bB15/8vCGZj8uxD62b/b9M9/WVgme0NZudpownKN+c0plXlQ==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "@npmcli/fs": "^2.1.0", - "@npmcli/move-file": "^2.0.0", - "chownr": "^2.0.0", - "fs-minipass": "^2.1.0", - "glob": "^8.0.1", - "infer-owner": "^1.0.4", - "lru-cache": "^7.7.1", - "minipass": "^3.1.6", - "minipass-collect": "^1.0.2", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "mkdirp": "^1.0.4", - "p-map": "^4.0.0", - "promise-inflight": "^1.0.1", - "rimraf": "^3.0.2", - "ssri": "^9.0.0", - "tar": "^6.1.11", - "unique-filename": "^2.0.0" + "chokidar": "^3.5.2", + "debug": "^4", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", + "semver": "^7.5.3", + "simple-update-notifier": "^2.0.0", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "bin": { + "nodemon": "bin/nodemon.js" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" } }, - "node_modules/node-gyp/node_modules/cacache/node_modules/glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", - "deprecated": "Glob versions prior to v9 are no longer supported", + "node_modules/nodemon/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" - }, + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/nodemon/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">=4" } }, - "node_modules/node-gyp/node_modules/cacache/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "node_modules/nodemon/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^1.1.7" }, "engines": { - "node": ">=10" + "node": "*" } }, - "node_modules/node-gyp/node_modules/fs-minipass": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", - "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "node_modules/nodemon/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "minipass": "^3.0.0" + "has-flag": "^3.0.0" }, "engines": { - "node": ">= 8" + "node": ">=4" } }, - "node_modules/node-gyp/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", + "node_modules/normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", "dev": true, - "license": "ISC", + "license": "BSD-2-Clause", "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" - }, + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "node_modules/normalize-package-data/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", "engines": { - "node": "*" + "node": ">=0.10.0" + } + }, + "node_modules/npm-check-updates": { + "version": "17.1.1", + "resolved": "https://registry.npmjs.org/npm-check-updates/-/npm-check-updates-17.1.1.tgz", + "integrity": "sha512-2aqIzGAEWB7xPf0hKHTkNmUM5jHbn2S5r2/z/7dA5Ij2h/sVYAg9R/uVkaUC3VORPAfBm7pKkCWo6E9clEVQ9A==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "ncu": "build/cli.js", + "npm-check-updates": "build/cli.js" }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "engines": { + "node": "^18.18.0 || >=20.0.0", + "npm": ">=8.12.1" } }, - "node_modules/node-gyp/node_modules/https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", "dev": true, "license": "MIT", "dependencies": { - "agent-base": "6", - "debug": "4" + "path-key": "^3.0.0" }, "engines": { - "node": ">= 6" + "node": ">=8" + } + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/number-allocator": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/number-allocator/-/number-allocator-1.0.14.tgz", + "integrity": "sha512-OrL44UTVAvkKdOdRQZIJpLkAdjXGTRda052sN4sO77bKEzYYqWKMBjQvrJFzqygI99gL6Z4u2xctPW1tB8ErvA==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.1", + "js-sdsl": "4.3.0" } }, - "node_modules/node-gyp/node_modules/lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", "dev": true, - "license": "ISC", + "license": "MIT", "engines": { - "node": ">=12" + "node": ">=0.10.0" } }, - "node_modules/node-gyp/node_modules/make-fetch-happen": { - "version": "10.2.1", - "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-10.2.1.tgz", - "integrity": "sha512-NgOPbRiaQM10DYXvN3/hhGVI2M5MtITFryzBGxHM5p4wnFxsVCbxkrBrDsk+EZ5OB4jEOT7AjDxtdF+KVEFT7w==", + "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, - "license": "ISC", - "dependencies": { - "agentkeepalive": "^4.2.1", - "cacache": "^16.1.0", - "http-cache-semantics": "^4.1.0", - "http-proxy-agent": "^5.0.0", - "https-proxy-agent": "^5.0.0", - "is-lambda": "^1.0.1", - "lru-cache": "^7.7.1", - "minipass": "^3.1.6", - "minipass-collect": "^1.0.2", - "minipass-fetch": "^2.0.3", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "negotiator": "^0.6.3", - "promise-retry": "^2.0.1", - "socks-proxy-agent": "^7.0.0", - "ssri": "^9.0.0" - }, + "license": "MIT", "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": ">= 6" } }, - "node_modules/node-gyp/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, + "node_modules/object-inspect": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", + "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", + "license": "MIT", "engines": { - "node": ">=8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/node-gyp/node_modules/minipass-fetch": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-2.1.2.tgz", - "integrity": "sha512-LT49Zi2/WMROHYoqGgdlQIZh8mLPZmOrN2NdJjMXxYe4nkN6FUyuPuOAOedNJDrx0IRGg9+4guZewtp8hE6TxA==", - "dev": true, + "node_modules/object-is": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.6.tgz", + "integrity": "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==", "license": "MIT", "dependencies": { - "minipass": "^3.1.6", - "minipass-sized": "^1.0.3", - "minizlib": "^2.1.2" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": ">= 0.4" }, - "optionalDependencies": { - "encoding": "^0.1.13" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/node-gyp/node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", "license": "MIT", - "bin": { - "mkdirp": "bin/cmd.js" - }, "engines": { - "node": ">=10" + "node": ">= 0.4" } }, - "node_modules/node-gyp/node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", - "dev": true, - "license": "ISC", + "node_modules/object.assign": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz", + "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==", + "license": "MIT", "dependencies": { - "glob": "^7.1.3" + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" }, - "bin": { - "rimraf": "bin.js" + "engines": { + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/node-gyp/node_modules/ssri": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-9.0.1.tgz", - "integrity": "sha512-o57Wcn66jMQvfHG1FlYbWeZWW/dHZhJXjpIcTfXldXEk5nz5lStPo3mK0OJQfGR3RbZUlbISexbljkJzuEj/8Q==", + "node_modules/obliterator": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/obliterator/-/obliterator-2.0.4.tgz", + "integrity": "sha512-lgHwxlxV1qIg1Eap7LgIeoBWIMFibOjbrYPIPJZcI1mmGAI2m3lNYpK12Y+GBdPQ0U1hRwSord7GIaawz962qQ==", "dev": true, - "license": "ISC", - "dependencies": { - "minipass": "^3.1.1" - }, + "license": "MIT" + }, + "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, + "license": "MIT", "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": ">=14.0.0" } }, - "node_modules/node-gyp/node_modules/unique-filename": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-2.0.1.tgz", - "integrity": "sha512-ODWHtkkdx3IAR+veKxFV+VBkUMcN+FaqzUUd7IZzt+0zhDZFPFxhlqwPF3YQvMHx1TD0tdgYl+kuPnJ8E6ql7A==", - "dev": true, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", "license": "ISC", "dependencies": { - "unique-slug": "^3.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "wrappy": "1" } }, - "node_modules/node-gyp/node_modules/unique-slug": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-3.0.0.tgz", - "integrity": "sha512-8EyMynh679x/0gqE9fT9oilG+qEt+ibFyqjuVTsZn1+CMxH+XLlpvr2UZx4nVcCwTpx81nICr2JQFkM+HPLq4w==", + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "imurmurhash": "^0.1.4" + "mimic-fn": "^2.1.0" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/node-persist": { - "version": "0.0.12", - "resolved": "https://registry.npmjs.org/node-persist/-/node-persist-0.0.12.tgz", - "integrity": "sha512-Fbia3FYnURzaql53wLu0t19dmAwQg/tXT6O7YPmdwNwysNKEyFmgoT2BQlPD3XXQnYeiQVNvR5lfvufGwKuxhg==", + "node_modules/oniguruma-to-js": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/oniguruma-to-js/-/oniguruma-to-js-0.3.3.tgz", + "integrity": "sha512-m90/WEhgs8g4BxG37+Nu3YrMfJDs2YXtYtIllhsEPR+wP3+K4EZk6dDUvy2v2K4MNFDDOYKL4/yqYPXDqyozTQ==", "dev": true, "license": "MIT", - "dependencies": { - "mkdirp": "~0.5.1", - "q": "~1.1.1" + "funding": { + "url": "https://github.com/sponsors/antfu" } }, - "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==", + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", "dev": true, "license": "MIT", "dependencies": { - "cron-parser": "^4.2.0", - "long-timeout": "0.1.1", - "sorted-array-functions": "^1.3.0" + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" }, "engines": { - "node": ">=6" - } - }, - "node_modules/node-switchbot": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/node-switchbot/-/node-switchbot-2.3.0.tgz", - "integrity": "sha512-Auqp3ejbeIbJkb2e5v5S0qSXTBNUCYig69Gh3Gain4mrMQL8Rkg4PvWxM+F41IpKa0qk9scyfTid1kV7JPpYRg==", - "license": "MIT", - "optional": true, - "dependencies": { - "@stoprocent/noble": "^1.14.1" - }, - "optionalDependencies": { - "@stoprocent/bluetooth-hci-socket": "^1.2.1" + "node": ">= 0.8.0" } }, - "node_modules/nodemon": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.4.tgz", - "integrity": "sha512-wjPBbFhtpJwmIeY2yP7QF+UKzPfltVGtfce1g/bB15/8vCGZj8uxD62b/b9M9/WVgme0NZudpownKN+c0plXlQ==", + "node_modules/ora": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", "dev": true, "license": "MIT", "dependencies": { - "chokidar": "^3.5.2", - "debug": "^4", - "ignore-by-default": "^1.0.1", - "minimatch": "^3.1.2", - "pstree.remy": "^1.1.8", - "semver": "^7.5.3", - "simple-update-notifier": "^2.0.0", - "supports-color": "^5.5.0", - "touch": "^3.1.0", - "undefsafe": "^2.0.5" - }, - "bin": { - "nodemon": "bin/nodemon.js" + "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": { - "type": "opencollective", - "url": "https://opencollective.com/nodemon" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/nodemon/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "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, "license": "MIT", - "engines": { - "node": ">=4" + "dependencies": { + "@otplib/core": "^12.0.1", + "@otplib/preset-default": "^12.0.1", + "@otplib/preset-v11": "^12.0.1" } }, - "node_modules/nodemon/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dev": true, "license": "MIT", "dependencies": { - "has-flag": "^3.0.0" + "yocto-queue": "^0.1.0" }, "engines": { - "node": ">=4" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/nopt": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-6.0.0.tgz", - "integrity": "sha512-ZwLpbTgdhuZUnZzjd7nb1ZV+4DoiC6/sfiVKok72ym/4Tlf+DFdlHYmT2JPmcNNWV6Pi3SDf1kT+A4r9RTuT9g==", + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "abbrev": "^1.0.0" + "p-limit": "^3.0.2" }, - "bin": { - "nopt": "bin/nopt.js" + "engines": { + "node": ">=10" }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "license": "MIT", "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": ">=6" } }, - "node_modules/normalize-package-data": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-5.0.0.tgz", - "integrity": "sha512-h9iPVIfrVZ9wVYQnxFgtw1ugSvGEMOlyPWWtm8BMJhnwyEL/FLbYbTY3V3PpjI/BUK67n9PEWDu6eHzu1fB15Q==", + "node_modules/package-json-from-dist": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz", + "integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/package-manager-detector": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-0.2.0.tgz", + "integrity": "sha512-E385OSk9qDcXhcM9LNSe4sdhx8a9mAPrZ4sMLW+tmxl5ZuGtPUcdFu+MPP2jbgiWAZ6Pfe5soGFMd+0Db5Vrog==", "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "hosted-git-info": "^6.0.0", - "is-core-module": "^2.8.1", - "semver": "^7.3.5", - "validate-npm-package-license": "^3.0.4" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } + "license": "MIT" }, - "node_modules/normalize-package-data/node_modules/hosted-git-info": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-6.1.1.tgz", - "integrity": "sha512-r0EI+HBMcXadMrugk0GCQ+6BQV39PiWAZVfq7oIckeGiN7sjRGyQxPdft3nQekFTCQbYxLBH+/axZMeH8UX6+w==", + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "lru-cache": "^7.5.1" + "callsites": "^3.0.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": ">=6" } }, - "node_modules/normalize-package-data/node_modules/lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "node_modules/parse-gitignore": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/parse-gitignore/-/parse-gitignore-2.0.0.tgz", + "integrity": "sha512-RmVuCHWsfu0QPNW+mraxh/xjQVw/lhUCUru8Zni3Ctq3AoMhpDTq0OVdKS6iesd6Kqb7viCV3isAL43dciOSog==", "dev": true, - "license": "ISC", + "license": "MIT", "engines": { - "node": ">=12" + "node": ">=14" } }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "node_modules/parse-imports": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/parse-imports/-/parse-imports-2.1.1.tgz", + "integrity": "sha512-TDT4HqzUiTMO1wJRwg/t/hYk8Wdp3iF/ToMIlAoVQfL1Xs/sTxq1dKWSMjMbQmIarfWKymOyly40+zmPHXMqCA==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", + "dependencies": { + "es-module-lexer": "^1.5.3", + "slashes": "^3.0.12" + }, "engines": { - "node": ">=0.10.0" + "node": ">= 18" } }, - "node_modules/normalize-url": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.0.1.tgz", - "integrity": "sha512-IO9QvjUMWxPQQhs60oOu10CRkWCiZzSUkzbXGGV9pviYl1fXYcvkzQ5jV9z8Y6un8ARoVRl4EtC6v6jNqbaJ/w==", + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", "dev": true, "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, "engines": { - "node": ">=14.16" + "node": ">=8" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/npm-bundled": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-3.0.1.tgz", - "integrity": "sha512-+AvaheE/ww1JEwRHOrn4WHNzOxGtVp+adrg2AeZS/7KuxGUYFuBta98wYpfHBbJp6Tg6j1NKSEVHNcfZzJHQwQ==", + "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, - "license": "ISC", + "license": "MIT", "dependencies": { - "npm-normalize-package-bin": "^3.0.0" + "passport-strategy": "1.x.x", + "pause": "0.0.1", + "utils-merge": "^1.0.1" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": ">= 0.4.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/jaredhanson" } }, - "node_modules/npm-check-updates": { - "version": "16.14.20", - "resolved": "https://registry.npmjs.org/npm-check-updates/-/npm-check-updates-16.14.20.tgz", - "integrity": "sha512-sYbIhun4DrjO7NFOTdvs11nCar0etEhZTsEjL47eM0TuiGMhmYughRCxG2SpGRmGAQ7AkwN7bw2lWzoE7q6yOQ==", + "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, - "license": "Apache-2.0", + "license": "MIT", "dependencies": { - "@types/semver-utils": "^1.1.1", - "chalk": "^5.3.0", - "cli-table3": "^0.6.3", - "commander": "^10.0.1", - "fast-memoize": "^2.5.2", - "find-up": "5.0.0", - "fp-and-or": "^0.1.4", - "get-stdin": "^8.0.0", - "globby": "^11.0.4", - "hosted-git-info": "^5.1.0", - "ini": "^4.1.1", - "js-yaml": "^4.1.0", - "json-parse-helpfulerror": "^1.0.3", - "jsonlines": "^0.1.1", - "lodash": "^4.17.21", - "make-fetch-happen": "^11.1.1", - "minimatch": "^9.0.3", - "p-map": "^4.0.0", - "pacote": "15.2.0", - "parse-github-url": "^1.0.2", - "progress": "^2.0.3", - "prompts-ncu": "^3.0.0", - "rc-config-loader": "^4.1.3", - "remote-git-tags": "^3.0.0", - "rimraf": "^5.0.5", - "semver": "^7.5.4", - "semver-utils": "^1.1.4", - "source-map-support": "^0.5.21", - "spawn-please": "^2.0.2", - "strip-ansi": "^7.1.0", - "strip-json-comments": "^5.0.1", - "untildify": "^4.0.0", - "update-notifier": "^6.0.2" - }, - "bin": { - "ncu": "build/src/bin/cli.js", - "npm-check-updates": "build/src/bin/cli.js" - }, - "engines": { - "node": ">=14.14" + "jsonwebtoken": "^9.0.0", + "passport-strategy": "^1.0.0" } }, - "node_modules/npm-check-updates/node_modules/ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "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, - "license": "MIT", "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" + "node": ">= 0.4.0" } }, - "node_modules/npm-check-updates/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true, "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" + "engines": { + "node": ">=8" } }, - "node_modules/npm-check-updates/node_modules/chalk": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", - "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", - "dev": true, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", "license": "MIT", "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "node": ">=0.10.0" } }, - "node_modules/npm-check-updates/node_modules/commander": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", - "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true, "license": "MIT", "engines": { - "node": ">=14" + "node": ">=8" } }, - "node_modules/npm-check-updates/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true, - "license": "ISC", + "license": "MIT" + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^2.0.1" + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": ">=16 || 14 >=14.18" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/npm-check-updates/node_modules/rimraf": { - "version": "5.0.9", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.9.tgz", - "integrity": "sha512-3i7b8OcswU6CpU8Ej89quJD4O98id7TtVM5U4Mybh84zQXdrFmDLouWBEEaD/QfO3gDDfH+AGFCGsR7kngzQnA==", + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", "dev": true, - "license": "ISC", - "dependencies": { - "glob": "^10.3.7" - }, - "bin": { - "rimraf": "dist/esm/bin.mjs" - }, + "license": "ISC" + }, + "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, + "license": "MIT" + }, + "node_modules/pathe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathval": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz", + "integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==", + "dev": true, + "license": "MIT", "engines": { - "node": "14 >=14.20 || 16 >=16.20 || >=18" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">= 14.16" } }, - "node_modules/npm-check-updates/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "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", + "integrity": "sha512-e3FBlXLmN/D1S+zHzanP4E/4Z60oFAa3O051qt1pxa7DEJWKAyil6upYVXCWadEnuoqa4Pkc9oUx9zsxYeRv8A==", "dev": true, - "license": "MIT", + "license": [ + "MIT", + "Apache2" + ], "dependencies": { - "ansi-regex": "^6.0.1" - }, + "through": "~2.3" + } + }, + "node_modules/picocolors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", + "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "license": "MIT", "engines": { "node": ">=12" }, "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" + "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/npm-check-updates/node_modules/strip-json-comments": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-5.0.1.tgz", - "integrity": "sha512-0fk9zBqO67Nq5M/m45qHCJxylV/DhBlIOVExqgOMiCCrzrhU6tCibRXNqE3jwJLftzE9SNuZtYbpzcO+i9FiKw==", + "node_modules/pino": { + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/pino/-/pino-9.4.0.tgz", + "integrity": "sha512-nbkQb5+9YPhQRz/BeQmrWpEknAaqjpAqRK8NwJpmrX/JHu7JuZC5G1CeAwJDJfGes4h+YihC6in3Q2nGb+Y09w==", "dev": true, "license": "MIT", - "engines": { - "node": ">=14.16" + "dependencies": { + "atomic-sleep": "^1.0.0", + "fast-redact": "^3.1.1", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^1.2.0", + "pino-std-serializers": "^7.0.0", + "process-warning": "^4.0.0", + "quick-format-unescaped": "^4.0.3", + "real-require": "^0.2.0", + "safe-stable-stringify": "^2.3.1", + "sonic-boom": "^4.0.1", + "thread-stream": "^3.0.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "bin": { + "pino": "bin.js" } }, - "node_modules/npm-install-checks": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-6.3.0.tgz", - "integrity": "sha512-W29RiK/xtpCGqn6f3ixfRYGk+zRyr+Ew9F2E20BfXxT5/euLdA/Nm7fO7OeTGuAmTs30cpgInyJ0cYe708YTZw==", + "node_modules/pino-abstract-transport": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-1.2.0.tgz", + "integrity": "sha512-Guhh8EZfPCfH+PMXAb6rKOjGQEoy0xlAIn+irODG5kgfYV+BQ0rGYYWTIel3P5mmyXqkYkPmdIkywsn6QKUR1Q==", "dev": true, - "license": "BSD-2-Clause", + "license": "MIT", "dependencies": { - "semver": "^7.1.1" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "readable-stream": "^4.0.0", + "split2": "^4.0.0" } }, - "node_modules/npm-normalize-package-bin": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-3.0.1.tgz", - "integrity": "sha512-dMxCf+zZ+3zeQZXKxmyuCKlIDPGuv8EF940xbkC4kQVDTtqoh6rJFO+JTKSA6/Rwi0getWmtuy4Itup0AMcaDQ==", + "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, - "license": "ISC", - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" } }, - "node_modules/npm-package-arg": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-10.1.0.tgz", - "integrity": "sha512-uFyyCEmgBfZTtrKk/5xDfHp6+MdrqGotX/VoOyEEl3mBwiEE5FlBaePanazJSVMPT7vKepcjYBY2ztg9A3yPIA==", + "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, - "license": "ISC", + "license": "MIT", "dependencies": { - "hosted-git-info": "^6.0.0", - "proc-log": "^3.0.0", - "semver": "^7.3.5", - "validate-npm-package-name": "^5.0.0" + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, - "node_modules/npm-package-arg/node_modules/hosted-git-info": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-6.1.1.tgz", - "integrity": "sha512-r0EI+HBMcXadMrugk0GCQ+6BQV39PiWAZVfq7oIckeGiN7sjRGyQxPdft3nQekFTCQbYxLBH+/axZMeH8UX6+w==", + "node_modules/pino-abstract-transport/node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", "dev": true, "license": "ISC", - "dependencies": { - "lru-cache": "^7.5.1" - }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": ">= 10.x" } }, - "node_modules/npm-package-arg/node_modules/lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "node_modules/pino-std-serializers": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.0.0.tgz", + "integrity": "sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==", "dev": true, - "license": "ISC", - "engines": { - "node": ">=12" - } + "license": "MIT" }, - "node_modules/npm-packlist": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-7.0.4.tgz", - "integrity": "sha512-d6RGEuRrNS5/N84iglPivjaJPxhDbZmlbTwTDX2IbcRHG5bZCdtysYMhwiPvcF4GisXHGn7xsxv+GQ7T/02M5Q==", + "node_modules/pino/node_modules/process-warning": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-4.0.0.tgz", + "integrity": "sha512-/MyYDxttz7DfGMMHiysAsFE4qF+pQYAA8ziO/3NcRVrQ5fSk+Mns4QZA/oRPFzvcqNoVJXQNWNAsdwBXLUkQKw==", "dev": true, - "license": "ISC", - "dependencies": { - "ignore-walk": "^6.0.0" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } + "license": "MIT" }, - "node_modules/npm-pick-manifest": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-8.0.2.tgz", - "integrity": "sha512-1dKY+86/AIiq1tkKVD3l0WI+Gd3vkknVGAggsFeBkTvbhMQ1OND/LKkYv4JtXPKUJ8bOTCyLiqEg2P6QNdK+Gg==", + "node_modules/pirates": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", + "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", "dev": true, - "license": "ISC", - "dependencies": { - "npm-install-checks": "^6.0.0", - "npm-normalize-package-bin": "^3.0.0", - "npm-package-arg": "^10.0.0", - "semver": "^7.3.5" - }, + "license": "MIT", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": ">= 6" } }, - "node_modules/npm-registry-fetch": { - "version": "14.0.5", - "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-14.0.5.tgz", - "integrity": "sha512-kIDMIo4aBm6xg7jOttupWZamsZRkAqMqwqqbVXnUqstY5+tapvv6bkH/qMR76jdgV+YljEUCyWx3hRYMrJiAgA==", + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "make-fetch-happen": "^11.0.0", - "minipass": "^5.0.0", - "minipass-fetch": "^3.0.0", - "minipass-json-stream": "^1.0.1", - "minizlib": "^2.1.2", - "npm-package-arg": "^10.0.0", - "proc-log": "^3.0.0" + "find-up": "^4.0.0" }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/npm-registry-fetch/node_modules/minipass": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", - "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", - "dev": true, - "license": "ISC", "engines": { "node": ">=8" } }, - "node_modules/npmlog": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz", - "integrity": "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==", - "deprecated": "This package is no longer supported.", + "node_modules/pkg-dir/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "are-we-there-yet": "^3.0.0", - "console-control-strings": "^1.1.0", - "gauge": "^4.0.3", - "set-blocking": "^2.0.0" + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/number-allocator": { - "version": "1.0.14", - "resolved": "https://registry.npmjs.org/number-allocator/-/number-allocator-1.0.14.tgz", - "integrity": "sha512-OrL44UTVAvkKdOdRQZIJpLkAdjXGTRda052sN4sO77bKEzYYqWKMBjQvrJFzqygI99gL6Z4u2xctPW1tB8ErvA==", - "license": "MIT", - "dependencies": { - "debug": "^4.3.1", - "js-sdsl": "4.3.0" + "node": ">=8" } }, - "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==", + "node_modules/pkg-dir/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "dev": true, "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, - "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==", + "node_modules/pkg-dir/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "dev": true, "license": "MIT", - "engines": { - "node": ">= 6" - } - }, - "node_modules/object-inspect": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", - "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object-is": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.6.tgz", - "integrity": "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==", - "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1" + "p-try": "^2.0.0" }, "engines": { - "node": ">= 0.4" + "node": ">=6" }, "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "license": "MIT", - "engines": { - "node": ">= 0.4" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/object.assign": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz", - "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==", + "node_modules/pkg-dir/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.5", - "define-properties": "^1.2.1", - "has-symbols": "^1.0.3", - "object-keys": "^1.1.1" + "p-limit": "^2.2.0" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=8" } }, - "node_modules/obliterator": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/obliterator/-/obliterator-2.0.4.tgz", - "integrity": "sha512-lgHwxlxV1qIg1Eap7LgIeoBWIMFibOjbrYPIPJZcI1mmGAI2m3lNYpK12Y+GBdPQ0U1hRwSord7GIaawz962qQ==", + "node_modules/pkg-types": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.2.0.tgz", + "integrity": "sha512-+ifYuSSqOQ8CqP4MbZA5hDpb97n3E8SVWdJe+Wms9kj745lmd3b7EZJiqvmLwAlmRfjrI7Hi5z3kdBJ93lFNPA==", "dev": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "confbox": "^0.1.7", + "mlly": "^1.7.1", + "pathe": "^1.1.2" + } }, - "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==", + "node_modules/pluralize": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", + "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==", "dev": true, "license": "MIT", "engines": { - "node": ">=14.0.0" + "node": ">=4" } }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "license": "ISC", - "dependencies": { - "wrappy": "1" + "node_modules/possible-typed-array-names": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", + "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.4" } }, - "node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "node_modules/postcss": { + "version": "8.4.45", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.45.tgz", + "integrity": "sha512-7KTLTdzdZZYscUc65XmjFiB73vBhBfbPztCYdUNvlaso9PrzjzcmjqBPR0lNGkcVlcO4BjiO5rK/qNz+XAen1Q==", "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], "license": "MIT", "dependencies": { - "mimic-fn": "^2.1.0" + "nanoid": "^3.3.7", + "picocolors": "^1.0.1", + "source-map-js": "^1.2.0" }, "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": "^10 || ^12 || >=14" } }, - "node_modules/optionator": { - "version": "0.9.4", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", - "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "node_modules/postcss-selector-parser": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", "dev": true, "license": "MIT", "dependencies": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.5" + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" }, "engines": { - "node": ">= 0.8.0" + "node": ">=4" } }, - "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==", + "node_modules/prebuild-install": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.2.tgz", + "integrity": "sha512-UnNke3IQb6sgarcZIDU3gbMeTp/9SSU1DAIkil7PrqG1vZlBtY5msYccSKSHDqa3hNg436IXK+SNImReuA1wEQ==", "dev": true, "license": "MIT", "dependencies": { - "bl": "^4.1.0", - "chalk": "^4.1.0", - "cli-cursor": "^3.1.0", - "cli-spinners": "^2.5.0", - "is-interactive": "^1.0.0", - "is-unicode-supported": "^0.1.0", - "log-symbols": "^4.1.0", - "strip-ansi": "^6.0.0", - "wcwidth": "^1.0.1" + "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" - }, - "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, - "license": "MIT", - "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", - "integrity": "sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==", + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true, "license": "MIT", "engines": { - "node": ">=12.20" + "node": ">= 0.8.0" } }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "node_modules/prettier": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz", + "integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==", "dev": true, "license": "MIT", - "dependencies": { - "yocto-queue": "^0.1.0" + "bin": { + "prettier": "bin/prettier.cjs" }, "engines": { - "node": ">=10" + "node": ">=14" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/prettier/prettier?sponsor=1" } }, - "node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "node_modules/prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", "dev": true, "license": "MIT", "dependencies": { - "p-limit": "^3.0.2" + "fast-diff": "^1.1.2" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=6.0.0" } }, - "node_modules/p-map": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", - "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", "dev": true, "license": "MIT", "dependencies": { - "aggregate-error": "^3.0.0" + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/package-json": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/package-json/-/package-json-8.1.1.tgz", - "integrity": "sha512-cbH9IAIJHNj9uXi196JVsRlt7cHKak6u/e6AkL/bkRelZ7rlL3X1YKxsZwa36xipOEKAsdtmaG6aAJoM1fx2zA==", + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "license": "MIT", - "dependencies": { - "got": "^12.1.0", - "registry-auth-token": "^5.0.1", - "registry-url": "^6.0.0", - "semver": "^7.3.7" - }, "engines": { - "node": ">=14.16" + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/package-json-from-dist": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz", - "integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==", - "dev": true, - "license": "BlueOak-1.0.0" - }, - "node_modules/pacote": { - "version": "15.2.0", - "resolved": "https://registry.npmjs.org/pacote/-/pacote-15.2.0.tgz", - "integrity": "sha512-rJVZeIwHTUta23sIZgEIM62WYwbmGbThdbnkt81ravBplQv+HjyroqnLRNH2+sLJHcGZmLRmhPwACqhfTcOmnA==", + "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, - "license": "ISC", - "dependencies": { - "@npmcli/git": "^4.0.0", - "@npmcli/installed-package-contents": "^2.0.1", - "@npmcli/promise-spawn": "^6.0.1", - "@npmcli/run-script": "^6.0.0", - "cacache": "^17.0.0", - "fs-minipass": "^3.0.0", - "minipass": "^5.0.0", - "npm-package-arg": "^10.0.0", - "npm-packlist": "^7.0.0", - "npm-pick-manifest": "^8.0.0", - "npm-registry-fetch": "^14.0.0", - "proc-log": "^3.0.0", - "promise-retry": "^2.0.1", - "read-package-json": "^6.0.0", - "read-package-json-fast": "^3.0.0", - "sigstore": "^1.3.0", - "ssri": "^10.0.0", - "tar": "^6.1.11" - }, - "bin": { - "pacote": "lib/bin.js" - }, + "license": "MIT", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": ">= 0.6.0" } }, - "node_modules/pacote/node_modules/minipass": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", - "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "license": "MIT" + }, + "node_modules/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, - "license": "ISC", - "engines": { - "node": ">=8" - } + "license": "MIT" }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", "dev": true, "license": "MIT", "dependencies": { - "callsites": "^3.0.0" + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" }, "engines": { - "node": ">=6" + "node": ">= 6" } }, - "node_modules/parse-github-url": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/parse-github-url/-/parse-github-url-1.0.3.tgz", - "integrity": "sha512-tfalY5/4SqGaV/GIGzWyHnFjlpTPTNpENR9Ea2lLldSJ8EWXMsvacWucqY3m3I4YPtas15IxTLQVQ5NSYXPrww==", + "node_modules/property-information": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.5.0.tgz", + "integrity": "sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==", "dev": true, "license": "MIT", - "bin": { - "parse-github-url": "cli.js" - }, - "engines": { - "node": ">= 0.10" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/passport": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/passport/-/passport-0.7.0.tgz", - "integrity": "sha512-cPLl+qZpSc+ireUvt+IzqbED1cHHkDoVYMo30jbJIdOOjQ1MQYZBPiNvmi8UM6lJuOpTPXJGZQk0DtC4y61MYQ==", + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", "dev": true, "license": "MIT", "dependencies": { - "passport-strategy": "1.x.x", - "pause": "0.0.1", - "utils-merge": "^1.0.1" + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" }, "engines": { - "node": ">= 0.4.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/jaredhanson" + "node": ">= 0.10" } }, - "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==", + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", "dev": true, + "license": "MIT" + }, + "node_modules/pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "dev": true, + "license": "MIT" + }, + "node_modules/pump": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz", + "integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==", "license": "MIT", "dependencies": { - "jsonwebtoken": "^9.0.0", - "passport-strategy": "^1.0.0" + "end-of-stream": "^1.1.0", + "once": "^1.3.1" } }, - "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==", + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "dev": true, + "license": "MIT", "engines": { - "node": ">= 0.4.0" + "node": ">=6" } }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "node_modules/punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", "dev": true, "license": "MIT", "engines": { - "node": ">=8" + "node": ">=6" } }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } + "node_modules/pure-rand": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", + "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT" }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "node_modules/q": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/q/-/q-1.1.2.tgz", + "integrity": "sha512-ROtylwux7Vkc4C07oKE/ReigUmb33kVoLtcR4SJ1QVqwaZkBEDL3vX4/kwFzIERQ5PfCl0XafbU8u2YUhyGgVA==", + "deprecated": "You or someone you depend on is using Q, the JavaScript Promise library that gave JavaScript developers strong feelings about promises. They can almost certainly migrate to the native JavaScript promise now. Thank you literally everyone for joining me in this bet against the odds. Be excellent to each other.\n\n(For a CapTP with native promises, see @endo/eventual-send and @endo/captp)", "dev": true, "license": "MIT", "engines": { - "node": ">=8" + "node": ">=0.6.0", + "teleport": ">=0.2.0" } }, - "node_modules/path-scurry": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", - "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "node_modules/qrcode-terminal": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/qrcode-terminal/-/qrcode-terminal-0.12.0.tgz", + "integrity": "sha512-EXtzRZmC+YGmGlDFbXKxQiMZNwCLEO6BANKXG4iCtSIM0yqc/pappSx3RIKr4r0uh5JsBckOXeKrB3Iz7mdQpQ==", "dev": true, - "license": "BlueOak-1.0.0", + "bin": { + "qrcode-terminal": "bin/qrcode-terminal.js" + } + }, + "node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "license": "BSD-3-Clause", "dependencies": { - "lru-cache": "^10.2.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + "side-channel": "^1.0.6" }, "engines": { - "node": ">=16 || 14 >=14.18" + "node": ">=0.6" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/path-scurry/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", "dev": true, - "license": "ISC" + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" }, - "node_modules/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==", + "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, "license": "MIT" }, - "node_modules/path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dev": true, + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/rc/node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/read-pkg": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", + "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/normalize-package-data": "^2.4.0", + "normalize-package-data": "^2.5.0", + "parse-json": "^5.0.0", + "type-fest": "^0.6.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg-up": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", + "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", "dev": true, "license": "MIT", + "dependencies": { + "find-up": "^4.1.0", + "read-pkg": "^5.2.0", + "type-fest": "^0.8.1" + }, "engines": { "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/pause": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz", - "integrity": "sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg==", - "dev": true + "node_modules/read-pkg-up/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } }, - "node_modules/pause-stream": { - "version": "0.0.11", - "resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", - "integrity": "sha512-e3FBlXLmN/D1S+zHzanP4E/4Z60oFAa3O051qt1pxa7DEJWKAyil6upYVXCWadEnuoqa4Pkc9oUx9zsxYeRv8A==", + "node_modules/read-pkg-up/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "dev": true, - "license": [ - "MIT", - "Apache2" - ], + "license": "MIT", "dependencies": { - "through": "~2.3" + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" } }, - "node_modules/picomatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", - "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "node_modules/read-pkg-up/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "dev": true, "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, "engines": { - "node": ">=12" + "node": ">=6" }, "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/pino": { - "version": "8.21.0", - "resolved": "https://registry.npmjs.org/pino/-/pino-8.21.0.tgz", - "integrity": "sha512-ip4qdzjkAyDDZklUaZkcRFb2iA118H9SgRh8yzTkSQK8HilsOJF7rSY8HoW5+I0M46AZgX/pxbprf2vvzQCE0Q==", + "node_modules/read-pkg-up/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "dev": true, "license": "MIT", "dependencies": { - "atomic-sleep": "^1.0.0", - "fast-redact": "^3.1.1", - "on-exit-leak-free": "^2.1.0", - "pino-abstract-transport": "^1.2.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.6.0" + "p-limit": "^2.2.0" }, - "bin": { - "pino": "bin.js" + "engines": { + "node": ">=8" } }, - "node_modules/pino-abstract-transport": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-1.2.0.tgz", - "integrity": "sha512-Guhh8EZfPCfH+PMXAb6rKOjGQEoy0xlAIn+irODG5kgfYV+BQ0rGYYWTIel3P5mmyXqkYkPmdIkywsn6QKUR1Q==", + "node_modules/read-pkg-up/node_modules/type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", "dev": true, - "license": "MIT", - "dependencies": { - "readable-stream": "^4.0.0", - "split2": "^4.0.0" + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=8" } }, - "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==", + "node_modules/read-pkg/node_modules/type-fest": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", + "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=8" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "license": "MIT", "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" } }, - "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==", + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", "dev": true, "license": "MIT", "dependencies": { - "abort-controller": "^3.0.0", - "buffer": "^6.0.3", - "events": "^3.3.0", - "process": "^0.11.10", - "string_decoder": "^1.3.0" + "picomatch": "^2.2.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": ">=8.10.0" } }, - "node_modules/pino-abstract-transport/node_modules/split2": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", - "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "node_modules/readdirp/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "dev": true, - "license": "ISC", + "license": "MIT", "engines": { - "node": ">= 10.x" + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" } }, - "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==", + "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, - "license": "MIT" - }, - "node_modules/possible-typed-array-names": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", - "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", "license": "MIT", "engines": { - "node": ">= 0.4" + "node": ">= 12.13.0" } }, - "node_modules/prebuild-install": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.2.tgz", - "integrity": "sha512-UnNke3IQb6sgarcZIDU3gbMeTp/9SSU1DAIkil7PrqG1vZlBtY5msYccSKSHDqa3hNg436IXK+SNImReuA1wEQ==", + "node_modules/rechoir": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", + "integrity": "sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==", "dev": true, - "license": "MIT", "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" + "resolve": "^1.1.6" }, "engines": { - "node": ">=10" + "node": ">= 0.10" } }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "node_modules/refa": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/refa/-/refa-0.12.1.tgz", + "integrity": "sha512-J8rn6v4DBb2nnFqkqwy6/NnTYMcgLA+sLr0iIO41qpv0n+ngb7ksag2tMRl0inb1bbO/esUwzW1vbJi7K0sI0g==", "dev": true, "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.8.0" + }, "engines": { - "node": ">= 0.8.0" + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, - "node_modules/proc-log": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-3.0.0.tgz", - "integrity": "sha512-++Vn7NS4Xf9NacaU9Xq3URUuqZETPsf8L4j5/ckhaRYsfPeRyzGw+iDjFhV/Jr3uNmTvvddEJFWh5R1gRgUH8A==", + "node_modules/reflect-metadata": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", + "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==", "dev": true, - "license": "ISC", + "license": "Apache-2.0" + }, + "node_modules/regex": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/regex/-/regex-4.3.2.tgz", + "integrity": "sha512-kK/AA3A9K6q2js89+VMymcboLOlF5lZRCYJv3gzszXFHBr6kO6qLGzbm+UIugBEV8SMMKCTR59txoY6ctRHYVw==", + "dev": true, + "license": "MIT" + }, + "node_modules/regexp-ast-analysis": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/regexp-ast-analysis/-/regexp-ast-analysis-0.7.1.tgz", + "integrity": "sha512-sZuz1dYW/ZsfG17WSAG7eS85r5a0dDsvg+7BiiYR5o6lKCAtUrEwdmRmaGF6rwVj3LcmAeYkOWKEPlbPzN3Y3A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.8.0", + "refa": "^0.12.1" + }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^12.0.0 || ^14.0.0 || >=16.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==", + "node_modules/regexp-tree": { + "version": "0.1.27", + "resolved": "https://registry.npmjs.org/regexp-tree/-/regexp-tree-0.1.27.tgz", + "integrity": "sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA==", "dev": true, "license": "MIT", + "bin": { + "regexp-tree": "bin/regexp-tree" + } + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz", + "integrity": "sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.6", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "set-function-name": "^2.0.1" + }, "engines": { - "node": ">= 0.6.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "license": "MIT" + "node_modules/regjsparser": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.10.0.tgz", + "integrity": "sha512-qx+xQGZVsy55CH0a1hiVwHmqjLryfh7wQyF5HO07XJ9f7dQMY/gPQHhlyDkIzJKC+x2fUCpCcUODUUUFrm7SHA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "jsesc": "~0.5.0" + }, + "bin": { + "regjsparser": "bin/parser" + } }, - "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==", + "node_modules/regjsparser/node_modules/jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", "dev": true, + "bin": { + "jsesc": "bin/jsesc" + } + }, + "node_modules/reinterval": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reinterval/-/reinterval-1.1.0.tgz", + "integrity": "sha512-QIRet3SYrGp0HUHO88jVskiG6seqUGC5iAG7AwI/BV4ypGcuqk9Du6YQBUOUqm9c8pw1eyLoIaONifRua1lsEQ==", "license": "MIT" }, - "node_modules/progress": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", "dev": true, "license": "MIT", "engines": { - "node": ">=0.4.0" + "node": ">=0.10.0" } }, - "node_modules/promise-inflight": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", - "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", "dev": true, - "license": "ISC" + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } }, - "node_modules/promise-retry": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", - "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", "dev": true, "license": "MIT", "dependencies": { - "err-code": "^2.0.2", - "retry": "^0.12.0" + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" }, - "engines": { - "node": ">=10" + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/prompts-ncu": { + "node_modules/resolve-cwd": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/prompts-ncu/-/prompts-ncu-3.0.0.tgz", - "integrity": "sha512-qyz9UxZ5MlPKWVhWrCmSZ1ahm2GVYdjLb8og2sg0IPth1KRuhcggHGuijz0e41dkx35p1t1q3GRISGH7QGALFA==", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", "dev": true, "license": "MIT", "dependencies": { - "kleur": "^4.0.1", - "sisteransi": "^1.0.5" + "resolve-from": "^5.0.0" }, "engines": { - "node": ">= 14" + "node": ">=8" } }, - "node_modules/proto-list": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", - "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==", - "dev": true, - "license": "ISC" - }, - "node_modules/proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "node_modules/resolve-cwd/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", "dev": true, "license": "MIT", - "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - }, "engines": { - "node": ">= 0.10" + "node": ">=8" } }, - "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "dev": true, - "license": "MIT" - }, - "node_modules/pstree.remy": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", - "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true, - "license": "MIT" - }, - "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==", "license": "MIT", - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" + "engines": { + "node": ">=4" } }, - "node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", "dev": true, "license": "MIT", - "engines": { - "node": ">=6" + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" } }, - "node_modules/punycode.js": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", - "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", + "node_modules/resolve.exports": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz", + "integrity": "sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==", "dev": true, "license": "MIT", "engines": { - "node": ">=6" + "node": ">=10" } }, - "node_modules/pupa": { + "node_modules/restore-cursor": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/pupa/-/pupa-3.1.0.tgz", - "integrity": "sha512-FLpr4flz5xZTSJxSeaheeMKN/EDzMdK7b8PTOC6a5PYFKTucWbdqjgqaEyH0shFiSJrVB1+Qqi4Tk19ccU6Aug==", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", "dev": true, "license": "MIT", "dependencies": { - "escape-goat": "^4.0.0" + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" }, "engines": { - "node": ">=12.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=8" } }, - "node_modules/q": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/q/-/q-1.1.2.tgz", - "integrity": "sha512-ROtylwux7Vkc4C07oKE/ReigUmb33kVoLtcR4SJ1QVqwaZkBEDL3vX4/kwFzIERQ5PfCl0XafbU8u2YUhyGgVA==", - "deprecated": "You or someone you depend on is using Q, the JavaScript Promise library that gave JavaScript developers strong feelings about promises. They can almost certainly migrate to the native JavaScript promise now. Thank you literally everyone for joining me in this bet against the odds. Be excellent to each other.\n\n(For a CapTP with native promises, see @endo/eventual-send and @endo/captp)", + "node_modules/restore-cursor/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/ret": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.4.3.tgz", + "integrity": "sha512-0f4Memo5QP7WQyUEAYUO3esD/XjOc3Zjjg5CPsAq1p8sIu0XPeMbHJemKA0BO7tV0X7+A0FoEpbmHXWxPyD3wQ==", "dev": true, "license": "MIT", "engines": { - "node": ">=0.6.0", - "teleport": ">=0.2.0" + "node": ">=10" } }, - "node_modules/qrcode-terminal": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/qrcode-terminal/-/qrcode-terminal-0.12.0.tgz", - "integrity": "sha512-EXtzRZmC+YGmGlDFbXKxQiMZNwCLEO6BANKXG4iCtSIM0yqc/pappSx3RIKr4r0uh5JsBckOXeKrB3Iz7mdQpQ==", + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", "dev": true, - "bin": { - "qrcode-terminal": "bin/qrcode-terminal.js" + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" } }, - "node_modules/qs": { - "version": "6.12.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.12.3.tgz", - "integrity": "sha512-AWJm14H1vVaO/iNZ4/hO+HyaTehuy9nRqVdkTqlJt0HWvBiBIEXFmb4C0DGeYo3Xes9rrEW+TxHsaigCbN5ICQ==", - "license": "BSD-3-Clause", + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "license": "MIT" + }, + "node_modules/rollup": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.21.2.tgz", + "integrity": "sha512-e3TapAgYf9xjdLvKQCkQTnbTKd4a6jwlpQSJJFokHGaX2IVjoEqkIIhiQfqsi0cdwlOD+tQGuOd5AJkc5RngBw==", + "dev": true, + "license": "MIT", "dependencies": { - "side-channel": "^1.0.6" + "@types/estree": "1.0.5" + }, + "bin": { + "rollup": "dist/bin/rollup" }, "engines": { - "node": ">=0.6" + "node": ">=18.0.0", + "npm": ">=8.0.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.21.2", + "@rollup/rollup-android-arm64": "4.21.2", + "@rollup/rollup-darwin-arm64": "4.21.2", + "@rollup/rollup-darwin-x64": "4.21.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.21.2", + "@rollup/rollup-linux-arm-musleabihf": "4.21.2", + "@rollup/rollup-linux-arm64-gnu": "4.21.2", + "@rollup/rollup-linux-arm64-musl": "4.21.2", + "@rollup/rollup-linux-powerpc64le-gnu": "4.21.2", + "@rollup/rollup-linux-riscv64-gnu": "4.21.2", + "@rollup/rollup-linux-s390x-gnu": "4.21.2", + "@rollup/rollup-linux-x64-gnu": "4.21.2", + "@rollup/rollup-linux-x64-musl": "4.21.2", + "@rollup/rollup-win32-arm64-msvc": "4.21.2", + "@rollup/rollup-win32-ia32-msvc": "4.21.2", + "@rollup/rollup-win32-x64-msvc": "4.21.2", + "fsevents": "~2.3.2" } }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", "dev": true, "funding": [ { @@ -9240,2084 +13384,2201 @@ "url": "https://feross.org/support" } ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/rxjs": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], "license": "MIT" }, - "node_modules/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==", + "node_modules/safe-regex2": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/safe-regex2/-/safe-regex2-3.1.0.tgz", + "integrity": "sha512-RAAZAGbap2kBfbVhvmnTFv73NWLMvDGOITFYTZBAaY8eR+Ir4ef7Up/e7amo+y1+AH+3PtLkrt9mvcTsG9LXug==", "dev": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "ret": "~0.4.0" + } }, - "node_modules/quick-lru": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", - "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "node_modules/safe-stable-stringify": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/sax": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", + "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==", "dev": true, + "license": "ISC" + }, + "node_modules/scslre": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/scslre/-/scslre-0.3.0.tgz", + "integrity": "sha512-3A6sD0WYP7+QrjbfNA2FN3FsOaGGFoekCVgTyypy53gPxhbkCIjtO6YWgdrfM+n/8sI8JeXZOIxsHjMTNxQ4nQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.8.0", + "refa": "^0.12.0", + "regexp-ast-analysis": "^0.7.0" + }, + "engines": { + "node": "^14.0.0 || >=16.0.0" + } + }, + "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, + "license": "BSD-3-Clause" + }, + "node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/serialport": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/serialport/-/serialport-12.0.0.tgz", + "integrity": "sha512-AmH3D9hHPFmnF/oq/rvigfiAouAKyK/TjnrkwZRYSFZxNggJxwvbAbfYrLeuvq7ktUdhuHdVdSjj852Z55R+uA==", + "license": "MIT", + "optional": true, + "dependencies": { + "@serialport/binding-mock": "10.2.2", + "@serialport/bindings-cpp": "12.0.1", + "@serialport/parser-byte-length": "12.0.0", + "@serialport/parser-cctalk": "12.0.0", + "@serialport/parser-delimiter": "12.0.0", + "@serialport/parser-inter-byte-timeout": "12.0.0", + "@serialport/parser-packet-length": "12.0.0", + "@serialport/parser-readline": "12.0.0", + "@serialport/parser-ready": "12.0.0", + "@serialport/parser-regex": "12.0.0", + "@serialport/parser-slip-encoder": "12.0.0", + "@serialport/parser-spacepacket": "12.0.0", + "@serialport/stream": "12.0.0", + "debug": "4.3.4" + }, + "engines": { + "node": ">=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/serialport/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "license": "MIT", + "optional": true, + "dependencies": { + "ms": "2.1.2" + }, "engines": { - "node": ">=10" + "node": ">=6.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/rc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "node_modules/serialport/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "license": "MIT", + "optional": true + }, + "node_modules/set-cookie-parser": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.0.tgz", + "integrity": "sha512-lXLOiqpkUumhRdFF3k1osNXCy9akgx/dyPZ5p8qAg9seJzXr5ZrlqZuWIMuY6ejOsVLE6flJ5/h3lsn57fQ/PQ==", "dev": true, - "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "license": "MIT" + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "license": "MIT", "dependencies": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" }, - "bin": { - "rc": "cli.js" + "engines": { + "node": ">= 0.4" } }, - "node_modules/rc-config-loader": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/rc-config-loader/-/rc-config-loader-4.1.3.tgz", - "integrity": "sha512-kD7FqML7l800i6pS6pvLyIE2ncbk9Du8Q0gp/4hMPhJU6ZxApkoLcGD8ZeqgiAlfwZ6BlETq6qqe+12DUL207w==", - "dev": true, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", "license": "MIT", "dependencies": { - "debug": "^4.3.4", - "js-yaml": "^4.1.0", - "json5": "^2.2.2", - "require-from-string": "^2.0.2" + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" } }, - "node_modules/rc/node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "dev": true, "license": "ISC" }, - "node_modules/rc/node_modules/strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/read-package-json": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/read-package-json/-/read-package-json-6.0.4.tgz", - "integrity": "sha512-AEtWXYfopBj2z5N5PbkAOeNHRPUg5q+Nen7QLxV8M2zJq1ym6/lCz3fYNTCXe19puu2d06jfHhrP7v/S2PtMMw==", - "deprecated": "This package is no longer supported. Please use @npmcli/package-json instead.", - "dev": true, - "license": "ISC", "dependencies": { - "glob": "^10.2.2", - "json-parse-even-better-errors": "^3.0.0", - "normalize-package-data": "^5.0.0", - "npm-normalize-package-bin": "^3.0.0" + "shebang-regex": "^3.0.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": ">=8" } }, - "node_modules/read-package-json-fast": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/read-package-json-fast/-/read-package-json-fast-3.0.2.tgz", - "integrity": "sha512-0J+Msgym3vrLOUB3hzQCuZHII0xkNGCtz/HJH9xZshwv9DbDwkw1KaE3gx/e2J5rpEY5rtOy6cyhKOPrkP7FZw==", + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true, - "license": "ISC", - "dependencies": { - "json-parse-even-better-errors": "^3.0.0", - "npm-normalize-package-bin": "^3.0.0" - }, + "license": "MIT", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": ">=8" } }, - "node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "license": "MIT", + "node_modules/shelljs": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.5.tgz", + "integrity": "sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==", + "dev": true, + "license": "BSD-3-Clause", "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" + "glob": "^7.0.0", + "interpret": "^1.0.0", + "rechoir": "^0.6.2" + }, + "bin": { + "shjs": "bin/shjs" }, "engines": { - "node": ">= 6" + "node": ">=4" } }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "node_modules/shelljs/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, "license": "MIT", "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, - "node_modules/readdirp/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "node_modules/shelljs/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", "dev": true, - "license": "MIT", + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, "engines": { - "node": ">=8.6" + "node": "*" }, "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "url": "https://github.com/sponsors/isaacs" } }, - "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==", + "node_modules/shelljs/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, - "license": "MIT", + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, "engines": { - "node": ">= 12.13.0" + "node": "*" } }, - "node_modules/reflect-metadata": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", - "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==", + "node_modules/shiki": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-1.17.0.tgz", + "integrity": "sha512-VZf8cPShRwfzPcaswv81+YP7qJEoFwRT+Ehy6bizim7M0zG9bk8Egug550C+xS9g7rKIOPhzAlp2uEyuCxbk/A==", "dev": true, - "license": "Apache-2.0" - }, - "node_modules/regexp.prototype.flags": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz", - "integrity": "sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==", "license": "MIT", "dependencies": { - "call-bind": "^1.0.6", - "define-properties": "^1.2.1", - "es-errors": "^1.3.0", - "set-function-name": "^2.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "@shikijs/core": "1.17.0", + "@shikijs/types": "1.17.0", + "@shikijs/vscode-textmate": "^9.2.2", + "@types/hast": "^3.0.4" } }, - "node_modules/registry-auth-token": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-5.0.2.tgz", - "integrity": "sha512-o/3ikDxtXaA59BmZuZrJZDJv8NMDGSj+6j6XaeBmHw8eY1i1qd9+6H+LjVvQXx3HN6aRCGa1cUdJ9RaJZUugnQ==", + "node_modules/shx": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/shx/-/shx-0.3.4.tgz", + "integrity": "sha512-N6A9MLVqjxZYcVn8hLmtneQWIJtp8IKzMP4eMnx+nqkvXoqinUPCbUFLp2UcWTEIUONhlk0ewxr/jaVGlc+J+g==", "dev": true, "license": "MIT", "dependencies": { - "@pnpm/npm-conf": "^2.1.0" + "minimist": "^1.2.3", + "shelljs": "^0.8.5" + }, + "bin": { + "shx": "lib/cli.js" }, "engines": { - "node": ">=14" + "node": ">=6" } }, - "node_modules/registry-url": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-6.0.1.tgz", - "integrity": "sha512-+crtS5QjFRqFCoQmvGduwYWEBng99ZvmFvF+cUJkGYF1L1BfU8C6Zp9T7f5vPAwyLkUExpvK+ANVZmGU49qi4Q==", - "dev": true, + "node_modules/side-channel": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", "license": "MIT", "dependencies": { - "rc": "1.2.8" + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" }, "engines": { - "node": ">=12" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/reinterval": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/reinterval/-/reinterval-1.1.0.tgz", - "integrity": "sha512-QIRet3SYrGp0HUHO88jVskiG6seqUGC5iAG7AwI/BV4ypGcuqk9Du6YQBUOUqm9c8pw1eyLoIaONifRua1lsEQ==", - "license": "MIT" - }, - "node_modules/remote-git-tags": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/remote-git-tags/-/remote-git-tags-3.0.0.tgz", - "integrity": "sha512-C9hAO4eoEsX+OXA4rla66pXZQ+TLQ8T9dttgQj18yuKlPMTVkIkdYXvlMC55IuUsIkV6DpmQYi10JKFLaU+l7w==", + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } + "license": "ISC" }, - "node_modules/require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "dev": true, - "license": "MIT", + "license": "ISC", "engines": { - "node": ">=0.10.0" + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/resolve-alpn": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", - "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", + "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" + } + ], "license": "MIT" }, - "node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "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" + } + ], "license": "MIT", - "engines": { - "node": ">=4" + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" } }, - "node_modules/responselike": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/responselike/-/responselike-3.0.0.tgz", - "integrity": "sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg==", + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", "dev": true, "license": "MIT", "dependencies": { - "lowercase-keys": "^3.0.0" + "semver": "^7.5.3" }, "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=10" } }, - "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==", + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true, + "license": "MIT" + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", "dev": true, "license": "MIT", - "dependencies": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" - }, "engines": { "node": ">=8" } }, - "node_modules/restore-cursor/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==", + "node_modules/slashes": { + "version": "3.0.12", + "resolved": "https://registry.npmjs.org/slashes/-/slashes-3.0.12.tgz", + "integrity": "sha512-Q9VME8WyGkc7pJf6QEkj3wE+2CnvZMI+XJhwdTPR8Z/kWQRXi7boAWLDibRPyHRTUTPx5FaU7MsyrjI3yLB4HA==", "dev": true, "license": "ISC" }, - "node_modules/ret": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/ret/-/ret-0.4.3.tgz", - "integrity": "sha512-0f4Memo5QP7WQyUEAYUO3esD/XjOc3Zjjg5CPsAq1p8sIu0XPeMbHJemKA0BO7tV0X7+A0FoEpbmHXWxPyD3wQ==", + "node_modules/socket.io": { + "version": "4.7.5", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.7.5.tgz", + "integrity": "sha512-DmeAkF6cwM9jSfmp6Dr/5/mfMwb5Z5qRrSXLpo3Fq5SqyU8CMF15jIN4ZhfSwu35ksM1qmHZDQ/DK5XTccSTvA==", "dev": true, "license": "MIT", + "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" + "node": ">=10.2.0" } }, - "node_modules/retry": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", - "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "node_modules/socket.io-adapter": { + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz", + "integrity": "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==", "dev": true, "license": "MIT", - "engines": { - "node": ">= 4" + "dependencies": { + "debug": "~4.3.4", + "ws": "~8.17.1" } }, - "node_modules/reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "node_modules/socket.io-adapter/node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", "dev": true, "license": "MIT", "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" + "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/rfdc": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", - "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", - "license": "MIT" - }, - "node_modules/rimraf": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-6.0.1.tgz", - "integrity": "sha512-9dkvaxAsk/xNXSJzMgFqqMCuFgt2+KsOFek3TMLfo8NCPfWpBmqwyNn5Y+NX56QUYfCtsyhF3ayiboEoUmJk/A==", + "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, - "license": "ISC", + "license": "MIT", "dependencies": { - "glob": "^11.0.0", - "package-json-from-dist": "^1.0.0" - }, - "bin": { - "rimraf": "dist/esm/bin.mjs" + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" }, "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">=10.0.0" } }, - "node_modules/rimraf/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "node_modules/sonic-boom": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.1.0.tgz", + "integrity": "sha512-NGipjjRicyJJ03rPiZCJYjwlsuP2d1/5QUviozRXC7S3WdVWNK5e3Ojieb9CCyfhq2UC+3+SRd9nG3I2lPRvUw==", "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0" + "atomic-sleep": "^1.0.0" } }, - "node_modules/rimraf/node_modules/glob": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.0.tgz", - "integrity": "sha512-9UiX/Bl6J2yaBbxKoEBRm4Cipxgok8kQYcOPEhScPwebu2I0HoQOuYdIO6S3hLuWoZgpDpwQZMzTFxgpkyT76g==", + "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, - "license": "ISC", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^4.0.1", - "minimatch": "^10.0.0", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^2.0.0" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } + "license": "MIT" }, - "node_modules/rimraf/node_modules/jackspeak": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.0.1.tgz", - "integrity": "sha512-cub8rahkh0Q/bw1+GxP7aeSe29hHHn2V4m29nnDlvCdlgU+3UGxkZp7Z53jLUdpX3jdTO0nJZUDl3xvbWc2Xog==", + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, + "license": "BSD-3-Clause", "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" + "node": ">=0.10.0" } }, - "node_modules/rimraf/node_modules/lru-cache": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.0.0.tgz", - "integrity": "sha512-Qv32eSV1RSCfhY3fpPE2GNZ8jgM9X7rdAfemLWqTUxwiyIC4jJ6Sy0fZ8H+oLWevO6i4/bizg7c8d8i6bxrzbA==", + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "dev": true, - "license": "ISC", + "license": "BSD-3-Clause", "engines": { - "node": "20 || >=22" + "node": ">=0.10.0" } }, - "node_modules/rimraf/node_modules/minimatch": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz", - "integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==", + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" } }, - "node_modules/rimraf/node_modules/path-scurry": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz", - "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==", + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "lru-cache": "^11.0.0", - "minipass": "^7.1.2" - }, - "engines": { - "node": "20 || >=22" - }, + "license": "MIT", "funding": { - "url": "https://github.com/sponsors/isaacs" + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "node_modules/spdx-correct": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", + "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "node_modules/rxjs": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", - "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", "license": "Apache-2.0", "dependencies": { - "tslib": "^2.1.0" + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" } }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/safe-regex2": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/safe-regex2/-/safe-regex2-3.1.0.tgz", - "integrity": "sha512-RAAZAGbap2kBfbVhvmnTFv73NWLMvDGOITFYTZBAaY8eR+Ir4ef7Up/e7amo+y1+AH+3PtLkrt9mvcTsG9LXug==", + "node_modules/spdx-correct/node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", "dev": true, "license": "MIT", "dependencies": { - "ret": "~0.4.0" + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.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==", + "node_modules/spdx-exceptions": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", + "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - } + "license": "CC-BY-3.0" }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "node_modules/spdx-expression-parse": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-4.0.0.tgz", + "integrity": "sha512-Clya5JIij/7C6bRR22+tnGXbc4VKlibKSVj2iHvVeX5iMW7s1SIQlqu699JkODJJIhh/pUu8L0/VLh8xflD+LQ==", + "dev": true, "license": "MIT", - "optional": true + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } }, - "node_modules/sax": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", - "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==", + "node_modules/spdx-license-ids": { + "version": "3.0.20", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.20.tgz", + "integrity": "sha512-jg25NiDV/1fLtSgEgyvVyDunvaNHbuwF9lfNV17gSmPFAlYzdfNBlLtLzXTevwkPj7DhGbmN9VnmJIgLnhvaBw==", "dev": true, - "license": "ISC" + "license": "CC0-1.0" }, - "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==", + "node_modules/split": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", + "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==", "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" + "license": "MIT", + "dependencies": { + "through": "2" }, "engines": { - "node": ">=10" + "node": "*" } }, - "node_modules/semver-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-4.0.0.tgz", - "integrity": "sha512-0Ju4+6A8iOnpL/Thra7dZsSlOHYAHIeMxfhWQRI1/VLcT3WDBZKKtQt/QkBOsiIN9ZpuvHE6cGZ0x4glCMmfiA==", - "dev": true, - "license": "MIT", + "node_modules/split2": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/split2/-/split2-3.2.2.tgz", + "integrity": "sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==", + "license": "ISC", "dependencies": { - "semver": "^7.3.5" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "readable-stream": "^3.0.0" } }, - "node_modules/semver-utils": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/semver-utils/-/semver-utils-1.1.4.tgz", - "integrity": "sha512-EjnoLE5OGmDAVV/8YDoN5KiajNadjzIp9BAHOhYeQHt7j0UWxjmgsx4YD48wp4Ue1Qogq38F1GNUJNqF1kKKxA==", + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/stable-hash": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.4.tgz", + "integrity": "sha512-LjdcbuBeLcdETCrPn9i8AYAZ1eCtu4ECAWtP7UleOiZ9LzVxRzzUZEoZ8zB24nhkQnDWyET0I+3sWokSDS3E7g==", "dev": true, - "license": "APACHEv2" + "license": "MIT" }, - "node_modules/serialport": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/serialport/-/serialport-12.0.0.tgz", - "integrity": "sha512-AmH3D9hHPFmnF/oq/rvigfiAouAKyK/TjnrkwZRYSFZxNggJxwvbAbfYrLeuvq7ktUdhuHdVdSjj852Z55R+uA==", + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, "license": "MIT", - "optional": true, "dependencies": { - "@serialport/binding-mock": "10.2.2", - "@serialport/bindings-cpp": "12.0.1", - "@serialport/parser-byte-length": "12.0.0", - "@serialport/parser-cctalk": "12.0.0", - "@serialport/parser-delimiter": "12.0.0", - "@serialport/parser-inter-byte-timeout": "12.0.0", - "@serialport/parser-packet-length": "12.0.0", - "@serialport/parser-readline": "12.0.0", - "@serialport/parser-ready": "12.0.0", - "@serialport/parser-regex": "12.0.0", - "@serialport/parser-slip-encoder": "12.0.0", - "@serialport/parser-spacepacket": "12.0.0", - "@serialport/stream": "12.0.0", - "debug": "4.3.4" + "escape-string-regexp": "^2.0.0" }, "engines": { - "node": ">=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/serialport/donate" + "node": ">=10" } }, - "node_modules/serialport/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "node_modules/stack-utils/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, "license": "MIT", - "optional": true, - "dependencies": { - "ms": "2.1.2" - }, "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "node": ">=8" } }, - "node_modules/set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", "dev": true, - "license": "ISC" + "license": "MIT" }, - "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==", + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/std-env": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.7.0.tgz", + "integrity": "sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==", "dev": true, "license": "MIT" }, - "node_modules/set-function-length": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", - "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "node_modules/stop-iteration-iterator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz", + "integrity": "sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ==", "license": "MIT", "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.2" + "internal-slot": "^1.0.4" }, "engines": { "node": ">= 0.4" } }, - "node_modules/set-function-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", - "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "node_modules/stream-combiner": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.2.2.tgz", + "integrity": "sha512-6yHMqgLYDzQDcAkL+tjJDC5nSNuNIx0vZtRZeiPh7Saef7VHX9H5Ijn9l2VIol2zaNYlYEX6KyuT/237A58qEQ==", + "dev": true, "license": "MIT", "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "functions-have-names": "^1.2.3", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" + "duplexer": "~0.1.1", + "through": "~2.3.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/stream-shift": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.3.tgz", + "integrity": "sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==", "license": "MIT" }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "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, - "license": "ISC" + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", "license": "MIT", "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" + "safe-buffer": "~5.2.0" } }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", "dev": true, "license": "MIT", + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, "engines": { - "node": ">=8" + "node": ">=10" } }, - "node_modules/shiki": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/shiki/-/shiki-1.10.3.tgz", - "integrity": "sha512-eneCLncGuvPdTutJuLyUGS8QNPAVFO5Trvld2wgEq1e002mwctAhJKeMGWtWVXOIEzmlcLRqcgPSorR6AVzOmQ==", + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", "dev": true, "license": "MIT", "dependencies": { - "@shikijs/core": "1.10.3", - "@types/hast": "^3.0.4" + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/side-channel": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", - "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.4", - "object-inspect": "^1.13.1" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" }, "engines": { - "node": ">= 0.4" + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, - "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==", + "node_modules/string-width/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", "dev": true, - "license": "ISC", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, "engines": { - "node": ">=14" + "node": ">=12" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/sigstore": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/sigstore/-/sigstore-1.9.0.tgz", - "integrity": "sha512-0Zjz0oe37d08VeOtBIuB6cRriqXse2e8w+7yIy2XSXjshRKxbc2KkhXjL229jXSxEm7UbcjS76wcJDGQddVI9A==", + "node_modules/stringify-entities": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "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" + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "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" - } - ], - "license": "MIT" - }, - "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==", + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], "license": "MIT", "dependencies": { - "decompress-response": "^6.0.0", - "once": "^1.3.1", - "simple-concat": "^1.0.0" + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" } }, - "node_modules/simple-update-notifier": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", - "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "license": "MIT", "dependencies": { - "semver": "^7.5.3" + "ansi-regex": "^5.0.1" }, "engines": { - "node": ">=10" + "node": ">=8" } }, - "node_modules/sisteransi": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", - "dev": true, - "license": "MIT" - }, - "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", "dev": true, "license": "MIT", "engines": { "node": ">=8" } }, - "node_modules/smart-buffer": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", - "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", "dev": true, "license": "MIT", "engines": { - "node": ">= 6.0.0", - "npm": ">= 3.0.0" + "node": ">=6" } }, - "node_modules/socket.io": { - "version": "4.7.5", - "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.7.5.tgz", - "integrity": "sha512-DmeAkF6cwM9jSfmp6Dr/5/mfMwb5Z5qRrSXLpo3Fq5SqyU8CMF15jIN4ZhfSwu35ksM1qmHZDQ/DK5XTccSTvA==", + "node_modules/strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", "dev": true, "license": "MIT", "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" + "min-indent": "^1.0.0" }, "engines": { - "node": ">=10.2.0" + "node": ">=8" } }, - "node_modules/socket.io-adapter": { - "version": "2.5.5", - "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz", - "integrity": "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==", + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true, "license": "MIT", - "dependencies": { - "debug": "~4.3.4", - "ws": "~8.17.1" + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/socket.io-adapter/node_modules/ws": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", - "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "license": "MIT", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" + "dependencies": { + "has-flag": "^4.0.0" }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } + "engines": { + "node": ">=8" } }, - "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==", + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", "dev": true, "license": "MIT", - "dependencies": { - "@socket.io/component-emitter": "~3.1.0", - "debug": "~4.3.1" - }, "engines": { - "node": ">=10.0.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/socks": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.3.tgz", - "integrity": "sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==", + "node_modules/swagger-ui-dist": { + "version": "5.17.14", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.17.14.tgz", + "integrity": "sha512-CVbSfaLpstV65OnSjbXfVd6Sta3q3F7Cj/yYuvHMp1P90LztOLs6PfUnKEVAeiIVQt9u2SaPwv0LiH/OyMjHRw==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/synckit": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.9.1.tgz", + "integrity": "sha512-7gr8p9TQP6RAHusBOSLs46F4564ZrjV8xFmw5zCmgmhGUcw2hxsShhJ6CEiHQMgPDwAQ1fWHPM0ypc4RMAig4A==", "dev": true, "license": "MIT", "dependencies": { - "ip-address": "^9.0.5", - "smart-buffer": "^4.2.0" + "@pkgr/core": "^0.1.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">= 10.0.0", - "npm": ">= 3.0.0" + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" } }, - "node_modules/socks-proxy-agent": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-7.0.0.tgz", - "integrity": "sha512-Fgl0YPZ902wEsAyiQ+idGd1A7rSFx/ayC1CQVMw5P+EQx2V0SgpGtf6OKFhVjPflPUl9YMmEOnmfjCdMUsygww==", + "node_modules/systeminformation": { + "version": "5.23.5", + "resolved": "https://registry.npmjs.org/systeminformation/-/systeminformation-5.23.5.tgz", + "integrity": "sha512-PEpJwhRYxZgBCAlWZhWIgfMTjXLqfcaZ1pJsJn9snWNfBW/Z1YQg1mbIUSWrEV3ErAHF7l/OoVLQeaZDlPzkpA==", "dev": true, "license": "MIT", - "dependencies": { - "agent-base": "^6.0.2", - "debug": "^4.3.3", - "socks": "^2.6.2" + "os": [ + "darwin", + "linux", + "win32", + "freebsd", + "openbsd", + "netbsd", + "sunos", + "android" + ], + "bin": { + "systeminformation": "lib/cli.js" }, "engines": { - "node": ">= 10" + "node": ">=8.0.0" + }, + "funding": { + "type": "Buy me a coffee", + "url": "https://www.buymeacoffee.com/systeminfo" } }, - "node_modules/socks-proxy-agent/node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "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, "license": "MIT", - "dependencies": { - "debug": "4" - }, "engines": { "node": ">= 6.0.0" } }, - "node_modules/sonic-boom": { - "version": "3.8.1", - "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-3.8.1.tgz", - "integrity": "sha512-y4Z8LCDBuum+PBP3lSV7RHrXscqksve/bi0as7mhwVnBW+/wUqKT/2Kb7um8yqcFy0duYbbPxzt89Zy2nOCaxg==", + "node_modules/tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", "dev": true, "license": "MIT", - "dependencies": { - "atomic-sleep": "^1.0.0" + "engines": { + "node": ">=6" } }, - "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==", + "node_modules/tar": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", "dev": true, - "license": "MIT" + "license": "ISC", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "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, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" + "license": "MIT", + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" } }, - "node_modules/source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "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, + "license": "ISC" + }, + "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, "license": "MIT", "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" + "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/spawn-please": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/spawn-please/-/spawn-please-2.0.2.tgz", - "integrity": "sha512-KM8coezO6ISQ89c1BzyWNtcn2V2kAVtwIXd3cN/V5a0xPYc1F/vydrRc01wsKFEQ/p+V1a4sw4z2yMITIXrgGw==", + "node_modules/tar/node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", "dev": true, "license": "ISC", - "dependencies": { - "cross-spawn": "^7.0.3" - }, "engines": { - "node": ">=14" + "node": ">=8" } }, - "node_modules/spdx-correct": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", - "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", + "node_modules/tar/node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", "dev": true, - "license": "Apache-2.0", - "dependencies": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" } }, - "node_modules/spdx-exceptions": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", - "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", + "node_modules/tar/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true, - "license": "CC-BY-3.0" + "license": "ISC" }, - "node_modules/spdx-expression-parse": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", - "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "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, "license": "MIT", "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" + "debug": "4.3.1", + "is2": "^2.0.6" } }, - "node_modules/spdx-license-ids": { - "version": "3.0.18", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.18.tgz", - "integrity": "sha512-xxRs31BqRYHwiMzudOrpSiHtZ8i/GeionCBDSilhYRj+9gIcI8wCZTlXZKu9vZIVqViP3dcp9qE5G6AlIaD+TQ==", - "dev": true, - "license": "CC0-1.0" - }, - "node_modules/split": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", - "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==", + "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, "license": "MIT", "dependencies": { - "through": "2" + "ms": "2.1.2" }, "engines": { - "node": "*" - } - }, - "node_modules/split2": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/split2/-/split2-3.2.2.tgz", - "integrity": "sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==", - "license": "ISC", - "dependencies": { - "readable-stream": "^3.0.0" + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/sprintf-js": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", - "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", + "node_modules/tcp-port-used/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true, - "license": "BSD-3-Clause" + "license": "MIT" }, - "node_modules/ssri": { - "version": "10.0.6", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-10.0.6.tgz", - "integrity": "sha512-MGrFH9Z4NP9Iyhqn16sDtBpRRNJ0Y2hNa6D65h736fVSaPCHr4DM4sWUNvVaSuC+0OBGhwsrydQwmgfg5LncqQ==", + "node_modules/test-exclude": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.1.tgz", + "integrity": "sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==", "dev": true, "license": "ISC", "dependencies": { - "minipass": "^7.0.3" + "@istanbuljs/schema": "^0.1.2", + "glob": "^10.4.1", + "minimatch": "^9.0.4" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": ">=18" } }, - "node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "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, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } + "license": "MIT" }, - "node_modules/stop-iteration-iterator": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz", - "integrity": "sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ==", - "license": "MIT", - "dependencies": { - "internal-slot": "^1.0.4" - }, + "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.4" + "node": ">=0.2.6" } }, - "node_modules/stream-combiner": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.2.2.tgz", - "integrity": "sha512-6yHMqgLYDzQDcAkL+tjJDC5nSNuNIx0vZtRZeiPh7Saef7VHX9H5Ijn9l2VIol2zaNYlYEX6KyuT/237A58qEQ==", + "node_modules/thread-stream": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-3.1.0.tgz", + "integrity": "sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==", "dev": true, "license": "MIT", "dependencies": { - "duplexer": "~0.1.1", - "through": "~2.3.4" + "real-require": "^0.2.0" } }, - "node_modules/stream-shift": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.3.tgz", - "integrity": "sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==", + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", + "dev": true, "license": "MIT" }, - "node_modules/stream-wormhole": { + "node_modules/thunky": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/stream-wormhole/-/stream-wormhole-1.1.0.tgz", - "integrity": "sha512-gHFfL3px0Kctd6Po0M8TzEvt3De/xu6cnRrjlfYNhwbhLPLwigI2t1nc6jrzNuaYg5C4YF78PPFuQPzRiqn9ew==", + "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", + "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", + "license": "MIT" + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=4.0.0" - } + "license": "MIT" }, - "node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "node_modules/tinyexec": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.0.tgz", + "integrity": "sha512-tVGE0mVJPGb0chKhqmsoosjsS+qUnJVGJpZgsHYQcGoPlG3B51R3PouqTgEGH2Dc9jjFyOqOpix6ZHNMXp1FZg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinypool": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.0.1.tgz", + "integrity": "sha512-URZYihUbRPcGv95En+sz6MfghfIc2OJ1sv/RmhWZLouPY0/8Vo80viwPvg3dlaS9fuq7fQMEfgRRK7BBZThBEA==", + "dev": true, "license": "MIT", - "dependencies": { - "safe-buffer": "~5.2.0" + "engines": { + "node": "^18.0.0 || >=20.0.0" } }, - "node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "node_modules/tinyrainbow": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-1.2.0.tgz", + "integrity": "sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==", "dev": true, "license": "MIT", - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=14.0.0" } }, - "node_modules/string-width-cjs": { - "name": "string-width", - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "node_modules/tinyspy": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.2.tgz", + "integrity": "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==", "dev": true, "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, "engines": { - "node": ">=8" + "node": ">=14.0.0" } }, - "node_modules/string-width-cjs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", "dev": true, - "license": "MIT" + "license": "BSD-3-Clause" }, - "node_modules/string-width/node_modules/ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", "dev": true, "license": "MIT", "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" + "node": ">=4" } }, - "node_modules/string-width/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dev": true, "license": "MIT", "dependencies": { - "ansi-regex": "^6.0.1" + "is-number": "^7.0.0" }, + "engines": { + "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, + "license": "MIT", "engines": { "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", "dev": true, "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, "engines": { - "node": ">=8" + "node": ">=0.6" } }, - "node_modules/strip-ansi-cjs": { - "name": "strip-ansi", - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "node_modules/toml-eslint-parser": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/toml-eslint-parser/-/toml-eslint-parser-0.10.0.tgz", + "integrity": "sha512-khrZo4buq4qVmsGzS5yQjKe/WsFvV8fGfOjDQN0q4iy9FjRfPWRgTFrU8u1R2iu/SfWLhY9WnCi4Jhdrcbtg+g==", "dev": true, "license": "MIT", "dependencies": { - "ansi-regex": "^5.0.1" + "eslint-visitor-keys": "^3.0.0" }, "engines": { - "node": ">=8" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ota-meshi" } }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "node_modules/toml-eslint-parser/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "engines": { - "node": ">=8" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://opencollective.com/eslint" } }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/touch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", + "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" + "license": "ISC", + "bin": { + "nodetouch": "bin/nodetouch.js" } }, - "node_modules/swagger-ui-dist": { - "version": "5.11.2", - "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.11.2.tgz", - "integrity": "sha512-jQG0cRgJNMZ7aCoiFofnoojeSaa/+KgWaDlfgs8QN+BXoGMpxeMVY5OEnjq4OlNvF3yjftO8c9GRAgcHlO+u7A==", - "dev": true, - "license": "Apache-2.0" + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" }, - "node_modules/systeminformation": { - "version": "5.22.7", - "resolved": "https://registry.npmjs.org/systeminformation/-/systeminformation-5.22.7.tgz", - "integrity": "sha512-AWxlP05KeHbpGdgvZkcudJpsmChc2Y5Eo/GvxG/iUA/Aws5LZKHAMSeAo+V+nD+nxWZaxrwpWcnx4SH3oxNL3A==", + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", "dev": true, "license": "MIT", - "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" + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/tail": { - "version": "2.2.6", - "resolved": "https://registry.npmjs.org/tail/-/tail-2.2.6.tgz", - "integrity": "sha512-IQ6G4wK/t8VBauYiGPLx+d3fA5XjSVagjWV5SIYzvEvglbQjwEcukeYI68JOPpdydjxhZ9sIgzRlSmwSpphHyw==", + "node_modules/ts-api-utils": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", + "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==", "dev": true, "license": "MIT", "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/tar": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", - "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", - "dev": true, - "license": "ISC", - "dependencies": { - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "minipass": "^5.0.0", - "minizlib": "^2.1.1", - "mkdirp": "^1.0.3", - "yallist": "^4.0.0" + "node": ">=16" }, - "engines": { - "node": ">=10" + "peerDependencies": { + "typescript": ">=4.2.0" } }, - "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==", + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", "dev": true, "license": "MIT", "dependencies": { - "chownr": "^1.1.1", - "mkdirp-classic": "^0.5.2", - "pump": "^3.0.0", - "tar-stream": "^2.1.4" + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } } }, - "node_modules/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, - "license": "ISC" + "node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", + "license": "0BSD" }, - "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==", + "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, - "license": "MIT", + "license": "Apache-2.0", "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" + "safe-buffer": "^5.0.1" }, "engines": { - "node": ">=6" + "node": "*" } }, - "node_modules/tar/node_modules/fs-minipass": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", - "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "node_modules/tweetnacl": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", + "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==", "dev": true, - "license": "ISC", + "license": "Unlicense" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", "dependencies": { - "minipass": "^3.0.0" + "prelude-ls": "^1.2.1" }, "engines": { - "node": ">= 8" + "node": ">= 0.8.0" } }, - "node_modules/tar/node_modules/fs-minipass/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=4" } }, - "node_modules/tar/node_modules/minipass": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", - "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", "dev": true, - "license": "ISC", + "license": "(MIT OR CC0-1.0)", "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/tar/node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "node_modules/typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", + "license": "MIT" + }, + "node_modules/typedoc": { + "version": "0.26.7", + "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.26.7.tgz", + "integrity": "sha512-gUeI/Wk99vjXXMi8kanwzyhmeFEGv1LTdTQsiyIsmSYsBebvFxhbcyAx7Zjo4cMbpLGxM4Uz3jVIjksu/I2v6Q==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", + "dependencies": { + "lunr": "^2.3.9", + "markdown-it": "^14.1.0", + "minimatch": "^9.0.5", + "shiki": "^1.16.2", + "yaml": "^2.5.1" + }, "bin": { - "mkdirp": "bin/cmd.js" + "typedoc": "bin/typedoc" }, "engines": { - "node": ">=10" + "node": ">= 18" + }, + "peerDependencies": { + "typescript": "4.6.x || 4.7.x || 4.8.x || 4.9.x || 5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x || 5.6.x" } }, - "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==", + "node_modules/typescript": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.2.tgz", + "integrity": "sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==", "dev": true, - "license": "MIT", - "dependencies": { - "debug": "4.3.1", - "is2": "^2.0.6" + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" } }, - "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==", + "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==", "dev": true, "license": "MIT", - "dependencies": { - "ms": "2.1.2" - }, "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "node": ">=6.0.0" } }, - "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==", + "node_modules/uc.micro": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", "dev": true, "license": "MIT" }, - "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==", + "node_modules/ufo": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.5.4.tgz", + "integrity": "sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==", "dev": true, - "engines": { - "node": ">=0.2.6" - } + "license": "MIT" }, - "node_modules/thread-stream": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-2.7.0.tgz", - "integrity": "sha512-qQiRWsU/wvNolI6tbbCKd9iKaTnCXsTwVxhhKM6nctPdujTyztjlbUkUTUymidWcMnZ5pWR0ej4a0tjsW021vw==", + "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, "license": "MIT", "dependencies": { - "real-require": "^0.2.0" + "@lukeed/csprng": "^1.0.0" + }, + "engines": { + "node": ">=8" } }, - "node_modules/through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", + "node_modules/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", "dev": true, "license": "MIT" }, - "node_modules/thunky": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", - "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", + "node_modules/undici": { + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.19.8.tgz", + "integrity": "sha512-U8uCCl2x9TK3WANvmBavymRzxbfFYG+tAu+fgx3zxQy3qdagQqBLwJVrdyO1TBfUXvfKveMKJZhpvUYoOjM+4g==", + "license": "MIT", + "engines": { + "node": ">=18.17" + } + }, + "node_modules/undici-types": { + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", + "dev": true, "license": "MIT" }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "node_modules/unist-util-is": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz", + "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==", "dev": true, "license": "MIT", "dependencies": { - "is-number": "^7.0.0" + "@types/unist": "^3.0.0" }, - "engines": { - "node": ">=8.0" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "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==", + "node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", "dev": true, "license": "MIT", - "engines": { - "node": ">=12" + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "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==", + "node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", "dev": true, "license": "MIT", - "engines": { - "node": ">=0.6" + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/touch": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", - "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", + "node_modules/unist-util-visit": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", + "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", "dev": true, - "license": "ISC", - "bin": { - "nodetouch": "bin/nodetouch.js" + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "license": "MIT" + "node_modules/unist-util-visit-parents": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz", + "integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } }, - "node_modules/traverse": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.3.9.tgz", - "integrity": "sha512-iawgk0hLP3SxGKDfnDJf8wTz4p2qImnyihM5Hh/sGvQ3K37dPi/w8sRhdNIxYA1TwFwc5mDhIJq+O0RsvXBKdQ==", + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", "dev": true, - "license": "MIT/X11", + "license": "MIT", "engines": { - "node": "*" + "node": ">= 10.0.0" } }, - "node_modules/ts-api-utils": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", - "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==", + "node_modules/unzipper": { + "version": "0.12.3", + "resolved": "https://registry.npmjs.org/unzipper/-/unzipper-0.12.3.tgz", + "integrity": "sha512-PZ8hTS+AqcGxsaQntl3IRBw65QrBI6lxzqDEL7IAo/XCEqRTKGfOX56Vea5TH9SZczRVxuzk1re04z/YjuYCJA==", "dev": true, "license": "MIT", - "engines": { - "node": ">=16" - }, - "peerDependencies": { - "typescript": ">=4.2.0" + "dependencies": { + "bluebird": "~3.7.2", + "duplexer2": "~0.1.4", + "fs-extra": "^11.2.0", + "graceful-fs": "^4.2.2", + "node-int64": "^0.4.0" } }, - "node_modules/ts-node": { - "version": "10.9.2", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", - "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "node_modules/update-browserslist-db": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz", + "integrity": "sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==", "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], "license": "MIT", "dependencies": { - "@cspotcode/source-map-support": "^0.8.0", - "@tsconfig/node10": "^1.0.7", - "@tsconfig/node12": "^1.0.7", - "@tsconfig/node14": "^1.0.0", - "@tsconfig/node16": "^1.0.2", - "acorn": "^8.4.1", - "acorn-walk": "^8.1.1", - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "v8-compile-cache-lib": "^3.0.1", - "yn": "3.1.1" + "escalade": "^3.1.2", + "picocolors": "^1.0.1" }, "bin": { - "ts-node": "dist/bin.js", - "ts-node-cwd": "dist/bin-cwd.js", - "ts-node-esm": "dist/bin-esm.js", - "ts-node-script": "dist/bin-script.js", - "ts-node-transpile-only": "dist/bin-transpile.js", - "ts-script": "dist/bin-script-deprecated.js" + "update-browserslist-db": "cli.js" }, "peerDependencies": { - "@swc/core": ">=1.2.50", - "@swc/wasm": ">=1.2.50", - "@types/node": "*", - "typescript": ">=2.7" - }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "@swc/wasm": { - "optional": true - } + "browserslist": ">= 4.21.0" } }, - "node_modules/tslib": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", - "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", - "license": "0BSD" - }, - "node_modules/tuf-js": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/tuf-js/-/tuf-js-1.1.7.tgz", - "integrity": "sha512-i3P9Kgw3ytjELUfpuKVDNBJvk4u5bXL6gskv572mcevPbSKCV3zt3djhmlEQ65yERjIbOSncy7U4cQJaB1CBCg==", + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/url-template": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/url-template/-/url-template-2.0.8.tgz", + "integrity": "sha512-XdVKMF4SJ0nP/O7XIPB0JwAEuT9lDIYnNsK8yGVe43y0AWoKeJNdv3ZNWh7ksJ6KqQFjOO6ox/VEitLnaVNufw==", + "license": "BSD" + }, + "node_modules/usb": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/usb/-/usb-1.9.2.tgz", + "integrity": "sha512-dryNz030LWBPAf6gj8vyq0Iev3vPbCLHCT8dBw3gQRXRzVNsIdeuU+VjPp3ksmSPkeMAl1k+kQ14Ij0QHyeiAg==", + "hasInstallScript": true, "license": "MIT", + "optional": true, "dependencies": { - "@tufjs/models": "1.0.4", - "debug": "^4.3.4", - "make-fetch-happen": "^11.1.1" + "node-addon-api": "^4.2.0", + "node-gyp-build": "^4.3.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": ">=10.16.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==", + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/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, - "license": "Apache-2.0", - "dependencies": { - "safe-buffer": "^5.0.1" - }, + "license": "MIT", "engines": { - "node": "*" + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" } }, - "node_modules/tweetnacl": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", - "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==", + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", "dev": true, - "license": "Unlicense" + "license": "MIT" }, - "node_modules/type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "node_modules/v8-to-istanbul": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", + "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "prelude-ls": "^1.2.1" + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^2.0.0" }, "engines": { - "node": ">= 0.8.0" + "node": ">=10.12.0" } }, - "node_modules/type-fest": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", - "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "node_modules/validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=12.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "license": "Apache-2.0", + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" } }, - "node_modules/typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", - "license": "MIT" - }, - "node_modules/typedarray-to-buffer": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", - "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "node_modules/validate-npm-package-license/node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", "dev": true, "license": "MIT", "dependencies": { - "is-typedarray": "^1.0.0" + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" } }, - "node_modules/typedoc": { - "version": "0.26.5", - "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.26.5.tgz", - "integrity": "sha512-Vn9YKdjKtDZqSk+by7beZ+xzkkr8T8CYoiasqyt4TTRFy5+UHzL/mF/o4wGBjRF+rlWQHDb0t6xCpA3JNL5phg==", + "node_modules/validator": { + "version": "13.12.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.12.0.tgz", + "integrity": "sha512-c1Q0mCiPlgdTVVVIJIrBuxNicYE+t/7oKeI9MWLj3fh/uq2Pxh/3eeWbVZ4OcGW1TUf53At0njHw5SMdA3tmMg==", "dev": true, - "license": "Apache-2.0", - "dependencies": { - "lunr": "^2.3.9", - "markdown-it": "^14.1.0", - "minimatch": "^9.0.5", - "shiki": "^1.9.1", - "yaml": "^2.4.5" - }, - "bin": { - "typedoc": "bin/typedoc" - }, + "license": "MIT", "engines": { - "node": ">= 18" - }, - "peerDependencies": { - "typescript": "4.6.x || 4.7.x || 4.8.x || 4.9.x || 5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x" + "node": ">= 0.10" } }, - "node_modules/typedoc/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", "dev": true, "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" + "engines": { + "node": ">= 0.8" } }, - "node_modules/typedoc/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/typescript": { - "version": "5.5.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.3.tgz", - "integrity": "sha512-/hreyEujaB0w76zKo6717l3L0o/qEUtRgdvUBvlkhoWeOVMjMuHNHk0BRBzikzuGDqNmPQbg5ifMEqsHLiIUcQ==", + "node_modules/vfile-message": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.2.tgz", + "integrity": "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==", "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" }, - "engines": { - "node": ">=14.17" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/typescript-eslint": { - "version": "8.0.0-alpha.48", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.0.0-alpha.48.tgz", - "integrity": "sha512-XGK+sPN9IWmfg9GZZyVup+YrIwgE1FoaeCgdSy0pt5Z8aVEedDrDp9cD546v/ZW/iztmCrCTbfnZ08d5rmxObA==", + "node_modules/vite": { + "version": "5.4.4", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.4.tgz", + "integrity": "sha512-RHFCkULitycHVTtelJ6jQLd+KSAAzOgEYorV32R2q++M6COBjKJR6BxqClwp5sf0XaBDjVMuJ9wnNfyAJwjMkA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.0.0-alpha.48", - "@typescript-eslint/parser": "8.0.0-alpha.48", - "@typescript-eslint/utils": "8.0.0-alpha.48" + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^18.0.0 || >=20.0.0" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" }, "peerDependenciesMeta": { - "typescript": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { "optional": true } } }, - "node_modules/uc.micro": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", - "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", - "dev": true, - "license": "MIT" - }, - "node_modules/uid": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/uid/-/uid-2.0.2.tgz", - "integrity": "sha512-u3xV3X7uzvi5b1MncmZo3i2Aw222Zk1keqLA1YkHldREkAhAqi65wuPfe7lHx8H/Wzy+8CE7S7uS3jekIM5s8g==", + "node_modules/vite-node": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.0.5.tgz", + "integrity": "sha512-LdsW4pxj0Ot69FAoXZ1yTnA9bjGohr2yNBU7QKRxpz8ITSkhuDl6h3zS/tvgz4qrNjeRnvrWeXQ8ZF7Um4W00Q==", "dev": true, "license": "MIT", "dependencies": { - "@lukeed/csprng": "^1.0.0" + "cac": "^6.7.14", + "debug": "^4.3.5", + "pathe": "^1.1.2", + "tinyrainbow": "^1.2.0", + "vite": "^5.0.0" + }, + "bin": { + "vite-node": "vite-node.mjs" }, "engines": { - "node": ">=8" + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" } }, - "node_modules/undefsafe": { + "node_modules/vitest": { "version": "2.0.5", - "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", - "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.0.5.tgz", + "integrity": "sha512-8GUxONfauuIdeSl5f9GTgVEpg5BTOlplET4WEDaeY2QBiN8wSm68vxN/tb5z405OwppfoCavnwXafiaYBC/xOA==", "dev": true, - "license": "MIT" - }, - "node_modules/undici": { - "version": "6.19.2", - "resolved": "https://registry.npmjs.org/undici/-/undici-6.19.2.tgz", - "integrity": "sha512-JfjKqIauur3Q6biAtHJ564e3bWa8VvT+7cSiOJHFbX4Erv6CLGDpg8z+Fmg/1OI/47RA+GI2QZaF48SSaLvyBA==", "license": "MIT", - "engines": { - "node": ">=18.17" - } - }, - "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, - "license": "MIT" - }, - "node_modules/unique-filename": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-3.0.0.tgz", - "integrity": "sha512-afXhuC55wkAmZ0P18QsVE6kp8JaxrEokN2HGIoIVv2ijHQd419H0+6EigAFcIzXeMIkcIkNBpB3L/DXB3cTS/g==", - "dev": true, - "license": "ISC", "dependencies": { - "unique-slug": "^4.0.0" + "@ampproject/remapping": "^2.3.0", + "@vitest/expect": "2.0.5", + "@vitest/pretty-format": "^2.0.5", + "@vitest/runner": "2.0.5", + "@vitest/snapshot": "2.0.5", + "@vitest/spy": "2.0.5", + "@vitest/utils": "2.0.5", + "chai": "^5.1.1", + "debug": "^4.3.5", + "execa": "^8.0.1", + "magic-string": "^0.30.10", + "pathe": "^1.1.2", + "std-env": "^3.7.0", + "tinybench": "^2.8.0", + "tinypool": "^1.0.0", + "tinyrainbow": "^1.2.0", + "vite": "^5.0.0", + "vite-node": "2.0.5", + "why-is-node-running": "^2.3.0" }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/unique-slug": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-4.0.0.tgz", - "integrity": "sha512-WrcA6AyEfqDX5bWige/4NQfPZMtASNVxdmWR76WESYQVAACSgWcR6e9i0mofqqBxYFtL4oAxPIptY73/0YE1DQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "imurmurhash": "^0.1.4" + "bin": { + "vitest": "vitest.mjs" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/node": "^18.0.0 || >=20.0.0", + "@vitest/browser": "2.0.5", + "@vitest/ui": "2.0.5", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } } }, - "node_modules/unique-string": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-3.0.0.tgz", - "integrity": "sha512-VGXBUVwxKMBUznyffQweQABPRRW1vHZAbadFZud4pLFAqRGvv/96vafgjWFqzourzr8YonlQiPgH0YCJfawoGQ==", + "node_modules/vitest/node_modules/execa": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", + "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", "dev": true, "license": "MIT", "dependencies": { - "crypto-random-string": "^4.0.0" + "cross-spawn": "^7.0.3", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^3.0.0" }, "engines": { - "node": ">=12" + "node": ">=16.17" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, - "node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "node_modules/vitest/node_modules/get-stream": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", + "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", "dev": true, "license": "MIT", "engines": { - "node": ">= 10.0.0" + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/untildify": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz", - "integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==", + "node_modules/vitest/node_modules/human-signals": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", + "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "engines": { - "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, - "license": "MIT", - "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": ">=16.17.0" } }, - "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, - "license": "MIT" - }, - "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==", + "node_modules/vitest/node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", "dev": true, "license": "MIT", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "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, - "license": "MIT" - }, - "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==", + "node_modules/vitest/node_modules/mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", "dev": true, "license": "MIT", - "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", - "integrity": "sha512-EDxhTEVPZZRLWYcJ4ZXjGFN0oP7qYvbXWzEgRm/Yql4dHX5wDbvh89YHP6PK1lzZJYrMtXUuZZz8XGK+U6U1og==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "boxen": "^7.0.0", - "chalk": "^5.0.1", - "configstore": "^6.0.0", - "has-yarn": "^3.0.0", - "import-lazy": "^4.0.0", - "is-ci": "^3.0.1", - "is-installed-globally": "^0.4.0", - "is-npm": "^6.0.0", - "is-yarn-global": "^0.4.0", - "latest-version": "^7.0.0", - "pupa": "^3.1.0", - "semver": "^7.3.7", - "semver-diff": "^4.0.0", - "xdg-basedir": "^5.1.0" - }, "engines": { - "node": ">=14.16" + "node": ">=12" }, "funding": { - "url": "https://github.com/yeoman/update-notifier?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/update-notifier/node_modules/chalk": { + "node_modules/vitest/node_modules/npm-run-path": { "version": "5.3.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", - "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", + "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", "dev": true, "license": "MIT", + "dependencies": { + "path-key": "^4.0.0" + }, "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "node_modules/vitest/node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/url-template": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/url-template/-/url-template-2.0.8.tgz", - "integrity": "sha512-XdVKMF4SJ0nP/O7XIPB0JwAEuT9lDIYnNsK8yGVe43y0AWoKeJNdv3ZNWh7ksJ6KqQFjOO6ox/VEitLnaVNufw==", - "license": "BSD" - }, - "node_modules/usb": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/usb/-/usb-1.9.2.tgz", - "integrity": "sha512-dryNz030LWBPAf6gj8vyq0Iev3vPbCLHCT8dBw3gQRXRzVNsIdeuU+VjPp3ksmSPkeMAl1k+kQ14Ij0QHyeiAg==", - "hasInstallScript": true, "license": "MIT", - "optional": true, "dependencies": { - "node-addon-api": "^4.2.0", - "node-gyp-build": "^4.3.0" + "mimic-fn": "^4.0.0" }, "engines": { - "node": ">=10.16.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "license": "MIT" - }, - "node_modules/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==", + "node_modules/vitest/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", "dev": true, "license": "MIT", "engines": { - "node": ">= 0.4.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/uuid": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", - "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], + "node_modules/vitest/node_modules/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "dev": true, "license": "MIT", - "bin": { - "uuid": "dist/bin/uuid" + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/v8-compile-cache-lib": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", - "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "node_modules/vue-eslint-parser": { + "version": "9.4.3", + "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-9.4.3.tgz", + "integrity": "sha512-2rYRLWlIpaiN8xbPiDyXZXRgLGOtWxERV7ND5fFAv5qo1D2N9Fu9MNajBNc6o13lZ+24DAWCkQCvj4klgmcITg==", "dev": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "debug": "^4.3.4", + "eslint-scope": "^7.1.1", + "eslint-visitor-keys": "^3.3.0", + "espree": "^9.3.1", + "esquery": "^1.4.0", + "lodash": "^4.17.21", + "semver": "^7.3.6" + }, + "engines": { + "node": "^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=6.0.0" + } }, - "node_modules/validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "node_modules/vue-eslint-parser/node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", "dev": true, - "license": "Apache-2.0", + "license": "BSD-2-Clause", "dependencies": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/validate-npm-package-name": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-5.0.1.tgz", - "integrity": "sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==", + "node_modules/vue-eslint-parser/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true, - "license": "ISC", + "license": "Apache-2.0", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/validator": { - "version": "13.12.0", - "resolved": "https://registry.npmjs.org/validator/-/validator-13.12.0.tgz", - "integrity": "sha512-c1Q0mCiPlgdTVVVIJIrBuxNicYE+t/7oKeI9MWLj3fh/uq2Pxh/3eeWbVZ4OcGW1TUf53At0njHw5SMdA3tmMg==", + "node_modules/vue-eslint-parser/node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", "dev": true, - "license": "MIT", + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, "engines": { - "node": ">= 0.10" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "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==", + "node_modules/walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" + "license": "Apache-2.0", + "dependencies": { + "makeerror": "1.0.12" } }, "node_modules/wcwidth": { @@ -11415,52 +15676,21 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/wide-align": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", - "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^1.0.2 || 2 || 3 || 4" - } - }, - "node_modules/wide-align/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/wide-align/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", "dev": true, "license": "MIT", "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" + "siginfo": "^2.0.0", + "stackback": "0.0.2" }, - "engines": { - "node": ">=8" - } - }, - "node_modules/widest-line": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-4.0.1.tgz", - "integrity": "sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig==", - "dev": true, - "license": "MIT", - "dependencies": { - "string-width": "^5.0.1" + "bin": { + "why-is-node-running": "cli.js" }, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=8" } }, "node_modules/word-wrap": { @@ -11533,9 +15763,9 @@ } }, "node_modules/wrap-ansi/node_modules/ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", "dev": true, "license": "MIT", "engines": { @@ -11581,16 +15811,17 @@ "license": "ISC" }, "node_modules/write-file-atomic": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", - "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", "dev": true, "license": "ISC", "dependencies": { "imurmurhash": "^0.1.4", - "is-typedarray": "^1.0.0", - "signal-exit": "^3.0.2", - "typedarray-to-buffer": "^3.1.5" + "signal-exit": "^3.0.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, "node_modules/write-file-atomic/node_modules/signal-exit": { @@ -11621,17 +15852,14 @@ } } }, - "node_modules/xdg-basedir": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-5.1.0.tgz", - "integrity": "sha512-GCPAHLvrIH13+c0SuacwvRYj2SxJXQ4kaVTT5xgL3kPrz56XxkF21IGhjSE1+W0aw7gpBWRGXLCPnPby6lSpmQ==", + "node_modules/xml-name-validator": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", + "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "engines": { "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/xml2js": { @@ -11667,16 +15895,27 @@ "node": ">=0.4" } }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, "license": "ISC" }, "node_modules/yaml": { - "version": "2.4.5", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.5.tgz", - "integrity": "sha512-aBx2bnqDzVOyNKfsysjA2ms5ZlnjSAW2eG3/L5G/CSujfjLJTJsEw1bGw8kCf04KodQWk1pxlGnZ56CRxiawmg==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.5.1.tgz", + "integrity": "sha512-bLQOjaX/ADgQ20isPJRvF0iRUHIxVhYvr53Of7wGcWlO2jvtUlH5m87DsmulFVxRpNLOnI4tB6p/oh8D7kpn9Q==", "dev": true, "license": "ISC", "bin": { @@ -11686,6 +15925,88 @@ "node": ">= 14" } }, + "node_modules/yaml-eslint-parser": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/yaml-eslint-parser/-/yaml-eslint-parser-1.2.3.tgz", + "integrity": "sha512-4wZWvE398hCP7O8n3nXKu/vdq1HcH01ixYlCREaJL5NUMwQ0g3MaGFUBNSlmBtKmhbtVG/Cm6lyYmSVTEVil8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.0.0", + "lodash": "^4.17.21", + "yaml": "^2.0.0" + }, + "engines": { + "node": "^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ota-meshi" + } + }, + "node_modules/yaml-eslint-parser/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/yargs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/yn": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", @@ -11708,6 +16029,17 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } } } } diff --git a/package.json b/package.json index cf4b3aa6..e46978de 100644 --- a/package.json +++ b/package.json @@ -1,49 +1,17 @@ { - "displayName": "SwitchBot", "name": "@switchbot/homebridge-switchbot", - "version": "3.7.0", + "displayName": "SwitchBot", + "type": "module", + "version": "3.8.0", "description": "The SwitchBot plugin allows you to access your SwitchBot device(s) from HomeKit.", - "author": { - "name": "SwitchBot", - "url": "https://github.com/SwitchBot", - "email": "support@wondertechlabs.com" - }, + "author": "SwitchBot (https://github.com/SwitchBot)", "contributors": [ { "name": "Donavan Becker", "url": "https://github.com/donavanbecker" } ], - "type": "module", "license": "ISC", - "icon": "https://raw.githubusercontent.com/OpenWonderLabs/homebridge-switchbot/latest/branding/icon.png", - "repository": { - "type": "git", - "url": "git://github.com/OpenWonderLabs/homebridge-switchbot.git" - }, - "bugs": { - "url": "https://github.com/OpenWonderLabs/homebridge-switchbot/issues" - }, - "engineStrict": true, - "engines": { - "homebridge": "^1.8.4 || ^2.0.0-alpha.0", - "node": "^18 || ^20 || ^22" - }, - "main": "dist/index.js", - "scripts": { - "check": "npm install && npm outdated", - "update": "ncu -u && npm update && npm install", - "lint": "eslint src/**/*.ts", - "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 lint && npm run build && npm run plugin-ui ", - "postpublish": "npm run clean", - "clean": "rimraf ./dist", - "test": "npm run lint", - "docs": "typedoc", - "lint-docs": "typedoc --emit none --treatWarningsAsErrors" - }, "funding": [ { "type": "Paypal", @@ -54,8 +22,13 @@ "url": "https://github.com/sponsors/donavanbecker" } ], - "publishConfig": { - "access": "public" + "homepage": "https://github.com/OpenWonderLabs/homebridge-switchbot#readme", + "repository": { + "type": "git", + "url": "git://github.com/OpenWonderLabs/homebridge-switchbot.git" + }, + "bugs": { + "url": "https://github.com/OpenWonderLabs/homebridge-switchbot/issues" }, "keywords": [ "homebridge-plugin", @@ -78,32 +51,66 @@ "ble", "ir" ], + "main": "dist/index.js", + "icon": "https://raw.githubusercontent.com/OpenWonderLabs/homebridge-switchbot/latest/branding/icon.png", + "engineStrict": true, + "engines": { + "homebridge": "^1.8.4 || ^2.0.0 || ^2.0.0-beta.11 || ^2.0.0-alpha.10", + "node": "^18 || ^20 || ^22" + }, + "scripts": { + "check": "npm install && npm outdated", + "update": "ncu -u && npm update && npm install", + "lint": "eslint src/**/*.ts", + "fix": "eslint src/**/*.ts --fix", + "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": "npm run clean && tsc && npm run plugin-ui", + "prepublishOnly": "npm run lint && npm run build && npm run plugin-ui ", + "postpublish": "npm run clean", + "clean": "shx rm -rf ./dist", + "test": "npm run lint", + "docs": "typedoc", + "lint-docs": "typedoc --emit none --treatWarningsAsErrors" + }, + "publishConfig": { + "access": "public" + }, "dependencies": { "@homebridge/plugin-ui-utils": "^1.0.3", "async-mqtt": "^2.6.3", - "fakegato-history": "^0.6.4", - "homebridge-lib": "^7.0.4", + "fakegato-history": "^0.6.5", + "homebridge-lib": "^7.0.8", + "node-switchbot": "2.3.0", "rxjs": "^7.8.1", - "undici": "^6.19.2" - }, - "optionalDependencies": { - "node-switchbot": "2.3.0" + "undici": "^6.19.8" }, "devDependencies": { - "@eslint/js": "^9.7.0", - "@stylistic/eslint-plugin": "^2.3.0", - "@types/eslint__js": "^8.42.3", - "@types/node": "^20.14.11", - "eslint": "^9.7.0", - "globals": "^15.8.0", + "@antfu/eslint-config": "^3.5.1", + "@types/aes-js": "^3.1.4", + "@types/debug": "^4.1.12", + "@types/fs-extra": "^11.0.4", + "@types/jest": "^29.5.12", + "@types/mdast": "^4.0.4", + "@types/node": "^22.5.4", + "@types/semver": "^7.5.8", + "@types/source-map-support": "^0.5.10", + "@vitest/coverage-v8": "^2.0.5", + "eslint": "^9.10.0", + "eslint-plugin-format": "^0.1.2", "homebridge": "^1.8.4", - "homebridge-config-ui-x": "4.56.4", + "homebridge-config-ui-x": "4.58.0", + "jest": "^29.7.0", "nodemon": "^3.1.4", - "npm-check-updates": "^16.14.20", - "rimraf": "^6.0.1", + "npm-check-updates": "^17.1.1", + "shx": "^0.3.4", "ts-node": "^10.9.2", - "typedoc": "^0.26.5", - "typescript": "^5.5.3", - "typescript-eslint": "^8.0.0-alpha.44" + "typedoc": "^0.26.7", + "typescript": "^5.6.2", + "typescript-axios-wb": "^1.0.3", + "vitest": "^2.0.5" + }, + "directories": { + "doc": "docs" } } diff --git a/src/device/blindtilt.ts b/src/device/blindtilt.ts index 68242f7e..3326c5c8 100644 --- a/src/device/blindtilt.ts +++ b/src/device/blindtilt.ts @@ -2,84 +2,103 @@ * * blindtilt.ts: @switchbot/homebridge-switchbot. */ -import { deviceBase } from './device.js'; -import { BlindTiltMappingMode } from '../utils.js'; -import { SwitchBotBLEModel, SwitchBotBLEModelName } from 'node-switchbot'; -import { Subject, debounceTime, interval, skipWhile, take, tap } from 'rxjs'; +import type { CharacteristicValue, PlatformAccessory, Service } from 'homebridge' -import type { devicesConfig } from '../settings.js'; -import type { device } from '../types/devicelist.js'; -import type { SwitchBotPlatform } from '../platform.js'; -import type { blindTiltServiceData } from '../types/bledevicestatus.js'; -import type { blindTiltStatus } from '../types/devicestatus.js'; -import type { blindTiltWebhookContext } from '../types/devicewebhookstatus.js'; -import type { Service, PlatformAccessory, CharacteristicValue } from 'homebridge'; +import type { SwitchBotPlatform } from '../platform.js' +import type { devicesConfig } from '../settings.js' +import type { blindTiltServiceData } from '../types/bledevicestatus.js' +import type { device } from '../types/devicelist.js' +import type { blindTiltStatus } from '../types/devicestatus.js' +import type { blindTiltWebhookContext } from '../types/devicewebhookstatus.js' +/* +* For Testing Locally: +* import { SwitchBotBLEModel, SwitchBotBLEModelName } from '/Users/Shared/GitHub/OpenWonderLabs/node-switchbot/dist/index.js'; +*/ +import { SwitchBotBLEModel, SwitchBotBLEModelName } from 'node-switchbot' +import { debounceTime, interval, skipWhile, Subject, take, tap } from 'rxjs' + +import { BlindTiltMappingMode } from '../utils.js' +import { deviceBase } from './device.js' export class BlindTilt extends deviceBase { // Services private WindowCovering: { - Name: CharacteristicValue; - Service: Service; - PositionState: CharacteristicValue; - TargetPosition: CharacteristicValue; - CurrentPosition: CharacteristicValue; - TargetHorizontalTiltAngle: CharacteristicValue; - CurrentHorizontalTiltAngle: CharacteristicValue; - }; + Name: CharacteristicValue + Service: Service + PositionState: CharacteristicValue + TargetPosition: CharacteristicValue + CurrentPosition: CharacteristicValue + TargetHorizontalTiltAngle: CharacteristicValue + CurrentHorizontalTiltAngle: CharacteristicValue + } private Battery: { - Name: CharacteristicValue; - Service: Service; - BatteryLevel: CharacteristicValue; - StatusLowBattery: CharacteristicValue; - ChargingState?: CharacteristicValue; - }; + Name: CharacteristicValue + Service: Service + BatteryLevel: CharacteristicValue + StatusLowBattery: CharacteristicValue + ChargingState?: CharacteristicValue + } private LightSensor?: { - Name: CharacteristicValue; - Service: Service; - CurrentAmbientLightLevel?: CharacteristicValue; - }; + Name: CharacteristicValue + Service: Service + CurrentAmbientLightLevel?: CharacteristicValue + } + + private OpenModeSwitch?: { + Name: CharacteristicValue + Service: Service + On: CharacteristicValue + } + + private CloseModeSwitch?: { + Name: CharacteristicValue + Service: Service + On: CharacteristicValue + } // OpenAPI - deviceStatus!: blindTiltStatus; - mappingMode: BlindTiltMappingMode = BlindTiltMappingMode.OnlyUp; + deviceStatus!: blindTiltStatus + mappingMode: BlindTiltMappingMode = BlindTiltMappingMode.OnlyUp - //Webhook - webhookContext!: blindTiltWebhookContext; + // Webhook + webhookContext!: blindTiltWebhookContext // BLE - serviceData!: blindTiltServiceData; + serviceData!: blindTiltServiceData // Target - setNewTarget!: boolean; - setNewTargetTimer!: NodeJS.Timeout; + setNewTarget!: boolean + setNewTargetTimer!: NodeJS.Timeout // Updates - blindTiltUpdateInProgress; - doBlindTiltUpdate: Subject; + blindTiltMoving: boolean + blindTiltUpdateInProgress: boolean + doBlindTiltUpdate: Subject constructor( readonly platform: SwitchBotPlatform, accessory: PlatformAccessory, device: device & devicesConfig, ) { - super(platform, accessory, device); + super(platform, accessory, device) // Set category - accessory.category = this.hap.Categories.WINDOW_COVERING; + accessory.category = this.hap.Categories.WINDOW_COVERING // default placeholders - this.mappingMode = (device.blindTilt?.mode as BlindTiltMappingMode) ?? BlindTiltMappingMode.OnlyUp; - this.debugLog(`Mapping mode: ${this.mappingMode}`); + this.mappingMode = (device.blindTilt?.mode as BlindTiltMappingMode) ?? BlindTiltMappingMode.OnlyUp + this.debugLog(`Mapping mode: ${this.mappingMode}`) // this is subject we use to track when we need to POST changes to the SwitchBot API - this.doBlindTiltUpdate = new Subject(); - this.blindTiltUpdateInProgress = false; - this.setNewTarget = false; + this.doBlindTiltUpdate = new Subject() + this.blindTiltMoving = false + this.blindTiltUpdateInProgress = false + this.setNewTarget = false // Initialize WindowCovering Service - accessory.context.WindowCovering = accessory.context.WindowCovering ?? {}; + accessory.context.WindowCovering = accessory.context.WindowCovering ?? {} this.WindowCovering = { Name: accessory.displayName, Service: accessory.getService(this.hap.Service.WindowCovering) ?? accessory.addService(this.hap.Service.WindowCovering) as Service, @@ -88,291 +107,336 @@ export class BlindTilt extends deviceBase { CurrentPosition: accessory.context.CurrentPosition ?? 100, TargetHorizontalTiltAngle: accessory.context.TargetHorizontalTiltAngle ?? 90, CurrentHorizontalTiltAngle: accessory.context.CurrentHorizontalTiltAngle ?? 90, - }; - accessory.context.WindowCovering = this.WindowCovering as object; + } + accessory.context.WindowCovering = this.WindowCovering as object // Initialize WindowCovering Characteristics - this.WindowCovering.Service - .setCharacteristic(this.hap.Characteristic.Name, this.WindowCovering.Name) - .getCharacteristic(this.hap.Characteristic.TargetPosition) - .setProps({ - minStep: device.blindTilt?.set_minStep ?? 1, - minValue: 0, - maxValue: 100, - validValueRanges: [0, 100], - }) - .onGet(() => { - return this.WindowCovering.TargetPosition; - }) - .onSet(this.TargetPositionSet.bind(this)); + this.WindowCovering.Service.setCharacteristic(this.hap.Characteristic.Name, this.WindowCovering.Name).getCharacteristic(this.hap.Characteristic.TargetPosition).setProps({ + minStep: device.blindTilt?.set_minStep ?? 1, + minValue: 0, + maxValue: 100, + validValueRanges: [0, 100], + }).onGet(() => { + return this.WindowCovering.TargetPosition + }).onSet(this.TargetPositionSet.bind(this)) // Initialize WindowCovering CurrentPosition Characteristic - this.WindowCovering.Service - .getCharacteristic(this.hap.Characteristic.CurrentPosition) - .setProps({ - minStep: device.blindTilt?.set_minStep ?? 1, - minValue: 0, - maxValue: 100, - validValueRanges: [0, 100], - }).onGet(() => { - return this.WindowCovering.CurrentPosition ?? 0; - }); + this.WindowCovering.Service.getCharacteristic(this.hap.Characteristic.CurrentPosition).setProps({ + minStep: device.blindTilt?.set_minStep ?? 1, + minValue: 0, + maxValue: 100, + validValueRanges: [0, 100], + }).onGet(() => { + return this.WindowCovering.CurrentPosition ?? 0 + }) // Initialize WindowCovering TargetHorizontalTiltAngle Characteristic - this.WindowCovering.Service - .getCharacteristic(this.hap.Characteristic.TargetHorizontalTiltAngle) - .setProps({ - minStep: 180, - minValue: -90, - maxValue: 90, - validValues: [-90, 90], - }) - .onGet(() => { - return this.WindowCovering.TargetHorizontalTiltAngle; - }) - .onSet(this.TargetHorizontalTiltAngleSet.bind(this)); + this.WindowCovering.Service.getCharacteristic(this.hap.Characteristic.TargetHorizontalTiltAngle).setProps({ + minStep: 180, + minValue: -90, + maxValue: 90, + validValues: [-90, 90], + }).onGet(() => { + return this.WindowCovering.TargetHorizontalTiltAngle + }).onSet(this.TargetHorizontalTiltAngleSet.bind(this)) // Initialize WindowCovering CurrentHorizontalTiltAngle Characteristic - this.WindowCovering.Service - .getCharacteristic(this.hap.Characteristic.CurrentHorizontalTiltAngle) - .setProps({ - minStep: 180, - minValue: -90, - maxValue: 90, - validValues: [-90, 90], - }).onGet(() => { - return this.WindowCovering.CurrentHorizontalTiltAngle ?? 0; - }); + this.WindowCovering.Service.getCharacteristic(this.hap.Characteristic.CurrentHorizontalTiltAngle).setProps({ + minStep: 180, + minValue: -90, + maxValue: 90, + validValues: [-90, 90], + }).onGet(() => { + return this.WindowCovering.CurrentHorizontalTiltAngle ?? 0 + }) // Initialize Battery Service - accessory.context.Battery = accessory.context.Battery ?? {}; + accessory.context.Battery = accessory.context.Battery ?? {} this.Battery = { Name: `${accessory.displayName} Battery`, Service: accessory.getService(this.hap.Service.Battery) ?? accessory.addService(this.hap.Service.Battery) as Service, BatteryLevel: accessory.context.BatteryLevel ?? 100, StatusLowBattery: this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL, ChargingState: accessory.context.ChargingState ?? this.hap.Characteristic.ChargingState.NOT_CHARGING, - }; - accessory.context.Battery = this.Battery as object; + } + accessory.context.Battery = this.Battery as object // Initialize Battery Name Characteristic - this.Battery.Service - .setCharacteristic(this.hap.Characteristic.Name, this.Battery.Name) - .setCharacteristic(this.hap.Characteristic.ChargingState, this.hap.Characteristic.ChargingState.NOT_CHARGEABLE); + this.Battery.Service.setCharacteristic(this.hap.Characteristic.Name, this.Battery.Name).setCharacteristic(this.hap.Characteristic.ChargingState, this.hap.Characteristic.ChargingState.NOT_CHARGEABLE) // Initialize LightSensor Service if (device.blindTilt?.hide_lightsensor) { if (this.LightSensor?.Service) { - this.debugLog('Removing Light Sensor Service'); - this.LightSensor.Service = accessory.getService(this.hap.Service.LightSensor) as Service; - accessory.removeService(this.LightSensor.Service); - accessory.context.LightSensor = {}; + this.debugLog('Removing Light Sensor Service') + this.LightSensor.Service = accessory.getService(this.hap.Service.LightSensor) as Service + accessory.removeService(this.LightSensor.Service) + accessory.context.LightSensor = {} } else { - this.debugLog('Light Sensor Service is already removed'); + this.debugLog('Light Sensor Service is already removed') } } else { - accessory.context.LightSensor = accessory.context.LightSensor ?? {}; + accessory.context.LightSensor = accessory.context.LightSensor ?? {} this.LightSensor = { Name: `${accessory.displayName} Light Sensor`, Service: accessory.getService(this.hap.Service.LightSensor) ?? accessory.addService(this.hap.Service.LightSensor) as Service, CurrentAmbientLightLevel: accessory.context.CurrentAmbientLightLevel ?? 0.0001, - }; - accessory.context.LightSensor = this.LightSensor as object; + } + accessory.context.LightSensor = this.LightSensor as object // Initialize LightSensor Characteristics - this.LightSensor.Service - .setCharacteristic(this.hap.Characteristic.Name, this.LightSensor.Name) - .setCharacteristic(this.hap.Characteristic.StatusActive, true) - .getCharacteristic(this.hap.Characteristic.CurrentAmbientLightLevel) - .onGet(() => { - return this.LightSensor?.CurrentAmbientLightLevel ?? 0.0001; - }); + this.LightSensor.Service.setCharacteristic(this.hap.Characteristic.Name, this.LightSensor.Name).setCharacteristic(this.hap.Characteristic.StatusActive, true).getCharacteristic(this.hap.Characteristic.CurrentAmbientLightLevel).onGet(() => { + return this.LightSensor?.CurrentAmbientLightLevel ?? 0.0001 + }) + } + + // Initialize Open Mode Switch Service + if (!device.blindTilt?.silentModeSwitch) { + if (this.OpenModeSwitch?.Service) { + this.debugLog('Removing Open Mode Switch Service') + this.OpenModeSwitch.Service = this.accessory.getService(this.hap.Service.Switch) as Service + accessory.removeService(this.OpenModeSwitch.Service) + accessory.context.OpenModeSwitch = {} + } + } else { + accessory.context.OpenModeSwitch = accessory.context.OpenModeSwitch ?? {} + this.OpenModeSwitch = { + Name: `${accessory.displayName} Silent Open Mode`, + Service: accessory.getService(this.hap.Service.Switch) ?? accessory.addService(this.hap.Service.Switch) as Service, + On: accessory.context.OpenModeSwitch.On ?? false, + } + accessory.context.OpenModeSwitch = this.OpenModeSwitch as object + + // Initialize Open Mode Switch Service + this.OpenModeSwitch.Service.setCharacteristic(this.hap.Characteristic.Name, this.OpenModeSwitch.Name).getCharacteristic(this.hap.Characteristic.On).onGet(() => { + return this.OpenModeSwitch?.On ?? false + }) + + this.OpenModeSwitch.Service.getCharacteristic(this.hap.Characteristic.On).onSet(this.OpenModeSwitchSet.bind(this)) + } + + // Initialize Close Mode Switch Service + if (!device.blindTilt?.silentModeSwitch) { + if (this.CloseModeSwitch?.Service) { + this.debugLog('Removing Close Mode Switch Service') + this.CloseModeSwitch.Service = this.accessory.getService(this.hap.Service.Switch) as Service + accessory.removeService(this.CloseModeSwitch.Service) + accessory.context.CloseModeSwitch = {} + } + } else { + accessory.context.CloseModeSwitch = accessory.context.CloseModeSwitch ?? {} + this.CloseModeSwitch = { + Name: `${accessory.displayName} Silent Close Mode`, + Service: accessory.getService(this.hap.Service.Switch) ?? accessory.addService(this.hap.Service.Switch) as Service, + On: accessory.context.CloseModeSwitch.On ?? false, + } + accessory.context.CloseModeSwitch = this.CloseModeSwitch as object + + // Initialize Close Mode Switch Service + this.CloseModeSwitch.Service.setCharacteristic(this.hap.Characteristic.Name, this.CloseModeSwitch.Name).getCharacteristic(this.hap.Characteristic.On).onGet(() => { + return this.CloseModeSwitch?.On ?? false + }) + + this.CloseModeSwitch.Service.getCharacteristic(this.hap.Characteristic.On).onSet(this.CloseModeSwitchSet.bind(this)) } // Retrieve initial values and updateHomekit - this.debugLog('Retrieve initial values and update Homekit'); - this.refreshStatus(); + try { + this.debugLog('Retrieve initial values and update Homekit') + this.refreshStatus() + } catch (e: any) { + this.errorLog(`failed to retrieve initial values and update Homekit, Error: ${e}`) + } + + // regisiter webhook event handler if enabled + try { + this.debugLog('Registering Webhook Event Handler') + this.registerWebhook() + } catch (e: any) { + this.errorLog(`failed to registerWebhook, Error: ${e}`) + } - //regisiter webhook event handler - this.debugLog('Registering Webhook Event Handler'); - this.registerWebhook(); + // regisiter platform BLE event handler if enabled + try { + this.debugLog('Registering Platform BLE Event Handler') + this.registerPlatformBLE() + } catch (e: any) { + this.errorLog(`failed to registerPlatformBLE, Error: ${e}`) + } // Start an update interval interval(this.deviceRefreshRate * 1000) .pipe(skipWhile(() => this.blindTiltUpdateInProgress)) .subscribe(async () => { - await this.refreshStatus(); - }); + await this.refreshStatus() + }) // update slide progress interval(this.deviceUpdateRate * 1000) + .pipe(skipWhile(() => !this.blindTiltMoving)) .subscribe(async () => { if (this.WindowCovering.PositionState === this.hap.Characteristic.PositionState.STOPPED) { - return; + return } - this.debugLog(`Refresh Status When Moving, PositionState: ${this.WindowCovering.PositionState}`); - await this.refreshStatus(); - }); + this.debugLog(`Refresh Status When Moving, PositionState: ${this.WindowCovering.PositionState}`) + await this.refreshStatus() + }) // Watch for BlindTilt change events // We put in a debounce of 100ms so we don't make duplicate calls this.doBlindTiltUpdate .pipe( tap(() => { - this.blindTiltUpdateInProgress = true; + this.blindTiltUpdateInProgress = true }), debounceTime(this.devicePushRate * 1000), ) .subscribe(async () => { try { - await this.pushChanges(); + await this.pushChanges() } catch (e: any) { - await this.apiError(e); - await this.errorLog(`failed pushChanges with ${device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`); + await this.apiError(e) + await this.errorLog(`failed pushChanges with ${device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`) } - this.blindTiltUpdateInProgress = false; - }); + this.blindTiltUpdateInProgress = false + }) } /** * Parse the device status from the SwitchBotBLE API */ async BLEparseStatus(): Promise { - await this.debugLog('BLEparseStatus'); - await this.debugLog(`(direction, slidePosition, battery, version) = BLE:(${this.serviceData.tilt}, ${this.serviceData.tilt},` - + ` ${this.serviceData.battery}, ${this.accessory.context.version}), current:(${this.WindowCovering.CurrentHorizontalTiltAngle},` - + ` ${this.WindowCovering.CurrentPosition}, ${this.Battery.BatteryLevel}, ${this.accessory.context.version})`); + await this.debugLog('BLEparseStatus') + await this.debugLog(`(direction, slidePosition, battery, version) = BLE:(${this.serviceData.tilt}, ${this.serviceData.tilt}, ${this.serviceData.battery}, ${this.accessory.context.version}), current:(${this.WindowCovering.CurrentHorizontalTiltAngle}, ${this.WindowCovering.CurrentPosition}, ${this.Battery.BatteryLevel}, ${this.accessory.context.version})`) // CurrentPosition - this.WindowCovering.CurrentPosition = 100 - Number(this.serviceData.tilt); - await this.setMinMax(); - await this.debugLog(`CurrentPosition ${this.WindowCovering.CurrentPosition}`); + this.WindowCovering.CurrentPosition = 100 - Number(this.serviceData.tilt) + await this.setMinMax() + await this.debugLog(`CurrentPosition ${this.WindowCovering.CurrentPosition}`) if (this.setNewTarget) { - await this.infoLog('Checking Status ...'); + await this.infoLog('Checking Status ...') } if (this.setNewTarget && this.serviceData.inMotion) { - await this.setMinMax(); + this.blindTiltMoving = true + await this.setMinMax() if (Number(this.WindowCovering.TargetPosition) > this.WindowCovering.CurrentPosition) { - await this.debugLog(`Closing, CurrentPosition: ${this.WindowCovering.CurrentPosition}`); - this.WindowCovering.PositionState = this.hap.Characteristic.PositionState.INCREASING; - this.WindowCovering.Service.getCharacteristic(this.hap.Characteristic.PositionState).updateValue(this.WindowCovering.PositionState); - await this.debugLog(`Increasing, PositionState: ${this.WindowCovering.PositionState}`); + await this.debugLog(`Closing, CurrentPosition: ${this.WindowCovering.CurrentPosition}`) + this.WindowCovering.PositionState = this.hap.Characteristic.PositionState.INCREASING + this.WindowCovering.Service.getCharacteristic(this.hap.Characteristic.PositionState).updateValue(this.WindowCovering.PositionState) + await this.debugLog(`Increasing, PositionState: ${this.WindowCovering.PositionState}`) } else if (Number(this.WindowCovering.TargetPosition) < this.WindowCovering.CurrentPosition) { - await this.debugLog(`Opening, CurrentPosition: ${this.WindowCovering.CurrentPosition}`); - this.WindowCovering.PositionState = this.hap.Characteristic.PositionState.DECREASING; - this.WindowCovering.Service.getCharacteristic(this.hap.Characteristic.PositionState).updateValue(this.WindowCovering.PositionState); - await this.debugLog(`Decreasing, PositionState: ${this.WindowCovering.PositionState}`); + await this.debugLog(`Opening, CurrentPosition: ${this.WindowCovering.CurrentPosition}`) + this.WindowCovering.PositionState = this.hap.Characteristic.PositionState.DECREASING + this.WindowCovering.Service.getCharacteristic(this.hap.Characteristic.PositionState).updateValue(this.WindowCovering.PositionState) + await this.debugLog(`Decreasing, PositionState: ${this.WindowCovering.PositionState}`) } else { - await this.debugLog(`Standby, CurrentPosition: ${this.WindowCovering.CurrentPosition}`); - this.WindowCovering.PositionState = this.hap.Characteristic.PositionState.STOPPED; - this.WindowCovering.Service.getCharacteristic(this.hap.Characteristic.PositionState).updateValue(this.WindowCovering.PositionState); - await this.debugLog('Stopped, PositionState', this.WindowCovering.PositionState); + await this.debugLog(`Standby, CurrentPosition: ${this.WindowCovering.CurrentPosition}`) + this.WindowCovering.PositionState = this.hap.Characteristic.PositionState.STOPPED + this.WindowCovering.Service.getCharacteristic(this.hap.Characteristic.PositionState).updateValue(this.WindowCovering.PositionState) + await this.debugLog('Stopped, PositionState', this.WindowCovering.PositionState) } } else { - await this.debugLog(`Standby, CurrentPosition: ${this.WindowCovering.CurrentPosition}`); - this.WindowCovering.TargetPosition = this.WindowCovering.CurrentPosition; - this.WindowCovering.PositionState = this.hap.Characteristic.PositionState.STOPPED; - await this.debugLog(`Stopped, PositionState: ${this.WindowCovering.PositionState}`); + this.blindTiltMoving = false + await this.debugLog(`Standby, CurrentPosition: ${this.WindowCovering.CurrentPosition}`) + this.WindowCovering.TargetPosition = this.WindowCovering.CurrentPosition + this.WindowCovering.PositionState = this.hap.Characteristic.PositionState.STOPPED + await this.debugLog(`Stopped, PositionState: ${this.WindowCovering.PositionState}`) } - await this.debugLog(`CurrentPosition: ${this.WindowCovering.CurrentPosition}, TargetPosition: ${this.WindowCovering.TargetPosition},` - + ` PositionState: ${this.WindowCovering.PositionState}`); + await this.debugLog(`CurrentPosition: ${this.WindowCovering.CurrentPosition}, TargetPosition: ${this.WindowCovering.TargetPosition}, PositionState: ${this.WindowCovering.PositionState}`) // CurrentAmbientLightLevel if (!this.device.blindTilt?.hide_lightsensor && this.LightSensor?.Service) { - const set_minLux = this.device.blindTilt?.set_minLux ?? 1; - const set_maxLux = this.device.blindTilt?.set_maxLux ?? 6001; - const spaceBetweenLevels = 9; - + const set_minLux = this.device.blindTilt?.set_minLux ?? 1 + const set_maxLux = this.device.blindTilt?.set_maxLux ?? 6001 + const spaceBetweenLevels = 9 - await this.getLightLevel(this.serviceData.lightLevel, set_minLux, set_maxLux, spaceBetweenLevels); - await this.debugLog(`LightLevel: ${this.serviceData.lightLevel}, CurrentAmbientLightLevel: ${this.LightSensor!.CurrentAmbientLightLevel}`); + await this.getLightLevel(this.serviceData.lightLevel, set_minLux, set_maxLux, spaceBetweenLevels) + await this.debugLog(`LightLevel: ${this.serviceData.lightLevel}, CurrentAmbientLightLevel: ${this.LightSensor!.CurrentAmbientLightLevel}`) } // BatteryLevel - this.Battery.BatteryLevel = this.serviceData.battery; - await this.debugLog(`BatteryLevel: ${this.Battery.BatteryLevel}`); + this.Battery.BatteryLevel = this.serviceData.battery + await this.debugLog(`BatteryLevel: ${this.Battery.BatteryLevel}`) // StatusLowBattery this.Battery.StatusLowBattery = this.Battery.BatteryLevel < 10 - ? this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW : this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL; - await this.debugLog(`StatusLowBattery: ${this.Battery.StatusLowBattery}`); + ? this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW + : this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL + await this.debugLog(`StatusLowBattery: ${this.Battery.StatusLowBattery}`) }; /** * Parse the device status from the SwitchBot OpenAPI */ async openAPIparseStatus(): Promise { - await this.debugLog('openAPIparseStatus'); - await this.debugLog(`(direction, slidePosition, battery, version) = OpenAPI:(${this.deviceStatus.direction}, ${this.deviceStatus.slidePosition},` - + ` ${this.deviceStatus.battery}, ${this.deviceStatus.version}), current:(${this.WindowCovering.CurrentHorizontalTiltAngle},` - + ` ${this.WindowCovering.CurrentPosition}, ${this.Battery.BatteryLevel}, ${this.accessory.context.version})`); + await this.debugLog('openAPIparseStatus') + await this.debugLog(`(direction, slidePosition, battery, version) = OpenAPI:(${this.deviceStatus.direction}, ${this.deviceStatus.slidePosition}, ${this.deviceStatus.battery}, ${this.deviceStatus.version}), current:(${this.WindowCovering.CurrentHorizontalTiltAngle}, ${this.WindowCovering.CurrentPosition}, ${this.Battery.BatteryLevel}, ${this.accessory.context.version})`) // CurrentPosition - await this.getCurrentPosttionDirection(this.deviceStatus.direction, this.deviceStatus.slidePosition); + await this.getCurrentPosttionDirection(this.deviceStatus.direction, this.deviceStatus.slidePosition) if (!this.device.blindTilt?.hide_lightsensor && this.LightSensor?.Service) { - const set_minLux = this.device.blindTilt?.set_minLux ?? 1; - const set_maxLux = this.device.blindTilt?.set_maxLux ?? 6001; - const lightLevel = this.deviceStatus.lightLevel === 'bright' ? set_maxLux : set_minLux; - this.LightSensor.CurrentAmbientLightLevel = await this.getLightLevel(lightLevel, set_minLux, set_maxLux, 2); - await this.debugLog(`LightLevel: ${this.deviceStatus.lightLevel}, CurrentAmbientLightLevel: ${this.LightSensor.CurrentAmbientLightLevel}`); + const set_minLux = this.device.blindTilt?.set_minLux ?? 1 + const set_maxLux = this.device.blindTilt?.set_maxLux ?? 6001 + const lightLevel = this.deviceStatus.lightLevel === 'bright' ? set_maxLux : set_minLux + this.LightSensor.CurrentAmbientLightLevel = await this.getLightLevel(lightLevel, set_minLux, set_maxLux, 2) + await this.debugLog(`LightLevel: ${this.deviceStatus.lightLevel}, CurrentAmbientLightLevel: ${this.LightSensor.CurrentAmbientLightLevel}`) } // BatteryLevel - this.Battery.BatteryLevel = this.deviceStatus.battery; - await this.debugLog(`BatteryLevel: ${this.Battery.BatteryLevel}`); + this.Battery.BatteryLevel = this.deviceStatus.battery + await this.debugLog(`BatteryLevel: ${this.Battery.BatteryLevel}`) // StatusLowBattery this.Battery.StatusLowBattery = this.Battery.BatteryLevel < 10 - ? this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW : this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL; - await this.debugLog(`StatusLowBattery: ${this.Battery.StatusLowBattery}`); + ? this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW + : this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL + await this.debugLog(`StatusLowBattery: ${this.Battery.StatusLowBattery}`) // Firmware Version - const version = this.deviceStatus.version.toString(); - await this.debugLog(`Firmware Version: ${version.replace(/^V|-.*$/g, '')}`); - let deviceVersion: string; + const version = this.deviceStatus.version.toString() + await this.debugLog(`Firmware Version: ${version.replace(/^V|-.*$/g, '')}`) + let deviceVersion: string if (version?.includes('.') === false) { - const replace = version?.replace(/^V|-.*$/g, ''); - const match = replace?.match(/.{1,1}/g); - const blindTiltVersion = match?.join('.') ?? '0.0.0'; - deviceVersion = blindTiltVersion; + const replace = version?.replace(/^V|-.*$/g, '') + const match = replace?.match(/./g) + const blindTiltVersion = match?.join('.') ?? '0.0.0' + deviceVersion = blindTiltVersion } else { - deviceVersion = version.replace(/^V|-.*$/g, '') ?? '0.0.0'; + deviceVersion = version.replace(/^V|-.*$/g, '') ?? '0.0.0' } this.accessory .getService(this.hap.Service.AccessoryInformation)! .setCharacteristic(this.hap.Characteristic.HardwareRevision, deviceVersion) .setCharacteristic(this.hap.Characteristic.FirmwareRevision, deviceVersion) .getCharacteristic(this.hap.Characteristic.FirmwareRevision) - .updateValue(deviceVersion); - this.accessory.context.version = deviceVersion; - await this.debugLog(`version: ${this.accessory.context.version}`); + .updateValue(deviceVersion) + this.accessory.context.version = deviceVersion + await this.debugLog(`version: ${this.accessory.context.version}`) } async parseStatusWebhook(): Promise { - await this.debugLog('parseStatusWebhook'); - await this.debugLog(`(slidePosition, battery, version) = Webhook:(${this.webhookContext.direction}, ${this.webhookContext.slidePosition},` - + ` ${this.webhookContext.battery}, ${this.webhookContext.version}, current:(${this.WindowCovering.CurrentHorizontalTiltAngle},` - + ` ${this.WindowCovering.CurrentPosition}, ${this.Battery.BatteryLevel}, ${this.accessory.context.version})`); + await this.debugLog('parseStatusWebhook') + await this.debugLog(`(slidePosition, battery, version) = Webhook:(${this.webhookContext.direction}, ${this.webhookContext.slidePosition}, ${this.webhookContext.battery}, ${this.webhookContext.version}, current:(${this.WindowCovering.CurrentHorizontalTiltAngle}, ${this.WindowCovering.CurrentPosition}, ${this.Battery.BatteryLevel}, ${this.accessory.context.version})`) // CurrentPosition and CurrentHorizontalTiltAngle - await this.getCurrentPosttionDirection(this.webhookContext.direction, this.webhookContext.slidePosition); + await this.getCurrentPosttionDirection(this.webhookContext.direction, this.webhookContext.slidePosition) // BatteryLevel - this.Battery.BatteryLevel = this.webhookContext.battery; - await this.debugLog(`BatteryLevel: ${this.Battery.BatteryLevel}`); + this.Battery.BatteryLevel = this.webhookContext.battery + await this.debugLog(`BatteryLevel: ${this.Battery.BatteryLevel}`) // StatusLowBattery this.Battery.StatusLowBattery = this.Battery.BatteryLevel < 10 - ? this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW : this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL; - await this.debugLog(`StatusLowBattery: ${this.Battery.StatusLowBattery}`); + ? this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW + : this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL + await this.debugLog(`StatusLowBattery: ${this.Battery.StatusLowBattery}`) // Firmware Version - const deviceVersion = this.webhookContext.version.replace(/^V|-.*$/g, '') ?? '0.0.0'; + const deviceVersion = this.webhookContext.version.replace(/^V|-.*$/g, '') ?? '0.0.0' this.accessory .getService(this.hap.Service.AccessoryInformation)! .setCharacteristic(this.hap.Characteristic.HardwareRevision, deviceVersion) .setCharacteristic(this.hap.Characteristic.FirmwareRevision, deviceVersion) .getCharacteristic(this.hap.Characteristic.FirmwareRevision) - .updateValue(deviceVersion); - this.accessory.context.version = deviceVersion; - await this.debugSuccessLog(`version: ${this.accessory.context.version}`); + .updateValue(deviceVersion) + this.accessory.context.version = deviceVersion + await this.debugSuccessLog(`version: ${this.accessory.context.version}`) } /** @@ -380,123 +444,128 @@ export class BlindTilt extends deviceBase { */ async refreshStatus(): Promise { if (!this.device.enableCloudService && this.OpenAPI) { - await this.errorLog(`refreshStatus enableCloudService: ${this.device.enableCloudService}`); - } else if (this.BLE || this.config.options?.BLE) { - await this.BLERefreshStatus(); + await this.errorLog(`refreshStatus enableCloudService: ${this.device.enableCloudService}`) + } else if (this.BLE) { + await this.BLERefreshStatus() } else if (this.OpenAPI && this.platform.config.credentials?.token) { - await this.openAPIRefreshStatus(); + await this.openAPIRefreshStatus() } else { - await this.offlineOff(); - await this.debugWarnLog(`Connection Type: ${this.device.connectionType}, refreshStatus will not happen.`); + await this.offlineOff() + await this.debugWarnLog(`Connection Type: ${this.device.connectionType}, refreshStatus will not happen.`) } } async BLERefreshStatus(): Promise { + await this.debugLog('BLERefreshStatus') + const switchbot = await this.switchbotBLE() + if (switchbot === undefined) { + await this.BLERefreshConnection(switchbot) + } else { + // Start to monitor advertisement packets + (async () => { + // Start to monitor advertisement packets + const serviceData = await this.monitorAdvertisementPackets(switchbot) as blindTiltServiceData + // Update HomeKit + if (serviceData.model === SwitchBotBLEModel.BlindTilt && serviceData.modelName === SwitchBotBLEModelName.BlindTilt) { + this.serviceData = serviceData + await this.BLEparseStatus() + await this.updateHomeKitCharacteristics() + } else { + await this.errorLog(`failed to get serviceData, serviceData: ${serviceData}`) + await this.BLERefreshConnection(switchbot) + } + })() + } + } + + async registerPlatformBLE(): Promise { + await this.debugLog('registerPlatformBLE') if (this.config.options?.BLE) { - await this.debugLog('is listening to Platform BLE.'); - this.device.bleMac = this.device.deviceId!.match(/.{1,2}/g)!.join(':').toLowerCase(); - await this.debugLog(`bleMac: ${this.device.bleMac}`); + await this.debugLog('is listening to Platform BLE.') + this.device.bleMac = this.device.deviceId!.match(/.{1,2}/g)!.join(':').toLowerCase() + await this.debugLog(`bleMac: ${this.device.bleMac}`) this.platform.bleEventHandler[this.device.bleMac] = async (context: blindTiltServiceData) => { try { - await this.debugLog(`received BLE: ${JSON.stringify(context)}`); - this.serviceData = context; - await this.BLEparseStatus(); - await this.updateHomeKitCharacteristics(); + await this.debugLog(`received BLE: ${JSON.stringify(context)}`) + this.serviceData = context + await this.BLEparseStatus() + await this.updateHomeKitCharacteristics() } catch (e: any) { - await this.errorLog(`failed to handle BLE. Received: ${JSON.stringify(context)} Error: ${e}`); + await this.errorLog(`failed to handle BLE. Received: ${JSON.stringify(context)} Error: ${e}`) } - }; - } else { - await this.debugLog('is using Device BLE Scanning.'); - const switchbot = await this.switchbotBLE(); - if (switchbot === undefined) { - await this.BLERefreshConnection(switchbot); - } else { - // Start to monitor advertisement packets - (async () => { - // Start to monitor advertisement packets - const serviceData = await this.monitorAdvertisementPackets(switchbot) as blindTiltServiceData; - // Update HomeKit - if (serviceData.model === SwitchBotBLEModel.BlindTilt && serviceData.modelName === SwitchBotBLEModelName.BlindTilt) { - this.serviceData = serviceData; - await this.BLEparseStatus(); - await this.updateHomeKitCharacteristics(); - } else { - await this.errorLog(`failed to get serviceData, serviceData: ${serviceData}`); - await this.BLERefreshConnection(switchbot); - } - })(); } + } else { + await this.debugLog('is not listening to Platform BLE') } } async openAPIRefreshStatus(): Promise { - await this.debugLog('openAPIRefreshStatus'); + await this.debugLog('openAPIRefreshStatus') try { - const { body, statusCode } = await this.deviceRefreshStatus(); - const deviceStatus: any = await body.json(); - await this.debugLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`);; + const { body, statusCode } = await this.deviceRefreshStatus() + const deviceStatus: any = await body.json() + await this.debugLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`) if (await this.successfulStatusCodes(statusCode, deviceStatus)) { - await this.debugSuccessLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`); - this.deviceStatus = deviceStatus.body; - await this.openAPIparseStatus(); - await this.updateHomeKitCharacteristics(); + await this.debugSuccessLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`) + this.deviceStatus = deviceStatus.body + await this.openAPIparseStatus() + await this.updateHomeKitCharacteristics() } else { - await this.debugWarnLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`); - await this.debugWarnLog(statusCode, deviceStatus); + await this.debugWarnLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`) + await this.debugWarnLog(statusCode, deviceStatus) } } catch (e: any) { - await this.apiError(e); - await this.errorLog(`failed openAPIRefreshStatus with ${this.device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`); + await this.apiError(e) + await this.errorLog(`failed openAPIRefreshStatus with ${this.device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`) } } async registerWebhook(): Promise { if (this.device.webhook) { - await this.debugLog('is listening webhook.'); + await this.debugLog('is listening webhook.') this.platform.webhookEventHandler[this.device.deviceId] = async (context: blindTiltWebhookContext) => { try { - await this.debugLog(`received Webhook: ${JSON.stringify(context)}`); - this.webhookContext = context; - await this.parseStatusWebhook(); - await this.updateHomeKitCharacteristics(); + await this.debugLog(`received Webhook: ${JSON.stringify(context)}`) + this.webhookContext = context + await this.parseStatusWebhook() + await this.updateHomeKitCharacteristics() } catch (e: any) { - await this.errorLog(`failed to handle webhook. Received: ${JSON.stringify(context)} Error: ${e}`); + await this.errorLog(`failed to handle webhook. Received: ${JSON.stringify(context)} Error: ${e}`) } - }; + } } else { - await this.debugLog('is not listening webhook.'); + await this.debugLog('is not listening webhook.') } } async pushChanges(): Promise { if (!this.device.enableCloudService && this.OpenAPI) { - await this.errorLog(`pushChanges enableCloudService: ${this.device.enableCloudService}`); + await this.errorLog(`pushChanges enableCloudService: ${this.device.enableCloudService}`) } else if (this.BLE) { - await this.BLEpushChanges(); + await this.BLEpushChanges() } else if (this.OpenAPI && this.platform.config.credentials?.token) { - await this.openAPIpushChanges(); + await this.openAPIpushChanges() } else { - await this.offlineOff(); - await this.debugWarnLog(`Connection Type: ${this.device.connectionType}, pushChanges will not happen.`); + await this.offlineOff() + await this.debugWarnLog(`Connection Type: ${this.device.connectionType}, pushChanges will not happen.`) } // Refresh the status from the API interval(15000) .pipe(skipWhile(() => this.blindTiltUpdateInProgress)) .pipe(take(1)) .subscribe(async () => { - await this.refreshStatus(); - }); + await this.refreshStatus() + }) } async BLEpushChanges(): Promise { - await this.debugLog('BLEpushChanges'); + await this.debugLog('BLEpushChanges') if (this.WindowCovering.TargetPosition !== this.WindowCovering.CurrentPosition) { - await this.debugLog(`BLEpushChanges On: ${this.WindowCovering.TargetPosition} OnCached: ${this.WindowCovering.CurrentPosition}`); - const switchbot = await this.platform.connectBLE(this.accessory, this.device); - await this.convertBLEAddress(); - const { setPositionMode, Mode }: { setPositionMode: number; Mode: string; } = await this.setPerformance(); - await this.debugLog(`Mode: ${Mode}, setPositionMode: ${setPositionMode}`); + await this.debugLog(`BLEpushChanges On: ${this.WindowCovering.TargetPosition} OnCached: ${this.WindowCovering.CurrentPosition}`) + const switchbot = await this.platform.connectBLE(this.accessory, this.device) + await this.convertBLEAddress() + const { setPositionMode, Mode }: { setPositionMode: number, Mode: string } = await this.setPerformance() + await this.debugLog(`Mode: ${Mode}, setPositionMode: ${setPositionMode}`) if (switchbot !== false) { switchbot .discover({ model: this.device.bleModel, quick: true, id: this.device.bleMac }) @@ -504,82 +573,78 @@ export class BlindTilt extends deviceBase { return await this.retryBLE({ max: await this.maxRetryBLE(), fn: async () => { - return await device_list[0].runToPos(100 - Number(this.WindowCovering.TargetPosition), setPositionMode); + return await device_list[0].runToPos(100 - Number(this.WindowCovering.TargetPosition), setPositionMode) }, - }); + }) }) .then(async () => { - await this.successLog(`TargetPostion: ${this.WindowCovering.TargetPosition} sent over SwitchBot BLE, sent successfully`); - await this.updateHomeKitCharacteristics(); + await this.successLog(`TargetPostion: ${this.WindowCovering.TargetPosition} sent over SwitchBot BLE, sent successfully`) + await this.updateHomeKitCharacteristics() }) .catch(async (e: any) => { - await this.apiError(e); - await this.errorLog(`failed BLEpushChanges with ${this.device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`); - await this.BLEPushConnection(); - }); + await this.apiError(e) + await this.errorLog(`failed BLEpushChanges with ${this.device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`) + await this.BLEPushConnection() + }) } else { - await this.errorLog(`wasn't able to establish BLE Connection, node-switchbot: ${switchbot}`); - await this.BLEPushConnection(); + await this.errorLog(`wasn't able to establish BLE Connection, node-switchbot: ${switchbot}`) + await this.BLEPushConnection() } } else { - await this.debugLog(`No changes (BLEpushChanges), TargetPosition: ${this.WindowCovering.TargetPosition},` - + ` CurrentPosition: ${this.WindowCovering.CurrentPosition}`); + await this.debugLog(`No changes (BLEpushChanges), TargetPosition: ${this.WindowCovering.TargetPosition}, CurrentPosition: ${this.WindowCovering.CurrentPosition}`) } } async openAPIpushChanges(): Promise { - await this.debugLog('openAPIpushChanges'); - const hasDifferentAndRelevantHorizontalTiltAngle = - this.mappingMode === BlindTiltMappingMode.UseTiltForDirection - && this.WindowCovering.TargetHorizontalTiltAngle !== this.WindowCovering.CurrentHorizontalTiltAngle; + await this.debugLog('openAPIpushChanges') + const hasDifferentAndRelevantHorizontalTiltAngle + = this.mappingMode === BlindTiltMappingMode.UseTiltForDirection + && this.WindowCovering.TargetHorizontalTiltAngle !== this.WindowCovering.CurrentHorizontalTiltAngle if (this.WindowCovering.TargetPosition !== this.WindowCovering.CurrentPosition || hasDifferentAndRelevantHorizontalTiltAngle || this.device.disableCaching) { - const [direction, position] = this.mapHomekitValuesToDeviceValues(Number(this.WindowCovering.TargetPosition), - Number(this.WindowCovering.TargetHorizontalTiltAngle)); - const { Mode, setPositionMode }: { setPositionMode: number; Mode: string; } = await this.setPerformance(); - await this.debugLog(`Pushing ${this.WindowCovering.TargetPosition} (device = ${direction};${position})`); - await this.debugLog(`Mode: ${Mode}, setPositionMode: ${setPositionMode}`); - let bodyChange: string; + const [direction, position] = this.mapHomekitValuesToDeviceValues(Number(this.WindowCovering.TargetPosition), Number(this.WindowCovering.TargetHorizontalTiltAngle)) + const { Mode, setPositionMode }: { setPositionMode: number, Mode: string } = await this.setPerformance() + await this.debugLog(`Pushing ${this.WindowCovering.TargetPosition} (device = ${direction};${position})`) + await this.debugLog(`Mode: ${Mode}, setPositionMode: ${setPositionMode}`) + let bodyChange: string if (position === 100) { bodyChange = JSON.stringify({ command: 'fullyOpen', parameter: 'default', commandType: 'command', - }); + }) } else if (position === 0) { bodyChange = JSON.stringify({ command: direction === 'up' ? 'closeUp' : 'closeDown', parameter: 'default', commandType: 'command', - }); + }) } else { bodyChange = JSON.stringify({ command: 'setPosition', parameter: `${direction};${position}`, commandType: 'command', - }); + }) } - await this.debugLog(`SwitchBot OpenAPI bodyChange: ${JSON.stringify(bodyChange)}`); + await this.debugLog(`SwitchBot OpenAPI bodyChange: ${JSON.stringify(bodyChange)}`) try { - const { body, statusCode } = await this.pushChangeRequest(bodyChange); - const deviceStatus: any = await body.json(); - await this.debugLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`); + const { body, statusCode } = await this.pushChangeRequest(bodyChange) + const deviceStatus: any = await body.json() + await this.debugLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`) if (await this.successfulStatusCodes(statusCode, deviceStatus)) { - await this.debugSuccessLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`); - await this.updateHomeKitCharacteristics(); + await this.debugSuccessLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`) + await this.updateHomeKitCharacteristics() } else { - await this.statusCode(statusCode); - await this.statusCode(deviceStatus.statusCode); + await this.statusCode(statusCode) + await this.statusCode(deviceStatus.statusCode) } } catch (e: any) { - await this.apiError(e); - await this.errorLog(`failed openAPIpushChanges with ${this.device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`); + await this.apiError(e) + await this.errorLog(`failed openAPIpushChanges with ${this.device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`) } } else { - await this.debugLog(`No changes (openAPIpushChanges), TargetPosition: ${this.WindowCovering.TargetPosition},` - + ` CurrentPosition: ${this.WindowCovering.CurrentPosition}`); - await this.debugLog(`No changes (openAPIpushChanges), TargetHorizontalTiltAngle: ${this.WindowCovering.TargetHorizontalTiltAngle},` - + ` CurrentHorizontalTiltAngle: ${this.WindowCovering.CurrentHorizontalTiltAngle}`); + await this.debugLog(`No changes (openAPIpushChanges), TargetPosition: ${this.WindowCovering.TargetPosition}, CurrentPosition: ${this.WindowCovering.CurrentPosition}`) + await this.debugLog(`No changes (openAPIpushChanges), TargetHorizontalTiltAngle: ${this.WindowCovering.TargetHorizontalTiltAngle}, CurrentHorizontalTiltAngle: ${this.WindowCovering.CurrentHorizontalTiltAngle}`) } } @@ -588,15 +653,15 @@ export class BlindTilt extends deviceBase { */ async TargetHorizontalTiltAngleSet(value: CharacteristicValue): Promise { if (this.WindowCovering.TargetHorizontalTiltAngle !== this.accessory.context.TargetHorizontalTiltAngle) { - await this.debugLog(`Set TargetHorizontalTiltAngle: ${value}`); + await this.debugLog(`Set TargetHorizontalTiltAngle: ${value}`) } else { - await this.debugLog(`No changes, TargetHorizontalTiltAngle: ${value}`); + await this.debugLog(`No changes, TargetHorizontalTiltAngle: ${value}`) } - //value = value < 0 ? -90 : 90; - this.WindowCovering.TargetHorizontalTiltAngle = value; - await this.mqtt('TargetHorizontalTiltAngle', this.WindowCovering.TargetHorizontalTiltAngle); - await this.startUpdatingBlindTiltIfNeeded(); + // value = value < 0 ? -90 : 90; + this.WindowCovering.TargetHorizontalTiltAngle = value + await this.mqtt('TargetHorizontalTiltAngle', this.WindowCovering.TargetHorizontalTiltAngle) + await this.startUpdatingBlindTiltIfNeeded() } /** @@ -604,172 +669,188 @@ export class BlindTilt extends deviceBase { */ async TargetPositionSet(value: CharacteristicValue): Promise { if (this.WindowCovering.TargetPosition !== this.accessory.context.TargetPosition) { - await this.debugLog(`Set TargetPosition: ${value}`); + await this.debugLog(`Set TargetPosition: ${value}`) } else { - await this.debugLog(`No changes, TargetPosition: ${value}`); + await this.debugLog(`No changes, TargetPosition: ${value}`) } - this.WindowCovering.TargetPosition = value; - await this.mqtt('TargetPosition', this.WindowCovering.TargetPosition); - await this.startUpdatingBlindTiltIfNeeded(); + this.WindowCovering.TargetPosition = value + await this.mqtt('TargetPosition', this.WindowCovering.TargetPosition) + await this.startUpdatingBlindTiltIfNeeded() } async startUpdatingBlindTiltIfNeeded(): Promise { - await this.setMinMax(); - await this.debugLog('setMinMax'); + await this.setMinMax() + await this.debugLog('setMinMax') if (this.WindowCovering.TargetPosition > this.WindowCovering.CurrentPosition || this.WindowCovering.TargetHorizontalTiltAngle !== this.WindowCovering.CurrentHorizontalTiltAngle) { - this.WindowCovering.PositionState = this.hap.Characteristic.PositionState.INCREASING; - this.setNewTarget = true; - await this.debugLog(`value: ${this.WindowCovering.CurrentPosition}, CurrentPosition: ${this.WindowCovering.CurrentPosition}`); + this.WindowCovering.PositionState = this.hap.Characteristic.PositionState.INCREASING + this.setNewTarget = true + await this.debugLog(`value: ${this.WindowCovering.CurrentPosition}, CurrentPosition: ${this.WindowCovering.CurrentPosition}`) } else if (this.WindowCovering.TargetPosition < this.WindowCovering.CurrentPosition) { - this.WindowCovering.PositionState = this.hap.Characteristic.PositionState.DECREASING; - this.setNewTarget = true; - await this.debugLog(`value: ${this.WindowCovering.CurrentPosition}, CurrentPosition: ${this.WindowCovering.CurrentPosition}`); + this.WindowCovering.PositionState = this.hap.Characteristic.PositionState.DECREASING + this.setNewTarget = true + await this.debugLog(`value: ${this.WindowCovering.CurrentPosition}, CurrentPosition: ${this.WindowCovering.CurrentPosition}`) } else { - this.WindowCovering.PositionState = this.hap.Characteristic.PositionState.STOPPED; - this.setNewTarget = false; - await this.debugLog(`value: ${this.WindowCovering.CurrentPosition}, CurrentPosition: ${this.WindowCovering.CurrentPosition}`); + this.WindowCovering.PositionState = this.hap.Characteristic.PositionState.STOPPED + this.setNewTarget = false + await this.debugLog(`value: ${this.WindowCovering.CurrentPosition}, CurrentPosition: ${this.WindowCovering.CurrentPosition}`) } - this.WindowCovering.Service.setCharacteristic(this.hap.Characteristic.PositionState, this.WindowCovering.PositionState); - this.WindowCovering.Service.getCharacteristic(this.hap.Characteristic.PositionState).updateValue(this.WindowCovering.PositionState); + this.WindowCovering.Service.setCharacteristic(this.hap.Characteristic.PositionState, this.WindowCovering.PositionState) + this.WindowCovering.Service.getCharacteristic(this.hap.Characteristic.PositionState).updateValue(this.WindowCovering.PositionState) /** * If Blind Tilt movement time is short, the moving flag from backend is always false. * The minimum time depends on the network control latency. */ - clearTimeout(this.setNewTargetTimer); - await this.debugLog(`deviceUpdateRate: ${this.deviceUpdateRate}`); + clearTimeout(this.setNewTargetTimer) + await this.debugLog(`deviceUpdateRate: ${this.deviceUpdateRate}`) if (this.setNewTarget) { this.setNewTargetTimer = setTimeout(async () => { - await this.debugLog(`setNewTarget ${this.setNewTarget} timeout`); - this.setNewTarget = false; - }, this.deviceUpdateRate * 1000); + await this.debugLog(`setNewTarget ${this.setNewTarget} timeout`) + this.setNewTarget = false + }, this.deviceUpdateRate * 1000) + } + this.doBlindTiltUpdate.next() + } + + /** + * Handle requests to set the value of the "Target Position" characteristic + */ + async OpenModeSwitchSet(value: CharacteristicValue): Promise { + if (this.OpenModeSwitch && this.device.blindTilt?.silentModeSwitch) { + this.debugLog(`Silent Open Mode: ${value}`) + this.OpenModeSwitch.On = value + this.accessory.context.OpenModeSwitch.On = value + this.doBlindTiltUpdate.next() + } + } + + /** + * Handle requests to set the value of the "Target Position" characteristic + */ + async CloseModeSwitchSet(value: CharacteristicValue): Promise { + if (this.CloseModeSwitch && this.device.blindTilt?.silentModeSwitch) { + this.debugLog(`Silent Close Mode: ${value}`) + this.CloseModeSwitch.On = value + this.accessory.context.CloseModeSwitch.On = value + this.doBlindTiltUpdate.next() } - this.doBlindTiltUpdate.next(); } async updateHomeKitCharacteristics(): Promise { - await this.setMinMax(); + await this.setMinMax() // CurrentHorizontalTiltAngle if (this.mappingMode === BlindTiltMappingMode.UseTiltForDirection) { - await this.updateCharacteristic(this.WindowCovering.Service, this.hap.Characteristic.CurrentHorizontalTiltAngle, - this.WindowCovering.CurrentHorizontalTiltAngle, 'CurrentHorizontalTiltAngle'); + await this.updateCharacteristic(this.WindowCovering.Service, this.hap.Characteristic.CurrentHorizontalTiltAngle, this.WindowCovering.CurrentHorizontalTiltAngle, 'CurrentHorizontalTiltAngle') } // CurrentPosition - await this.updateCharacteristic(this.WindowCovering.Service, this.hap.Characteristic.CurrentPosition, - this.WindowCovering.CurrentPosition, 'CurrentPosition'); + await this.updateCharacteristic(this.WindowCovering.Service, this.hap.Characteristic.CurrentPosition, this.WindowCovering.CurrentPosition, 'CurrentPosition') // PositionState - await this.updateCharacteristic(this.WindowCovering.Service, this.hap.Characteristic.PositionState, - this.WindowCovering.PositionState, 'PositionState'); + await this.updateCharacteristic(this.WindowCovering.Service, this.hap.Characteristic.PositionState, this.WindowCovering.PositionState, 'PositionState') // TargetPosition - await this.updateCharacteristic(this.WindowCovering.Service, this.hap.Characteristic.TargetPosition, - this.WindowCovering.TargetPosition, 'TargetPosition'); + await this.updateCharacteristic(this.WindowCovering.Service, this.hap.Characteristic.TargetPosition, this.WindowCovering.TargetPosition, 'TargetPosition') // CurrentAmbientLightLevel if (!this.device.blindTilt?.hide_lightsensor && this.LightSensor?.Service) { - const history = { time: Math.round(new Date().valueOf() / 1000), lux: this.LightSensor.CurrentAmbientLightLevel }; - await this.updateCharacteristic(this.LightSensor?.Service, this.hap.Characteristic.CurrentAmbientLightLevel, - this.LightSensor?.CurrentAmbientLightLevel, 'CurrentAmbientLightLevel', history); + const history = { time: Math.round(new Date().valueOf() / 1000), lux: this.LightSensor.CurrentAmbientLightLevel } + await this.updateCharacteristic(this.LightSensor?.Service, this.hap.Characteristic.CurrentAmbientLightLevel, this.LightSensor?.CurrentAmbientLightLevel, 'CurrentAmbientLightLevel', history) } // BatteryLevel - await this.updateCharacteristic(this.Battery.Service, this.hap.Characteristic.BatteryLevel, - this.Battery.BatteryLevel, 'BatteryLevel'); + await this.updateCharacteristic(this.Battery.Service, this.hap.Characteristic.BatteryLevel, this.Battery.BatteryLevel, 'BatteryLevel') // StatusLowBattery - await this.updateCharacteristic(this.Battery.Service, this.hap.Characteristic.StatusLowBattery, - this.Battery.StatusLowBattery, 'StatusLowBattery'); + await this.updateCharacteristic(this.Battery.Service, this.hap.Characteristic.StatusLowBattery, this.Battery.StatusLowBattery, 'StatusLowBattery') // ChargingState - await this.updateCharacteristic(this.Battery.Service, this.hap.Characteristic.ChargingState, - this.Battery.ChargingState, 'ChargingState'); + await this.updateCharacteristic(this.Battery.Service, this.hap.Characteristic.ChargingState, this.Battery.ChargingState, 'ChargingState') } async BLEPushConnection() { if (this.platform.config.credentials?.token && this.device.connectionType === 'BLE/OpenAPI') { - await this.warnLog('Using OpenAPI Connection to Push Changes'); - await this.openAPIpushChanges(); + await this.warnLog('Using OpenAPI Connection to Push Changes') + await this.openAPIpushChanges() } } async BLERefreshConnection(switchbot: any): Promise { - await this.errorLog(`wasn't able to establish BLE Connection, node-switchbot: ${switchbot}`); + await this.errorLog(`wasn't able to establish BLE Connection, node-switchbot: ${switchbot}`) if (this.platform.config.credentials?.token && this.device.connectionType === 'BLE/OpenAPI') { - await this.warnLog('Using OpenAPI Connection to Refresh Status'); - await this.openAPIRefreshStatus(); + await this.warnLog('Using OpenAPI Connection to Refresh Status') + await this.openAPIRefreshStatus() } } async setPerformance() { - let setPositionMode: number; - let Mode: string; + let setPositionMode: number + let Mode: string if (Number(this.WindowCovering.TargetPosition) > 50) { - if (this.device.blindTilt?.setOpenMode === '1') { - setPositionMode = 1; - Mode = 'Silent Mode'; - } else if (this.device.blindTilt?.setOpenMode === '0') { - setPositionMode = 0; - Mode = 'Performance Mode'; + if (this.device.blindTilt?.setOpenMode === '1' || this.OpenModeSwitch?.On) { + setPositionMode = 1 + Mode = 'Silent Mode' + } else if (this.device.blindTilt?.setOpenMode === '0' || !this.OpenModeSwitch?.On) { + setPositionMode = 0 + Mode = 'Performance Mode' } else { - setPositionMode = 0; - Mode = 'Default Mode'; + setPositionMode = 0 + Mode = 'Default Mode' } } else { - if (this.device.blindTilt?.setCloseMode === '1') { - setPositionMode = 1; - Mode = 'Silent Mode'; - } else if (this.device.blindTilt?.setOpenMode === '0') { - setPositionMode = 0; - Mode = 'Performance Mode'; + if (this.device.blindTilt?.setCloseMode === '1' || this.CloseModeSwitch?.On) { + setPositionMode = 1 + Mode = 'Silent Mode' + } else if (this.device.blindTilt?.setOpenMode === '0' || !this.CloseModeSwitch?.On) { + setPositionMode = 0 + Mode = 'Performance Mode' } else { - setPositionMode = 0; - Mode = 'Default Mode'; + setPositionMode = 0 + Mode = 'Default Mode' } } - return { setPositionMode, Mode }; + return { setPositionMode, Mode } } async setMinMax(): Promise { if (this.device.blindTilt?.set_min) { if (Number(this.WindowCovering.CurrentPosition) <= this.device.blindTilt?.set_min) { - this.WindowCovering.CurrentPosition = 0; + this.WindowCovering.CurrentPosition = 0 } } if (this.device.blindTilt?.set_max) { if (Number(this.WindowCovering.CurrentPosition) >= this.device.blindTilt?.set_max) { - this.WindowCovering.CurrentPosition = 100; + this.WindowCovering.CurrentPosition = 100 } } if (this.device.history) { - const motion = this.accessory.getService(this.hap.Service.MotionSensor); - const state = Number(this.WindowCovering.CurrentPosition) > 0 ? 1 : 0; - motion?.updateCharacteristic(this.hap.Characteristic.MotionDetected, state); + const motion = this.accessory.getService(this.hap.Service.MotionSensor) + const state = Number(this.WindowCovering.CurrentPosition) > 0 ? 1 : 0 + motion?.updateCharacteristic(this.hap.Characteristic.MotionDetected, state) } if (this.mappingMode === BlindTiltMappingMode.UseTiltForDirection) { - this.WindowCovering.CurrentHorizontalTiltAngle = Number(this.WindowCovering.CurrentHorizontalTiltAngle) < 0 ? -90 : 90; + this.WindowCovering.CurrentHorizontalTiltAngle = Number(this.WindowCovering.CurrentHorizontalTiltAngle) < 0 ? -90 : 90 } } async offlineOff(): Promise { if (this.device.offline) { - this.WindowCovering.Service.updateCharacteristic(this.hap.Characteristic.CurrentPosition, 100); - this.WindowCovering.Service.updateCharacteristic(this.hap.Characteristic.PositionState, this.hap.Characteristic.PositionState.STOPPED); - this.WindowCovering.Service.updateCharacteristic(this.hap.Characteristic.TargetPosition, 100); - this.WindowCovering.Service.updateCharacteristic(this.hap.Characteristic.CurrentHorizontalTiltAngle, 90); - this.WindowCovering.Service.updateCharacteristic(this.hap.Characteristic.TargetHorizontalTiltAngle, 90); + this.WindowCovering.Service.updateCharacteristic(this.hap.Characteristic.CurrentPosition, 100) + this.WindowCovering.Service.updateCharacteristic(this.hap.Characteristic.PositionState, this.hap.Characteristic.PositionState.STOPPED) + this.WindowCovering.Service.updateCharacteristic(this.hap.Characteristic.TargetPosition, 100) + this.WindowCovering.Service.updateCharacteristic(this.hap.Characteristic.CurrentHorizontalTiltAngle, 90) + this.WindowCovering.Service.updateCharacteristic(this.hap.Characteristic.TargetHorizontalTiltAngle, 90) } } async apiError(e: any): Promise { - this.WindowCovering.Service.updateCharacteristic(this.hap.Characteristic.CurrentPosition, e); - this.WindowCovering.Service.updateCharacteristic(this.hap.Characteristic.PositionState, e); - this.WindowCovering.Service.updateCharacteristic(this.hap.Characteristic.TargetPosition, e); - this.WindowCovering.Service.updateCharacteristic(this.hap.Characteristic.CurrentHorizontalTiltAngle, e); - this.WindowCovering.Service.updateCharacteristic(this.hap.Characteristic.TargetHorizontalTiltAngle, e); - this.Battery.Service.updateCharacteristic(this.hap.Characteristic.BatteryLevel, e); - this.Battery.Service.updateCharacteristic(this.hap.Characteristic.StatusLowBattery, e); - this.Battery.Service.updateCharacteristic(this.hap.Characteristic.ChargingState, e); + this.WindowCovering.Service.updateCharacteristic(this.hap.Characteristic.CurrentPosition, e) + this.WindowCovering.Service.updateCharacteristic(this.hap.Characteristic.PositionState, e) + this.WindowCovering.Service.updateCharacteristic(this.hap.Characteristic.TargetPosition, e) + this.WindowCovering.Service.updateCharacteristic(this.hap.Characteristic.CurrentHorizontalTiltAngle, e) + this.WindowCovering.Service.updateCharacteristic(this.hap.Characteristic.TargetHorizontalTiltAngle, e) + this.Battery.Service.updateCharacteristic(this.hap.Characteristic.BatteryLevel, e) + this.Battery.Service.updateCharacteristic(this.hap.Characteristic.StatusLowBattery, e) + this.Battery.Service.updateCharacteristic(this.hap.Characteristic.ChargingState, e) if (!this.device.blindTilt?.hide_lightsensor && this.LightSensor?.Service) { - this.LightSensor.Service.updateCharacteristic(this.hap.Characteristic.CurrentAmbientLightLevel, e); - this.LightSensor.Service.updateCharacteristic(this.hap.Characteristic.StatusActive, e); + this.LightSensor.Service.updateCharacteristic(this.hap.Characteristic.CurrentAmbientLightLevel, e) + this.LightSensor.Service.updateCharacteristic(this.hap.Characteristic.StatusActive, e) } } @@ -777,58 +858,58 @@ export class BlindTilt extends deviceBase { direction: blindTiltStatus['direction'] | blindTiltWebhookContext['direction'], slidePosition: blindTiltStatus['slidePosition'] | blindTiltWebhookContext['slidePosition'], ) { - const [homekitPosition, homekitTiltAngle] = this.mapDeviceValuesToHomekitValues(Number(slidePosition), - String(direction)); - await this.debugLog(`Slide Position: ${slidePosition}`); - await this.debugLog(`Homekit Position: ${homekitPosition}`); + const [homekitPosition, homekitTiltAngle] = this.mapDeviceValuesToHomekitValues(Number(slidePosition), String(direction)) + await this.debugLog(`Slide Position: ${slidePosition}`) + await this.debugLog(`Homekit Position: ${homekitPosition}`) - this.WindowCovering.CurrentPosition = homekitPosition; - await this.setMinMax(); - await this.debugLog(`CurrentPosition: ${this.WindowCovering.CurrentPosition}`); + this.WindowCovering.CurrentPosition = homekitPosition + await this.setMinMax() + await this.debugLog(`CurrentPosition: ${this.WindowCovering.CurrentPosition}`) if (homekitTiltAngle) { - this.WindowCovering.CurrentHorizontalTiltAngle = homekitTiltAngle!; - await this.debugLog(`CurrentHorizontalTiltAngle: ${this.WindowCovering.CurrentHorizontalTiltAngle}`); + this.WindowCovering.CurrentHorizontalTiltAngle = homekitTiltAngle! + await this.debugLog(`CurrentHorizontalTiltAngle: ${this.WindowCovering.CurrentHorizontalTiltAngle}`) } if (this.setNewTarget) { - await this.infoLog('Checking Status ...'); - await this.setMinMax(); + this.blindTiltMoving = true + await this.infoLog('Checking Status ...') + await this.setMinMax() if (this.WindowCovering.TargetPosition > this.WindowCovering.CurrentPosition || (homekitTiltAngle && this.WindowCovering.TargetHorizontalTiltAngle !== this.WindowCovering.CurrentHorizontalTiltAngle)) { - await this.debugLog(`Closing, CurrentPosition: ${this.WindowCovering.CurrentPosition}`); - this.WindowCovering.PositionState = this.hap.Characteristic.PositionState.INCREASING; - this.WindowCovering.Service.getCharacteristic(this.hap.Characteristic.PositionState).updateValue(this.WindowCovering.PositionState); - await this.debugLog(`Increasing, PositionState: ${this.WindowCovering.PositionState}`); + await this.debugLog(`Closing, CurrentPosition: ${this.WindowCovering.CurrentPosition}`) + this.WindowCovering.PositionState = this.hap.Characteristic.PositionState.INCREASING + this.WindowCovering.Service.getCharacteristic(this.hap.Characteristic.PositionState).updateValue(this.WindowCovering.PositionState) + await this.debugLog(`Increasing, PositionState: ${this.WindowCovering.PositionState}`) } else if (this.WindowCovering.TargetPosition < this.WindowCovering.CurrentPosition) { - await this.debugLog(`Opening, CurrentPosition: ${this.WindowCovering.CurrentPosition}`); - this.WindowCovering.PositionState = this.hap.Characteristic.PositionState.DECREASING; - this.WindowCovering.Service.getCharacteristic(this.hap.Characteristic.PositionState).updateValue(this.WindowCovering.PositionState); - await this.debugLog(`Decreasing, PositionState: ${this.WindowCovering.PositionState}`); + await this.debugLog(`Opening, CurrentPosition: ${this.WindowCovering.CurrentPosition}`) + this.WindowCovering.PositionState = this.hap.Characteristic.PositionState.DECREASING + this.WindowCovering.Service.getCharacteristic(this.hap.Characteristic.PositionState).updateValue(this.WindowCovering.PositionState) + await this.debugLog(`Decreasing, PositionState: ${this.WindowCovering.PositionState}`) } else { - await this.debugLog(`Standby because reached position, CurrentPosition: ${this.WindowCovering.CurrentPosition}`); - this.WindowCovering.PositionState = this.hap.Characteristic.PositionState.STOPPED; - this.WindowCovering.Service.getCharacteristic(this.hap.Characteristic.PositionState).updateValue(this.WindowCovering.PositionState); - await this.debugLog(`Stopped, PositionState: ${this.WindowCovering.PositionState}`); + await this.debugLog(`Standby because reached position, CurrentPosition: ${this.WindowCovering.CurrentPosition}`) + this.WindowCovering.PositionState = this.hap.Characteristic.PositionState.STOPPED + this.WindowCovering.Service.getCharacteristic(this.hap.Characteristic.PositionState).updateValue(this.WindowCovering.PositionState) + await this.debugLog(`Stopped, PositionState: ${this.WindowCovering.PositionState}`) } } else { - await this.debugLog(`Standby because device not moving, CurrentPosition: ${this.WindowCovering.CurrentPosition}`); - this.WindowCovering.TargetPosition = this.WindowCovering.CurrentPosition; + this.blindTiltMoving = false + await this.debugLog(`Standby because device not moving, CurrentPosition: ${this.WindowCovering.CurrentPosition}`) + this.WindowCovering.TargetPosition = this.WindowCovering.CurrentPosition if (homekitTiltAngle) { - this.WindowCovering.TargetHorizontalTiltAngle = this.WindowCovering.CurrentHorizontalTiltAngle; + this.WindowCovering.TargetHorizontalTiltAngle = this.WindowCovering.CurrentHorizontalTiltAngle } - this.WindowCovering.PositionState = this.hap.Characteristic.PositionState.STOPPED; - await this.debugLog(`Stopped, PositionState: ${this.WindowCovering.PositionState}`); + this.WindowCovering.PositionState = this.hap.Characteristic.PositionState.STOPPED + await this.debugLog(`Stopped, PositionState: ${this.WindowCovering.PositionState}`) } - await this.debugLog(`CurrentPosition: ${this.WindowCovering.CurrentPosition}, TargetPosition: ${this.WindowCovering.TargetPosition},` - + ` PositionState: ${this.WindowCovering.PositionState}`); + await this.debugLog(`CurrentPosition: ${this.WindowCovering.CurrentPosition}, TargetPosition: ${this.WindowCovering.TargetPosition}, PositionState: ${this.WindowCovering.PositionState}`) } /** * Maps device values to homekit values * * @param devicePosition the position as reported by the devide - * @param direction the direction as reported by the device + * @param deviceDirection the direction as reported by the device * @returns [homekit position, homekit tiltAngle] */ mapDeviceValuesToHomekitValues(devicePosition: number, deviceDirection: string): [CharacteristicValue, CharacteristicValue?] { @@ -838,44 +919,44 @@ export class BlindTilt extends deviceBase { // homekit position 0 => closed // homekit position 100 => open - const direction = deviceDirection === 'up' ? 'up' : 'down'; - this.debugLog(`Mapping device values to homekit values, devicePostion: ${devicePosition}, deviceDirection: ${direction}`); + const direction = deviceDirection === 'up' ? 'up' : 'down' + this.debugLog(`Mapping device values to homekit values, devicePostion: ${devicePosition}, deviceDirection: ${direction}`) switch (this.mappingMode) { case BlindTiltMappingMode.OnlyUp: // we only close upwards, so we see anything that is tilted downwards(<50) as open if (devicePosition < 50) { - return [100, undefined]; // fully open in homekit + return [100, undefined] // fully open in homekit } else { // we range from 50->100, with 100 being closed, so map to homekit by scaling to 0..100 and then reversing - return [100 - (devicePosition - 50) * 2, undefined]; + return [100 - (devicePosition - 50) * 2, undefined] } case BlindTiltMappingMode.OnlyDown: // we only close downwards, so we see anything that is tilted upwards(>50) as upwards if (devicePosition > 50) { - return [100, undefined]; // fully open in homekit + return [100, undefined] // fully open in homekit } else { // we range from 0..50 so scale to homekit and then reverse - return [devicePosition * 2, undefined]; + return [devicePosition * 2, undefined] } case BlindTiltMappingMode.DownAndUp: // we close both ways with closed downwards being 0 in homekit and closed upwards in homekit being 100. Open is 50 in homekit - return [devicePosition, undefined]; + return [devicePosition, undefined] case BlindTiltMappingMode.UpAndDown: // we close both ways with closed downwards being 1000 in homekit and closed upwards in homekit being 0. Open is 50 in homekit., // so we reverse the value - return [100 - devicePosition, undefined]; + return [100 - devicePosition, undefined] case BlindTiltMappingMode.UseTiltForDirection: // we use tilt for direction, so being closed downwards is 0 in homekit with -90 tilt, while being closed upwards is 0 with 90 tilt. if (devicePosition <= 50) { // downwards tilted, so we range from 0..50, with 0 being closed and 50 being open, so scale. - return [devicePosition * 2, -90]; + return [devicePosition * 2, -90] } else { // upwards tilted, so we range from 50..100, with 50 being open and 100 being closed, so scale and rever - return [100 - (devicePosition - 50) * 2, 90]; + return [100 - (devicePosition - 50) * 2, 90] } } } @@ -884,7 +965,7 @@ export class BlindTilt extends deviceBase { * Maps homekit values to device values * * @param homekitPosition the position as reported by homekit - * @param homekitTiltAngle? the tilt angle as reported by homekit + * @param homekitTiltAngle the tilt angle as reported by homekit * @returns [device position, device direction] */ mapHomekitValuesToDeviceValues(homekitPosition: number, homekitTiltAngle: number): [string, number] { @@ -899,10 +980,10 @@ export class BlindTilt extends deviceBase { switch (this.mappingMode) { case BlindTiltMappingMode.OnlyUp: // invert - return ['up', homekitPosition]; + return ['up', homekitPosition] case BlindTiltMappingMode.OnlyDown: // invert - return ['down', homekitPosition]; + return ['down', homekitPosition] case BlindTiltMappingMode.DownAndUp: // homekit 0 = downwards closed, @@ -910,10 +991,10 @@ export class BlindTilt extends deviceBase { // homekit 100 = upwards closed if (homekitPosition <= 50) { // homekit 0..50 -> device 100..0 so scale and invert - return ['down', 100 - homekitPosition * 2]; + return ['down', 100 - homekitPosition * 2] } else { // homekit 50..100 -> device 0..100, so rebase, scale and invert - return ['up', (homekitPosition - 50) * 2]; + return ['up', (homekitPosition - 50) * 2] } case BlindTiltMappingMode.UpAndDown: @@ -922,10 +1003,10 @@ export class BlindTilt extends deviceBase { // homekit 100 = upwards closed if (homekitPosition <= 50) { // homekit 0..50 -> device 0..100 so scale and invert - return ['up', homekitPosition * 2]; + return ['up', homekitPosition * 2] } else { // homekit 50..100 -> device 100...0 so scale - return ['down', 100 - homekitPosition * 2]; + return ['down', 100 - homekitPosition * 2] } case BlindTiltMappingMode.UseTiltForDirection: @@ -936,11 +1017,11 @@ export class BlindTilt extends deviceBase { if (homekitTiltAngle! <= 0) { // downwards // homekit 0..100 -> device 0..100, so invert - return ['down', homekitPosition]; + return ['down', homekitPosition] } else { // upwards // homekit 0..100 -> device 0..100, so invert - return ['up', homekitPosition]; + return ['up', homekitPosition] } } } diff --git a/src/device/bot.ts b/src/device/bot.ts index e58e9830..394f7d24 100644 --- a/src/device/bot.ts +++ b/src/device/bot.ts @@ -2,17 +2,23 @@ * * bot.ts: @switchbot/homebridge-switchbot. */ -import { deviceBase } from './device.js'; -import { SwitchBotBLEModel, SwitchBotBLEModelName } from 'node-switchbot'; -import { Subject, debounceTime, interval, skipWhile, take, tap } from 'rxjs'; - -import type { devicesConfig } from '../settings.js'; -import type { device } from '../types/devicelist.js'; -import type { SwitchBotPlatform } from '../platform.js'; -import type { botServiceData } from '../types/bledevicestatus.js'; -import type { botStatus } from '../types/devicestatus.js'; -import type { botWebhookContext } from '../types/devicewebhookstatus.js'; -import type { Service, PlatformAccessory, CharacteristicValue } from 'homebridge'; +import type { CharacteristicValue, PlatformAccessory, Service } from 'homebridge' + +import type { SwitchBotPlatform } from '../platform.js' +import type { devicesConfig } from '../settings.js' +import type { botServiceData } from '../types/bledevicestatus.js' +import type { device } from '../types/devicelist.js' +import type { botStatus } from '../types/devicestatus.js' +import type { botWebhookContext } from '../types/devicewebhookstatus.js' + +/* +* For Testing Locally: +* import { SwitchBotBLEModel, SwitchBotBLEModelName } from '/Users/Shared/GitHub/OpenWonderLabs/node-switchbot/dist/index.js'; +*/ +import { SwitchBotBLEModel, SwitchBotBLEModelName } from 'node-switchbot' +import { debounceTime, interval, skipWhile, Subject, take, tap } from 'rxjs' + +import { deviceBase } from './device.js' /** * Platform Accessory @@ -22,428 +28,403 @@ import type { Service, PlatformAccessory, CharacteristicValue } from 'homebridge export class Bot extends deviceBase { // Services private Battery: { - Name: CharacteristicValue; - Service: Service; - BatteryLevel: CharacteristicValue; - StatusLowBattery: CharacteristicValue; - }; + Name: CharacteristicValue + Service: Service + BatteryLevel: CharacteristicValue + StatusLowBattery: CharacteristicValue + } private Switch?: { - Name: CharacteristicValue; - Service: Service; - }; + Name: CharacteristicValue + Service: Service + } private GarageDoor?: { - Name: CharacteristicValue; - Service: Service; - }; + Name: CharacteristicValue + Service: Service + } private Door?: { - Name: CharacteristicValue; - Service: Service; - }; + Name: CharacteristicValue + Service: Service + } private Window?: { - Name: CharacteristicValue; - Service: Service; - }; + Name: CharacteristicValue + Service: Service + } private WindowCovering?: { - Name: CharacteristicValue; - Service: Service; - }; + Name: CharacteristicValue + Service: Service + } private LockMechanism?: { - Name: CharacteristicValue; - Service: Service; - }; + Name: CharacteristicValue + Service: Service + } private Faucet?: { - Name: CharacteristicValue; - Service: Service; - }; + Name: CharacteristicValue + Service: Service + } private Fan?: { - Name: CharacteristicValue; - Service: Service; - }; + Name: CharacteristicValue + Service: Service + } private StatefulProgrammableSwitch?: { - Name: CharacteristicValue; - Service: Service; - }; + Name: CharacteristicValue + Service: Service + } private Outlet?: { - Name: CharacteristicValue; - Service: Service; - }; + Name: CharacteristicValue + Service: Service + } - On!: boolean; + On!: boolean // OpenAPI - deviceStatus!: botStatus; + deviceStatus!: botStatus - //Webhook - webhookContext!: botWebhookContext; + // Webhook + webhookContext!: botWebhookContext // BLE - serviceData!: botServiceData; + serviceData!: botServiceData // Config - botMode!: string; - allowPush?: boolean; - doublePress!: number; - botDeviceType!: string; - pushRatePress!: number; - multiPressCount!: number; + botMode!: string + allowPush?: boolean + doublePress!: number + botDeviceType!: string + pushRatePress!: number + multiPressCount!: number // Updates - botUpdateInProgress!: boolean; - doBotUpdate!: Subject; + botUpdateInProgress!: boolean + doBotUpdate!: Subject constructor( readonly platform: SwitchBotPlatform, accessory: PlatformAccessory, device: device & devicesConfig, ) { - super(platform, accessory, device); + super(platform, accessory, device) // default placeholders - this.getBotConfigSettings(device); + this.getBotConfigSettings(device) // this is subject we use to track when we need to POST changes to the SwitchBot API - this.doBotUpdate = new Subject(); - this.botUpdateInProgress = false; + this.doBotUpdate = new Subject() + this.botUpdateInProgress = false // Initialize Battery property - accessory.context.Battery = accessory.context.Battery ?? {}; + accessory.context.Battery = accessory.context.Battery ?? {} this.Battery = { Name: `${accessory.displayName} Battery`, Service: accessory.getService(this.hap.Service.Battery) ?? accessory.addService(this.hap.Service.Battery) as Service, BatteryLevel: accessory.context.BatteryLevel ?? 100, StatusLowBattery: accessory.context.StatusLowBattery ?? this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL, - }; - accessory.context.Battery = this.Battery as object; + } + accessory.context.Battery = this.Battery as object // Initialize Battery Characteristics - this.Battery.Service - .setCharacteristic(this.hap.Characteristic.Name, this.Battery.Name) - .setCharacteristic(this.hap.Characteristic.StatusLowBattery, this.Battery.StatusLowBattery) - .setCharacteristic(this.hap.Characteristic.ChargingState, this.hap.Characteristic.ChargingState.NOT_CHARGEABLE); + this.Battery.Service.setCharacteristic(this.hap.Characteristic.Name, this.Battery.Name).setCharacteristic(this.hap.Characteristic.StatusLowBattery, this.Battery.StatusLowBattery).setCharacteristic(this.hap.Characteristic.ChargingState, this.hap.Characteristic.ChargingState.NOT_CHARGEABLE) // deviceType if (this.botDeviceType === 'switch') { // Set category - accessory.category = this.hap.Categories.SWITCH; + accessory.category = this.hap.Categories.SWITCH // Initialize Switch Service - accessory.context.Switch = accessory.context.Switch ?? {}; + accessory.context.Switch = accessory.context.Switch ?? {} this.Switch = { Name: accessory.displayName, Service: accessory.getService(this.hap.Service.Switch) ?? accessory.addService(this.hap.Service.Switch) as Service, - }; - accessory.context.Switch = this.Switch as object; - this.debugLog('Displaying as Switch'); + } + accessory.context.Switch = this.Switch as object + this.debugLog('Displaying as Switch') // Initialize Switch Characteristics - this.Switch.Service - .setCharacteristic(this.hap.Characteristic.Name, this.Switch.Name) - .getCharacteristic(this.hap.Characteristic.On) - .onSet(this.OnSet.bind(this)); + this.Switch.Service.setCharacteristic(this.hap.Characteristic.Name, this.Switch.Name).getCharacteristic(this.hap.Characteristic.On).onSet(this.OnSet.bind(this)) // Remove other services - this.removeFanService(accessory); - this.removeLockService(accessory); - this.removeDoorService(accessory); - this.removeFaucetService(accessory); - this.removeOutletService(accessory); - this.removeWindowService(accessory); - this.removeGarageDoorService(accessory); - this.removeWindowCoveringService(accessory); - this.removeStatefulProgrammableSwitchService(accessory); + this.removeFanService(accessory) + this.removeLockService(accessory) + this.removeDoorService(accessory) + this.removeFaucetService(accessory) + this.removeOutletService(accessory) + this.removeWindowService(accessory) + this.removeGarageDoorService(accessory) + this.removeWindowCoveringService(accessory) + this.removeStatefulProgrammableSwitchService(accessory) } else if (this.botDeviceType === 'garagedoor') { // Set category - accessory.category = this.hap.Categories.GARAGE_DOOR_OPENER; + accessory.category = this.hap.Categories.GARAGE_DOOR_OPENER // Initialize GarageDoor Service - accessory.context.GarageDoor = accessory.context.GarageDoor ?? {}; + accessory.context.GarageDoor = accessory.context.GarageDoor ?? {} this.GarageDoor = { Name: accessory.displayName, Service: accessory.getService(this.hap.Service.GarageDoorOpener) ?? accessory.addService(this.hap.Service.GarageDoorOpener) as Service, - }; - accessory.context.GarageDoor = this.GarageDoor as object; - this.debugLog('Displaying as Garage Door Opener'); + } + accessory.context.GarageDoor = this.GarageDoor as object + this.debugLog('Displaying as Garage Door Opener') // Initialize GarageDoor Characteristics - this.GarageDoor.Service - .setCharacteristic(this.hap.Characteristic.Name, this.GarageDoor.Name) - .setCharacteristic(this.hap.Characteristic.ObstructionDetected, false) - .getCharacteristic(this.hap.Characteristic.TargetDoorState) - .setProps({ - validValues: [0, 100], - minValue: 0, - maxValue: 100, - minStep: 100, - }) - .onSet(this.OnSet.bind(this)); + this.GarageDoor.Service.setCharacteristic(this.hap.Characteristic.Name, this.GarageDoor.Name).setCharacteristic(this.hap.Characteristic.ObstructionDetected, false).getCharacteristic(this.hap.Characteristic.TargetDoorState).setProps({ + validValues: [0, 100], + minValue: 0, + maxValue: 100, + minStep: 100, + }).onSet(this.OnSet.bind(this)) // Remove other services - this.removeFanService(accessory); - this.removeLockService(accessory); - this.removeDoorService(accessory); - this.removeFaucetService(accessory); - this.removeOutletService(accessory); - this.removeSwitchService(accessory); - this.removeWindowService(accessory); - this.removeWindowCoveringService(accessory); - this.removeStatefulProgrammableSwitchService(accessory); + this.removeFanService(accessory) + this.removeLockService(accessory) + this.removeDoorService(accessory) + this.removeFaucetService(accessory) + this.removeOutletService(accessory) + this.removeSwitchService(accessory) + this.removeWindowService(accessory) + this.removeWindowCoveringService(accessory) + this.removeStatefulProgrammableSwitchService(accessory) } else if (this.botDeviceType === 'door') { // Set category - accessory.category = this.hap.Categories.DOOR; + accessory.category = this.hap.Categories.DOOR // Initialize Door Service - accessory.context.Door = accessory.context.Door ?? {}; + accessory.context.Door = accessory.context.Door ?? {} this.Door = { Name: accessory.displayName, Service: accessory.getService(this.hap.Service.Door) ?? accessory.addService(this.hap.Service.Door) as Service, - }; - accessory.context.Door = this.Door as object; - this.debugLog('Displaying as Door'); + } + accessory.context.Door = this.Door as object + this.debugLog('Displaying as Door') // Initialize Door Characteristics - this.Door.Service - .setCharacteristic(this.hap.Characteristic.Name, this.Door.Name) - .setCharacteristic(this.hap.Characteristic.PositionState, this.hap.Characteristic.PositionState.STOPPED) - .getCharacteristic(this.hap.Characteristic.TargetPosition).setProps({ - validValues: [0, 100], - minValue: 0, - maxValue: 100, - minStep: 100, - }) - .onSet(this.OnSet.bind(this)); + this.Door.Service.setCharacteristic(this.hap.Characteristic.Name, this.Door.Name).setCharacteristic(this.hap.Characteristic.PositionState, this.hap.Characteristic.PositionState.STOPPED).getCharacteristic(this.hap.Characteristic.TargetPosition).setProps({ + validValues: [0, 100], + minValue: 0, + maxValue: 100, + minStep: 100, + }).onSet(this.OnSet.bind(this)) // Remove other services - this.removeFanService(accessory); - this.removeLockService(accessory); - this.removeOutletService(accessory); - this.removeFaucetService(accessory); - this.removeSwitchService(accessory); - this.removeWindowService(accessory); - this.removeGarageDoorService(accessory); - this.removeWindowCoveringService(accessory); - this.removeStatefulProgrammableSwitchService(accessory); + this.removeFanService(accessory) + this.removeLockService(accessory) + this.removeOutletService(accessory) + this.removeFaucetService(accessory) + this.removeSwitchService(accessory) + this.removeWindowService(accessory) + this.removeGarageDoorService(accessory) + this.removeWindowCoveringService(accessory) + this.removeStatefulProgrammableSwitchService(accessory) } else if (this.botDeviceType === 'window') { // Set category - accessory.category = this.hap.Categories.WINDOW; + accessory.category = this.hap.Categories.WINDOW // Initialize Window Service - accessory.context.Window = accessory.context.Window ?? {}; + accessory.context.Window = accessory.context.Window ?? {} this.Window = { Name: accessory.displayName, Service: accessory.getService(this.hap.Service.Window) ?? accessory.addService(this.hap.Service.Window) as Service, - }; - accessory.context.Window = this.Window as object; - this.debugLog('Displaying as Window'); + } + accessory.context.Window = this.Window as object + this.debugLog('Displaying as Window') // Initialize Window Characteristics - this.Window.Service - .setCharacteristic(this.hap.Characteristic.Name, this.Window.Name) - .setCharacteristic(this.hap.Characteristic.PositionState, this.hap.Characteristic.PositionState.STOPPED) - .getCharacteristic(this.hap.Characteristic.TargetPosition).setProps({ - validValues: [0, 100], - minValue: 0, - maxValue: 100, - minStep: 100, - }) - .onSet(this.OnSet.bind(this)); + this.Window.Service.setCharacteristic(this.hap.Characteristic.Name, this.Window.Name).setCharacteristic(this.hap.Characteristic.PositionState, this.hap.Characteristic.PositionState.STOPPED).getCharacteristic(this.hap.Characteristic.TargetPosition).setProps({ + validValues: [0, 100], + minValue: 0, + maxValue: 100, + minStep: 100, + }).onSet(this.OnSet.bind(this)) // Remove other services - this.removeFanService(accessory); - this.removeLockService(accessory); - this.removeDoorService(accessory); - this.removeOutletService(accessory); - this.removeFaucetService(accessory); - this.removeSwitchService(accessory); - this.removeGarageDoorService(accessory); - this.removeWindowCoveringService(accessory); - this.removeStatefulProgrammableSwitchService(accessory); + this.removeFanService(accessory) + this.removeLockService(accessory) + this.removeDoorService(accessory) + this.removeOutletService(accessory) + this.removeFaucetService(accessory) + this.removeSwitchService(accessory) + this.removeGarageDoorService(accessory) + this.removeWindowCoveringService(accessory) + this.removeStatefulProgrammableSwitchService(accessory) } else if (this.botDeviceType === 'windowcovering') { // Set category - accessory.category = this.hap.Categories.WINDOW_COVERING; + accessory.category = this.hap.Categories.WINDOW_COVERING // Initialize WindowCovering Service - accessory.context.WindowCovering = accessory.context.WindowCovering ?? {}; + accessory.context.WindowCovering = accessory.context.WindowCovering ?? {} this.WindowCovering = { Name: accessory.displayName, Service: accessory.getService(this.hap.Service.WindowCovering) ?? accessory.addService(this.hap.Service.WindowCovering) as Service, - }; - accessory.context.WindowCovering = this.WindowCovering as object; - this.debugLog('Displaying as Window Covering'); + } + accessory.context.WindowCovering = this.WindowCovering as object + this.debugLog('Displaying as Window Covering') // Initialize WindowCovering Characteristics - this.WindowCovering.Service - .setCharacteristic(this.hap.Characteristic.Name, this.WindowCovering.Name) - .setCharacteristic(this.hap.Characteristic.PositionState, this.hap.Characteristic.PositionState.STOPPED) - .getCharacteristic(this.hap.Characteristic.TargetPosition) - .setProps({ - validValues: [0, 100], - minValue: 0, - maxValue: 100, - minStep: 100, - }) - .onSet(this.OnSet.bind(this)); + this.WindowCovering.Service.setCharacteristic(this.hap.Characteristic.Name, this.WindowCovering.Name).setCharacteristic(this.hap.Characteristic.PositionState, this.hap.Characteristic.PositionState.STOPPED).getCharacteristic(this.hap.Characteristic.TargetPosition).setProps({ + validValues: [0, 100], + minValue: 0, + maxValue: 100, + minStep: 100, + }).onSet(this.OnSet.bind(this)) // Remove other services - this.removeFanService(accessory); - this.removeLockService(accessory); - this.removeDoorService(accessory); - this.removeOutletService(accessory); - this.removeFaucetService(accessory); - this.removeSwitchService(accessory); - this.removeWindowService(accessory); - this.removeGarageDoorService(accessory); - this.removeStatefulProgrammableSwitchService(accessory); + this.removeFanService(accessory) + this.removeLockService(accessory) + this.removeDoorService(accessory) + this.removeOutletService(accessory) + this.removeFaucetService(accessory) + this.removeSwitchService(accessory) + this.removeWindowService(accessory) + this.removeGarageDoorService(accessory) + this.removeStatefulProgrammableSwitchService(accessory) } else if (this.botDeviceType === 'lock') { // Set category - accessory.category = this.hap.Categories.DOOR_LOCK; + accessory.category = this.hap.Categories.DOOR_LOCK // Initialize Lock Service - accessory.context.LockMechanism = accessory.context.LockMechanism ?? {}; + accessory.context.LockMechanism = accessory.context.LockMechanism ?? {} this.LockMechanism = { Name: accessory.displayName, Service: accessory.getService(this.hap.Service.LockMechanism) ?? accessory.addService(this.hap.Service.LockMechanism) as Service, - }; - accessory.context.LockMechanism = this.LockMechanism as object; - this.debugLog('Displaying as Lock'); + } + accessory.context.LockMechanism = this.LockMechanism as object + this.debugLog('Displaying as Lock') // Initialize Lock Characteristics - this.LockMechanism.Service - .setCharacteristic(this.hap.Characteristic.Name, this.LockMechanism.Name) - .setCharacteristic(this.hap.Characteristic.PositionState, this.hap.Characteristic.PositionState.STOPPED) - .getCharacteristic(this.hap.Characteristic.LockTargetState) - .onSet(this.OnSet.bind(this)); + this.LockMechanism.Service.setCharacteristic(this.hap.Characteristic.Name, this.LockMechanism.Name).setCharacteristic(this.hap.Characteristic.PositionState, this.hap.Characteristic.PositionState.STOPPED).getCharacteristic(this.hap.Characteristic.LockTargetState).onSet(this.OnSet.bind(this)) // Remove other services - this.removeFanService(accessory); - this.removeDoorService(accessory); - this.removeOutletService(accessory); - this.removeSwitchService(accessory); - this.removeFaucetService(accessory); - this.removeWindowService(accessory); - this.removeGarageDoorService(accessory); - this.removeWindowCoveringService(accessory); - this.removeStatefulProgrammableSwitchService(accessory); + this.removeFanService(accessory) + this.removeDoorService(accessory) + this.removeOutletService(accessory) + this.removeSwitchService(accessory) + this.removeFaucetService(accessory) + this.removeWindowService(accessory) + this.removeGarageDoorService(accessory) + this.removeWindowCoveringService(accessory) + this.removeStatefulProgrammableSwitchService(accessory) } else if (this.botDeviceType === 'faucet') { // Set category - accessory.category = this.hap.Categories.FAUCET; + accessory.category = this.hap.Categories.FAUCET // Initialize Faucet Service - accessory.context.Faucet = accessory.context.Faucet ?? {}; + accessory.context.Faucet = accessory.context.Faucet ?? {} this.Faucet = { Name: accessory.displayName, Service: accessory.getService(this.hap.Service.Faucet) ?? accessory.addService(this.hap.Service.Faucet) as Service, - }; - accessory.context.Faucet = this.Faucet as object; - this.debugLog('Displaying as Faucet'); + } + accessory.context.Faucet = this.Faucet as object + this.debugLog('Displaying as Faucet') // Initialize Faucet Characteristics - this.Faucet.Service - .setCharacteristic(this.hap.Characteristic.Name, this.Faucet.Name) - .getCharacteristic(this.hap.Characteristic.Active) - .onSet(this.OnSet.bind(this)); + this.Faucet.Service.setCharacteristic(this.hap.Characteristic.Name, this.Faucet.Name).getCharacteristic(this.hap.Characteristic.Active).onSet(this.OnSet.bind(this)) // Remove other services - this.removeFanService(accessory); - this.removeLockService(accessory); - this.removeDoorService(accessory); - this.removeOutletService(accessory); - this.removeSwitchService(accessory); - this.removeWindowService(accessory); - this.removeGarageDoorService(accessory); - this.removeWindowCoveringService(accessory); - this.removeStatefulProgrammableSwitchService(accessory); + this.removeFanService(accessory) + this.removeLockService(accessory) + this.removeDoorService(accessory) + this.removeOutletService(accessory) + this.removeSwitchService(accessory) + this.removeWindowService(accessory) + this.removeGarageDoorService(accessory) + this.removeWindowCoveringService(accessory) + this.removeStatefulProgrammableSwitchService(accessory) } else if (this.botDeviceType === 'fan') { // Set category - accessory.category = this.hap.Categories.FAN; + accessory.category = this.hap.Categories.FAN // Initialize Fan Service - accessory.context.Fan = accessory.context.Fan ?? {}; + accessory.context.Fan = accessory.context.Fan ?? {} this.Fan = { Name: accessory.displayName, Service: accessory.getService(this.hap.Service.Fanv2) ?? accessory.addService(this.hap.Service.Fanv2) as Service, - }; - accessory.context.Fan = this.Fan as object; - this.debugLog('Displaying as Fan'); + } + accessory.context.Fan = this.Fan as object + this.debugLog('Displaying as Fan') // Initialize Fan Characteristics - this.Fan.Service - .setCharacteristic(this.hap.Characteristic.Name, this.Fan.Name) - .getCharacteristic(this.hap.Characteristic.Active) - .onSet(this.OnSet.bind(this)); + this.Fan.Service.setCharacteristic(this.hap.Characteristic.Name, this.Fan.Name).getCharacteristic(this.hap.Characteristic.Active).onSet(this.OnSet.bind(this)) // Remove other services - this.removeLockService(accessory); - this.removeDoorService(accessory); - this.removeFaucetService(accessory); - this.removeOutletService(accessory); - this.removeSwitchService(accessory); - this.removeWindowService(accessory); - this.removeGarageDoorService(accessory); - this.removeWindowCoveringService(accessory); - this.removeStatefulProgrammableSwitchService(accessory); + this.removeLockService(accessory) + this.removeDoorService(accessory) + this.removeFaucetService(accessory) + this.removeOutletService(accessory) + this.removeSwitchService(accessory) + this.removeWindowService(accessory) + this.removeGarageDoorService(accessory) + this.removeWindowCoveringService(accessory) + this.removeStatefulProgrammableSwitchService(accessory) } else if (this.botDeviceType === 'stateful') { // Set category - accessory.category = this.hap.Categories.PROGRAMMABLE_SWITCH; + accessory.category = this.hap.Categories.PROGRAMMABLE_SWITCH // Initialize StatefulProgrammableSwitch Service - accessory.context.StatefulProgrammableSwitch = accessory.context.StatefulProgrammableSwitch ?? {}; + accessory.context.StatefulProgrammableSwitch = accessory.context.StatefulProgrammableSwitch ?? {} this.StatefulProgrammableSwitch = { Name: accessory.displayName, - Service: accessory.getService(this.hap.Service.StatefulProgrammableSwitch) - ?? accessory.addService(this.hap.Service.StatefulProgrammableSwitch) as Service, - }; - accessory.context.StatefulProgrammableSwitch = this.StatefulProgrammableSwitch as object; - this.debugLog('Displaying as Stateful Programmable Switch'); + Service: accessory.getService(this.hap.Service.StatefulProgrammableSwitch) ?? accessory.addService(this.hap.Service.StatefulProgrammableSwitch) as Service, + } + accessory.context.StatefulProgrammableSwitch = this.StatefulProgrammableSwitch as object + this.debugLog('Displaying as Stateful Programmable Switch') // Initialize StatefulProgrammableSwitch Characteristics - this.StatefulProgrammableSwitch.Service - .setCharacteristic(this.hap.Characteristic.Name, this.StatefulProgrammableSwitch.Name) - .getCharacteristic(this.hap.Characteristic.ProgrammableSwitchOutputState) - .onSet(this.OnSet.bind(this)); + this.StatefulProgrammableSwitch.Service.setCharacteristic(this.hap.Characteristic.Name, this.StatefulProgrammableSwitch.Name).getCharacteristic(this.hap.Characteristic.ProgrammableSwitchOutputState).onSet(this.OnSet.bind(this)) // Remove other services - this.removeFanService(accessory); - this.removeLockService(accessory); - this.removeDoorService(accessory); - this.removeFaucetService(accessory); - this.removeOutletService(accessory); - this.removeSwitchService(accessory); - this.removeWindowService(accessory); - this.removeGarageDoorService(accessory); - this.removeWindowCoveringService(accessory); + this.removeFanService(accessory) + this.removeLockService(accessory) + this.removeDoorService(accessory) + this.removeFaucetService(accessory) + this.removeOutletService(accessory) + this.removeSwitchService(accessory) + this.removeWindowService(accessory) + this.removeGarageDoorService(accessory) + this.removeWindowCoveringService(accessory) } else if (this.botDeviceType === 'outlet') { // Set category - accessory.category = this.hap.Categories.OUTLET; + accessory.category = this.hap.Categories.OUTLET // Initialize Switch property - accessory.context.Outlet = accessory.context.Outlet ?? {}; + accessory.context.Outlet = accessory.context.Outlet ?? {} this.Outlet = { Name: accessory.displayName, Service: accessory.getService(this.hap.Service.Outlet) ?? accessory.addService(this.hap.Service.Outlet) as Service, - }; - accessory.context.Outlet = this.Outlet as object; - this.debugLog('Displaying as Outlet'); + } + accessory.context.Outlet = this.Outlet as object + this.debugLog('Displaying as Outlet') // Initialize Outlet Characteristics - this.Outlet.Service - .setCharacteristic(this.hap.Characteristic.Name, this.Outlet.Name) - .getCharacteristic(this.hap.Characteristic.On) - .onSet(this.OnSet.bind(this)); + this.Outlet.Service.setCharacteristic(this.hap.Characteristic.Name, this.Outlet.Name).getCharacteristic(this.hap.Characteristic.On).onSet(this.OnSet.bind(this)) // Remove other services - this.removeFanService(accessory); - this.removeLockService(accessory); - this.removeDoorService(accessory); - this.removeFaucetService(accessory); - this.removeSwitchService(accessory); - this.removeWindowService(accessory); - this.removeGarageDoorService(accessory); - this.removeWindowCoveringService(accessory); - this.removeStatefulProgrammableSwitchService(accessory); + this.removeFanService(accessory) + this.removeLockService(accessory) + this.removeDoorService(accessory) + this.removeFaucetService(accessory) + this.removeSwitchService(accessory) + this.removeWindowService(accessory) + this.removeGarageDoorService(accessory) + this.removeWindowCoveringService(accessory) + this.removeStatefulProgrammableSwitchService(accessory) } else { - this.errorLog('Device Type not set'); + this.errorLog('Device Type not set') } // Retrieve initial values and updateHomekit - this.debugLog('Retrieve initial values and update Homekit'); - this.refreshStatus(); + try { + this.debugLog('Retrieve initial values and update Homekit') + this.refreshStatus() + } catch (e: any) { + this.errorLog(`failed to retrieve initial values and update Homekit, Error: ${e}`) + } - //regisiter webhook event handler - this.debugLog('Registering Webhook Event Handler'); - this.registerWebhook(); + // regisiter webhook event handler if enabled + try { + this.debugLog('Registering Webhook Event Handler') + this.registerWebhook() + } catch (e: any) { + this.errorLog(`failed to registerWebhook, Error: ${e}`) + } + + // regisiter platform BLE event handler if enabled + try { + this.debugLog('Registering Platform BLE Event Handler') + this.registerPlatformBLE() + } catch (e: any) { + this.errorLog(`failed to registerPlatformBLE, Error: ${e}`) + } // Start an update interval interval(this.deviceRefreshRate * 1000) .pipe(skipWhile(() => this.botUpdateInProgress)) .subscribe(async () => { - await this.refreshStatus(); - }); + await this.refreshStatus() + }) // Watch for Bot change events // We put in a debounce of 1000ms so we don't make duplicate calls this.doBotUpdate .pipe( tap(() => { - this.botUpdateInProgress = true; + this.botUpdateInProgress = true }), debounceTime(this.devicePushRate * 1000), ) @@ -453,103 +434,102 @@ export class Bot extends deviceBase { interval(this.pushRatePress * 1000) .pipe(take(this.doublePress!)) .subscribe(async () => { - await this.pushChanges(); - }); + await this.pushChanges() + }) } else { - await this.pushChanges(); + await this.pushChanges() } } catch (e: any) { - await this.apiError(e); - await this.errorLog(`failed pushChanges with ${device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`); + await this.apiError(e) + await this.errorLog(`failed pushChanges with ${device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`) } - this.botUpdateInProgress = false; - }); + this.botUpdateInProgress = false + }) } /** * Parse the device status from the SwitchBotBLE API */ async BLEparseStatus(): Promise { - await this.debugLog('BLEparseStatus'); - await this.debugLog(`(power, battery, deviceMode) = BLE:(${this.serviceData.state}, ${this.serviceData.battery}, ${this.serviceData.mode}),` - + ` current:(${this.accessory.context.On}, ${this.Battery.BatteryLevel}, ${this.botMode})`); + await this.debugLog('BLEparseStatus') + await this.debugLog(`(power, battery, deviceMode) = BLE:(${this.serviceData.state}, ${this.serviceData.battery}, ${this.serviceData.mode}), current:(${this.accessory.context.On}, ${this.Battery.BatteryLevel}, ${this.botMode})`) // BLEmode (true if Switch Mode) | (false if Press Mode) - this.On = this.serviceData.mode ? this.serviceData.state : false; - const mode = this.serviceData.mode ? 'Switch' : 'Press'; - await this.debugLog(`${mode} Mode, On: ${this.accessory.context.On}`); - this.accessory.context.On = this.On; + this.On = this.serviceData.mode ? this.serviceData.state : false + const mode = this.serviceData.mode ? 'Switch' : 'Press' + await this.debugLog(`${mode} Mode, On: ${this.accessory.context.On}`) + this.accessory.context.On = this.On // BatteryLevel - this.Battery.BatteryLevel = this.serviceData.battery; - await this.debugLog(`BatteryLevel: ${this.Battery.BatteryLevel}`); + this.Battery.BatteryLevel = this.serviceData.battery + await this.debugLog(`BatteryLevel: ${this.Battery.BatteryLevel}`) // StatusLowBattery this.Battery.StatusLowBattery = this.Battery.BatteryLevel < 10 - ? this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW : this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL; - await this.debugLog(`StatusLowBattery: ${this.Battery.StatusLowBattery}`); + ? this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW + : this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL + await this.debugLog(`StatusLowBattery: ${this.Battery.StatusLowBattery}`) } - /** * Parse the device status from the SwitchBot OpenAPI */ async openAPIparseStatus(): Promise { - await this.debugLog('openAPIparseStatus'); - await this.debugLog(`(power, battery, deviceMode) = API:(${this.deviceStatus.power}, ${this.deviceStatus.battery}, ${this.botMode}),` - + ` current:(${this.accessory.context.On}, ${this.Battery.BatteryLevel}, ${this.botMode})`); + await this.debugLog('openAPIparseStatus') + await this.debugLog(`(power, battery, deviceMode) = API:(${this.deviceStatus.power}, ${this.deviceStatus.battery}, ${this.botMode}), current:(${this.accessory.context.On}, ${this.Battery.BatteryLevel}, ${this.botMode})`) // On - this.On = this.botMode === 'press' ? false : this.deviceStatus.power === 'on' ? true : false; - this.accessory.context.On = this.On; - await this.debugLog(`On: ${this.accessory.context.On}`); + this.On = this.botMode === 'press' ? false : this.deviceStatus.power === 'on' + this.accessory.context.On = this.On + await this.debugLog(`On: ${this.accessory.context.On}`) // Battery Level - this.Battery.BatteryLevel = this.deviceStatus.battery; - await this.debugLog(`BatteryLevel: ${this.Battery.BatteryLevel}`); + this.Battery.BatteryLevel = this.deviceStatus.battery + await this.debugLog(`BatteryLevel: ${this.Battery.BatteryLevel}`) // StatusLowBattery this.Battery.StatusLowBattery = this.Battery.BatteryLevel < 10 - ? this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW : this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL; - await this.debugLog(`StatusLowBattery: ${this.Battery.StatusLowBattery}`); + ? this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW + : this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL + await this.debugLog(`StatusLowBattery: ${this.Battery.StatusLowBattery}`) // Firmware Version if (this.deviceStatus.version) { - const version = this.deviceStatus.version.toString(); - this.debugLog(`Firmware Version: ${version.replace(/^V|-.*$/g, '')}`); - const deviceVersion = version.replace(/^V|-.*$/g, '') ?? '0.0.0'; + const version = this.deviceStatus.version.toString() + this.debugLog(`Firmware Version: ${version.replace(/^V|-.*$/g, '')}`) + const deviceVersion = version.replace(/^V|-.*$/g, '') ?? '0.0.0' this.accessory .getService(this.hap.Service.AccessoryInformation)! .setCharacteristic(this.hap.Characteristic.HardwareRevision, deviceVersion) .setCharacteristic(this.hap.Characteristic.FirmwareRevision, deviceVersion) .getCharacteristic(this.hap.Characteristic.FirmwareRevision) - .updateValue(deviceVersion); - this.accessory.context.version = deviceVersion; - await this.debugLog(`version: ${this.accessory.context.version}`); + .updateValue(deviceVersion) + this.accessory.context.version = deviceVersion + await this.debugLog(`version: ${this.accessory.context.version}`) } } async parseStatusWebhook(): Promise { - await this.debugLog('parseStatusWebhook'); - await this.debugLog(`(power, battery, deviceMode) = Webhook:(${this.webhookContext.power}, ${this.webhookContext.battery},` - + ` ${this.webhookContext.deviceMode}), current:(${this.On}, ${this.Battery.BatteryLevel}, ${this.botMode})`); + await this.debugLog('parseStatusWebhook') + await this.debugLog(`(power, battery, deviceMode) = Webhook:(${this.webhookContext.power}, ${this.webhookContext.battery}, ${this.webhookContext.deviceMode}), current:(${this.On}, ${this.Battery.BatteryLevel}, ${this.botMode})`) // On - this.On = this.webhookContext.power === 'on' ? true : false; - await this.debugLog(`On: ${this.On}`); + this.On = this.webhookContext.power === 'on' + await this.debugLog(`On: ${this.On}`) // BatteryLevel - this.Battery.BatteryLevel = this.webhookContext.battery; - await this.debugLog(`BatteryLevel: ${this.Battery.BatteryLevel}`); + this.Battery.BatteryLevel = this.webhookContext.battery + await this.debugLog(`BatteryLevel: ${this.Battery.BatteryLevel}`) // StatusLowBattery this.Battery.StatusLowBattery = this.Battery.BatteryLevel < 10 - ? this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW : this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL; - await this.debugLog(`StatusLowBattery: ${this.Battery.StatusLowBattery}`); + ? this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW + : this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL + await this.debugLog(`StatusLowBattery: ${this.Battery.StatusLowBattery}`) // Mode - this.botMode = this.webhookContext.deviceMode; - await this.debugLog(`Mode: ${this.botMode}`); + this.botMode = this.webhookContext.deviceMode + await this.debugLog(`Mode: ${this.botMode}`) } /** @@ -557,233 +537,237 @@ export class Bot extends deviceBase { */ async refreshStatus(): Promise { if (!this.device.enableCloudService && this.OpenAPI) { - await this.errorLog(`refreshStatus enableCloudService: ${this.device.enableCloudService}`); - } else if (this.BLE || this.config.options?.BLE) { - await this.BLERefreshStatus(); + await this.errorLog(`refreshStatus enableCloudService: ${this.device.enableCloudService}`) + } else if (this.BLE) { + await this.BLERefreshStatus() } else if (this.OpenAPI && this.platform.config.credentials?.token) { - await this.openAPIRefreshStatus(); + await this.openAPIRefreshStatus() } else { - await this.offlineOff(); - await this.debugWarnLog(`Connection Type: ${this.device.connectionType}, refreshStatus will not happen.`); + await this.offlineOff() + await this.debugWarnLog(`Connection Type: ${this.device.connectionType}, refreshStatus will not happen.`) } } async BLERefreshStatus(): Promise { - await this.debugLog('BLERefreshStatus'); + await this.debugLog('BLERefreshStatus') + const switchbot = await this.switchbotBLE() + if (switchbot === undefined) { + await this.BLERefreshConnection(switchbot) + } else { + // Start to monitor advertisement packets + (async () => { + // Start to monitor advertisement packets + const serviceData = await this.monitorAdvertisementPackets(switchbot) as botServiceData + // Update HomeKit + if (serviceData.model === SwitchBotBLEModel.Bot && serviceData.modelName === SwitchBotBLEModelName.Bot) { + this.serviceData = serviceData + await this.BLEparseStatus() + await this.updateHomeKitCharacteristics() + } else { + await this.errorLog(`failed to get serviceData, serviceData: ${serviceData}`) + await this.BLERefreshConnection(switchbot) + } + })() + } + } + + async registerPlatformBLE(): Promise { + await this.debugLog('registerPlatformBLE') if (this.config.options?.BLE) { - await this.debugLog('is listening to Platform BLE.'); - this.device.bleMac = this.device.deviceId!.match(/.{1,2}/g)!.join(':').toLowerCase(); - await this.debugLog(`bleMac: ${this.device.bleMac}`); + await this.debugLog('is listening to Platform BLE.') + this.device.bleMac = this.device.deviceId!.match(/.{1,2}/g)!.join(':').toLowerCase() + await this.debugLog(`bleMac: ${this.device.bleMac}`) this.platform.bleEventHandler[this.device.bleMac] = async (context: botServiceData) => { try { - await this.debugLog(`received BLE: ${JSON.stringify(context)}`); - this.serviceData = context; - await this.BLEparseStatus(); - await this.updateHomeKitCharacteristics(); + await this.debugLog(`received BLE: ${JSON.stringify(context)}`) + this.serviceData = context + await this.BLEparseStatus() + await this.updateHomeKitCharacteristics() } catch (e: any) { - await this.errorLog(`failed to handle BLE. Received: ${JSON.stringify(context)} Error: ${e}`); + await this.errorLog(`failed to handle BLE. Received: ${JSON.stringify(context)} Error: ${e}`) } - }; - } else { - await this.debugLog('is using Device BLE Scanning.'); - const switchbot = await this.switchbotBLE(); - if (switchbot === undefined) { - await this.BLERefreshConnection(switchbot); - } else { - // Start to monitor advertisement packets - (async () => { - // Start to monitor advertisement packets - const serviceData = await this.monitorAdvertisementPackets(switchbot) as botServiceData; - // Update HomeKit - if (serviceData.model === SwitchBotBLEModel.Bot && serviceData.modelName === SwitchBotBLEModelName.Bot) { - this.serviceData = serviceData; - await this.BLEparseStatus(); - await this.updateHomeKitCharacteristics(); - } else { - await this.errorLog(`failed to get serviceData, serviceData: ${serviceData}`); - await this.BLERefreshConnection(switchbot); - } - })(); } + } else { + await this.debugLog('is not listening to Platform BLE') } } async openAPIRefreshStatus(): Promise { - await this.debugLog('openAPIRefreshStatus'); + await this.debugLog('openAPIRefreshStatus') try { - const { body, statusCode } = await this.deviceRefreshStatus(); - const deviceStatus: any = await body.json(); - await this.debugLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`);; + const { body, statusCode } = await this.deviceRefreshStatus() + const deviceStatus: any = await body.json() + await this.debugLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`) if (await this.successfulStatusCodes(statusCode, deviceStatus)) { - await this.debugSuccessLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`); - this.deviceStatus = deviceStatus.body; - await this.openAPIparseStatus(); - await this.updateHomeKitCharacteristics(); + await this.debugSuccessLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`) + this.deviceStatus = deviceStatus.body + await this.openAPIparseStatus() + await this.updateHomeKitCharacteristics() } else { - await this.debugWarnLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`); - await this.debugWarnLog(statusCode, deviceStatus); + await this.debugWarnLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`) + await this.debugWarnLog(statusCode, deviceStatus) } } catch (e: any) { - await this.apiError(e); - await this.errorLog(`failed openAPIRefreshStatus with ${this.device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`); + await this.apiError(e) + await this.errorLog(`failed openAPIRefreshStatus with ${this.device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`) } } async registerWebhook() { if (this.device.webhook) { - await this.debugLog('is listening webhook.'); + await this.debugLog('is listening webhook.') this.platform.webhookEventHandler[this.device.deviceId] = async (context: botWebhookContext) => { try { - await this.debugLog(`received Webhook: ${JSON.stringify(context)}`); - this.webhookContext = context; - await this.parseStatusWebhook(); - await this.updateHomeKitCharacteristics(); + await this.debugLog(`received Webhook: ${JSON.stringify(context)}`) + this.webhookContext = context + await this.parseStatusWebhook() + await this.updateHomeKitCharacteristics() } catch (e: any) { - await this.errorLog(`failed to handle webhook. Received: ${JSON.stringify(context)} Error: ${e}`); + await this.errorLog(`failed to handle webhook. Received: ${JSON.stringify(context)} Error: ${e}`) } - }; + } } else { - await this.debugLog('is not listening webhook.'); + await this.debugLog('is not listening webhook.') } } /** * Pushes the requested changes to the SwitchBot API - * deviceType commandType Command command parameter Description - * Bot - "command" "turnOff" "default" = set to OFF state - * Bot - "command" "turnOn" "default" = set to ON state - * Bot - "command" "press" "default" = trigger press + * deviceType commandType Command command parameter Description + * Bot "command" "turnOff" "default" = set to OFF state + * Bot "command" "turnOn" "default" = set to ON state + * Bot "command" "press" "default" = trigger press */ async pushChanges(): Promise { if (!this.device.enableCloudService && this.OpenAPI) { - await this.errorLog(`pushChanges enableCloudService: ${this.device.enableCloudService}`); + await this.errorLog(`pushChanges enableCloudService: ${this.device.enableCloudService}`) } else if (this.BLE) { - await this.BLEpushChanges(); + await this.BLEpushChanges() } else if (this.OpenAPI && this.platform.config.credentials?.token) { - await this.openAPIpushChanges(); + await this.openAPIpushChanges() } else { - await this.offlineOff(); - await this.debugWarnLog(`Connection Type: ${this.device.connectionType}, pushChanges will not happen.`); + await this.offlineOff() + await this.debugWarnLog(`Connection Type: ${this.device.connectionType}, pushChanges will not happen.`) } // Refresh the status from the API interval(15000) .pipe(skipWhile(() => this.botUpdateInProgress)) .pipe(take(1)) .subscribe(async () => { - await this.refreshStatus(); - }); + await this.refreshStatus() + }) } async BLEpushChanges(): Promise { - await this.debugLog('BLEpushChanges'); + await this.debugLog('BLEpushChanges') if (this.On !== this.accessory.context.On || this.allowPush) { - await this.debugLog(`BLEpushChanges On: ${this.On} OnCached: ${this.accessory.context.On}`); - const switchbot = await this.platform.connectBLE(this.accessory, this.device); - await this.convertBLEAddress(); - //if (switchbot !== false) { - await this.debugLog(`Bot Mode: ${this.botMode}`); + await this.debugLog(`BLEpushChanges On: ${this.On} OnCached: ${this.accessory.context.On}`) + const switchbot = await this.platform.connectBLE(this.accessory, this.device) + await this.convertBLEAddress() + // if (switchbot !== false) { + await this.debugLog(`Bot Mode: ${this.botMode}`) if (this.botMode === 'press') { switchbot .discover({ model: 'H', quick: true, id: this.device.bleMac }) .then(async (device_list: { press: (arg0: { id: string | undefined }) => any }[]) => { - await this.infoLog(`On: ${this.On}`); - return await device_list[0].press({ id: this.device.bleMac }); + await this.infoLog(`On: ${this.On}`) + return await device_list[0].press({ id: this.device.bleMac }) }) .then(async () => { - await this.successLog(`On: ${this.On} sent over SwitchBot BLE, sent successfully`); - await this.updateHomeKitCharacteristics(); + await this.successLog(`On: ${this.On} sent over SwitchBot BLE, sent successfully`) + await this.updateHomeKitCharacteristics() setTimeout(async () => { - this.On = false; - await this.updateHomeKitCharacteristics(); - this.debugLog(`On: ${this.On}, Switch Timeout`); - }, 500); + this.On = false + await this.updateHomeKitCharacteristics() + this.debugLog(`On: ${this.On}, Switch Timeout`) + }, 500) }) .catch(async (e: any) => { - await this.apiError(e); - await this.errorLog(`failed BLEpushChanges with ${this.device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`); - await this.BLEPushConnection(); - }); + await this.apiError(e) + await this.errorLog(`failed BLEpushChanges with ${this.device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`) + await this.BLEPushConnection() + }) } else if (this.botMode === 'switch') { switchbot .discover({ model: this.device.bleModel, quick: true, id: this.device.bleMac }) .then(async (device_list: any) => { - this.infoLog(`On: ${this.On}`); + this.infoLog(`On: ${this.On}`) return await this.retryBLE({ max: await this.maxRetryBLE(), fn: async () => { if (this.On) { - return await device_list[0].turnOn({ id: this.device.bleMac }); + return await device_list[0].turnOn({ id: this.device.bleMac }) } else { - return await device_list[0].turnOff({ id: this.device.bleMac }); + return await device_list[0].turnOff({ id: this.device.bleMac }) } }, - }); + }) }) .then(async () => { - await this.successLog(`On: ${this.On} sent over SwitchBot BLE, sent successfully`); - await this.updateHomeKitCharacteristics(); + await this.successLog(`On: ${this.On} sent over SwitchBot BLE, sent successfully`) + await this.updateHomeKitCharacteristics() }) .catch(async (e: any) => { - await this.apiError(e); - await this.errorLog(`failed BLEpushChanges with ${this.device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`); - await this.BLEPushConnection(); - }); + await this.apiError(e) + await this.errorLog(`failed BLEpushChanges with ${this.device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`) + await this.BLEPushConnection() + }) } else { - await this.errorLog(`Device Parameters not set for this Bot, please check the device configuration. Bot Mode: ${this.botMode}`); + await this.errorLog(`Device Parameters not set for this Bot, please check the device configuration. Bot Mode: ${this.botMode}`) } } else { - await this.debugLog(`No Changes (BLEpushChanges), On: ${this.On} OnCached: ${this.accessory.context.On}`); + await this.debugLog(`No Changes (BLEpushChanges), On: ${this.On} OnCached: ${this.accessory.context.On}`) } } async openAPIpushChanges(): Promise { - await this.debugLog('openAPIpushChanges'); + await this.debugLog('openAPIpushChanges') if (this.multiPressCount > 0) { - await this.debugLog(`${this.multiPressCount} request(s) queued.`); + await this.debugLog(`${this.multiPressCount} request(s) queued.`) } if (this.On !== this.accessory.context.On || this.allowPush || this.multiPressCount > 0) { - let command = ''; + let command = '' if (this.botMode === 'switch') { - command = this.On ? 'turnOn' : 'turnOff'; - await this.debugLog(`Switch Mode, Command: ${command}`); + command = this.On ? 'turnOn' : 'turnOff' + await this.debugLog(`Switch Mode, Command: ${command}`) } else if (this.botMode === 'press' || this.botMode === 'multipress') { - command = 'press'; - await this.debugLog('Press Mode'); - this.On = false; + command = 'press' + await this.debugLog('Press Mode') + this.On = false } else { - throw new Error('Device Parameters not set for this Bot.'); + throw new Error('Device Parameters not set for this Bot.') } const bodyChange = JSON.stringify({ command: `${command}`, parameter: 'default', commandType: 'command', - }); - this.debugLog(`Sending request to SwitchBot API, body: ${bodyChange},`); + }) + this.debugLog(`Sending request to SwitchBot API, body: ${bodyChange},`) try { - const { body, statusCode } = await this.pushChangeRequest(bodyChange); - const deviceStatus: any = await body.json(); - await this.debugLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`); + const { body, statusCode } = await this.pushChangeRequest(bodyChange) + const deviceStatus: any = await body.json() + await this.debugLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`) if (await this.successfulStatusCodes(statusCode, deviceStatus)) { - await this.debugSuccessLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`); - await this.updateHomeKitCharacteristics(); + await this.debugSuccessLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`) + await this.updateHomeKitCharacteristics() } else { - await this.statusCode(statusCode); - await this.statusCode(deviceStatus.statusCode); + await this.statusCode(statusCode) + await this.statusCode(deviceStatus.statusCode) } if (this.device.bot?.mode === 'multipress') { - this.multiPressCount--; + this.multiPressCount-- if (this.multiPressCount > 0) { - await this.debugLog(`multiPressCount: ${this.multiPressCount}`); - this.On = true; - await this.openAPIpushChanges(); + await this.debugLog(`multiPressCount: ${this.multiPressCount}`) + this.On = true + await this.openAPIpushChanges() } } } catch (e: any) { - await this.apiError(e); - await this.errorLog(`failed openAPIpushChanges with ${this.device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`); + await this.apiError(e) + await this.errorLog(`failed openAPIpushChanges with ${this.device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`) } } else { - await this.debugLog(`No Changes (openAPIpushChanges), On: ${this.On} OnCached: ${this.accessory.context.On}`); + await this.debugLog(`No Changes (openAPIpushChanges), On: ${this.On} OnCached: ${this.accessory.context.On}`) } } @@ -791,403 +775,419 @@ export class Bot extends deviceBase { * Handle requests to set the "On" characteristic */ async OnSet(value: CharacteristicValue): Promise { - if (this.botDeviceType === 'switch') { - if (this.Switch) { - await this.debugLog(`Set On: ${value}`); - this.On = value === false ? false : true; - } - } else if (this.botDeviceType === 'garagedoor') { - if (this.GarageDoor) { - await this.debugLog(`Set TargetDoorState: ${value}`); - this.On = value === this.hap.Characteristic.TargetDoorState.CLOSED ? false : true; - } - } else if (this.botDeviceType === 'door') { - if (this.Door) { - await this.debugLog(`Set TargetPosition: ${value}`); - this.On = value === 0 ? false : true; - } - } else if (this.botDeviceType === 'window') { - if (this.Window) { - await this.debugLog(`Set TargetPosition: ${value}`); - this.On = value === 0 ? false : true; - } - } else if (this.botDeviceType === 'windowcovering') { - if (this.WindowCovering) { - await this.debugLog(`Set TargetPosition: ${value}`); - this.On = value === 0 ? false : true; - } - } else if (this.botDeviceType === 'lock') { - if (this.LockMechanism) { - await this.debugLog(`Set LockTargetState: ${value}`); - this.On = value === this.hap.Characteristic.LockTargetState.SECURED ? false : true; - } - } else if (this.botDeviceType === 'faucet') { - if (this.Faucet) { - await this.debugLog(`Set Active: ${value}`); - this.On = value === this.hap.Characteristic.Active.INACTIVE ? false : true; - } - } else if (this.botDeviceType === 'stateful') { - if (this.StatefulProgrammableSwitch) { - await this.debugLog(`Set ProgrammableSwitchOutputState: ${value}`); - this.On = value === 0 ? false : true; - } - } else { - if (this.Outlet) { - await this.debugLog(`Set On: ${value}`); - this.On = value === false ? false : true; - } - } - if (this.device.bot?.mode === 'multipress') { - if (this.On === true) { - this.multiPressCount++; - await this.debugLog(`multiPressCount: ${this.multiPressCount}`); - } + this.On = this.accessory.context.On ?? false + this.accessory.context.On = this.On + const deviceTypeActions: { [key: string]: () => Promise } = { + switch: async () => { + if (this.Switch) { + await this.debugLog(`Set On: ${value}`) + this.On = value !== false + } + }, + garagedoor: async () => { + if (this.GarageDoor) { + await this.debugLog(`Set TargetDoorState: ${value}`) + this.On = value !== this.hap.Characteristic.TargetDoorState.CLOSED + } + }, + door: async () => { + if (this.Door) { + await this.debugLog(`Set TargetPosition: ${value}`) + this.On = value !== 0 + } + }, + window: async () => { + if (this.Window) { + await this.debugLog(`Set TargetPosition: ${value}`) + this.On = value !== 0 + } + }, + windowcovering: async () => { + if (this.WindowCovering) { + await this.debugLog(`Set TargetPosition: ${value}`) + this.On = value !== 0 + } + }, + lock: async () => { + if (this.LockMechanism) { + await this.debugLog(`Set LockTargetState: ${value}`) + this.On = value !== this.hap.Characteristic.LockTargetState.SECURED + } + }, + faucet: async () => { + if (this.Faucet) { + await this.debugLog(`Set Active: ${value}`) + this.On = value !== this.hap.Characteristic.Active.INACTIVE + } + }, + stateful: async () => { + if (this.StatefulProgrammableSwitch) { + await this.debugLog(`Set ProgrammableSwitchOutputState: ${value}`) + this.On = value !== 0 + } + }, + default: async () => { + if (this.Outlet) { + await this.debugLog(`Set On: ${value}`) + this.On = value !== false + } + if (this.device.bot?.mode === 'multipress' && this.On) { + this.multiPressCount++ + await this.debugLog(`multiPressCount: ${this.multiPressCount}`) + } + }, } - this.doBotUpdate.next(); + const action = deviceTypeActions[this.botDeviceType] || deviceTypeActions.default + await action() + this.accessory.context.On = this.On + this.doBotUpdate.next() } /** * Updates the status for each of the HomeKit Characteristics */ async updateHomeKitCharacteristics(): Promise { - await this.debugLog('updateHomeKitCharacteristics'); + await this.debugLog('updateHomeKitCharacteristics') // BatteryLevel if (this.Battery.BatteryLevel === undefined) { - await this.debugLog(`BatteryLevel: ${this.Battery.BatteryLevel}`); + await this.debugLog(`BatteryLevel: ${this.Battery.BatteryLevel}`) } else { - this.accessory.context.BatteryLevel = this.Battery.BatteryLevel; - this.Battery.Service.updateCharacteristic(this.hap.Characteristic.BatteryLevel, this.Battery.BatteryLevel); - await this.debugLog(`updateCharacteristic BatteryLevel: ${this.Battery.BatteryLevel}`); + this.accessory.context.BatteryLevel = this.Battery.BatteryLevel + this.Battery.Service.updateCharacteristic(this.hap.Characteristic.BatteryLevel, this.Battery.BatteryLevel) + await this.debugLog(`updateCharacteristic BatteryLevel: ${this.Battery.BatteryLevel}`) } // StatusLowBattery if (this.Battery.StatusLowBattery === undefined) { - await this.debugLog(`StatusLowBattery: ${this.Battery.StatusLowBattery}`); + await this.debugLog(`StatusLowBattery: ${this.Battery.StatusLowBattery}`) } else { - this.accessory.context.StatusLowBattery = this.Battery.StatusLowBattery; - this.Battery.Service.updateCharacteristic(this.hap.Characteristic.StatusLowBattery, this.Battery.StatusLowBattery); - await this.debugLog(`updateCharacteristic StatusLowBattery: ${this.Battery.StatusLowBattery}`); + this.accessory.context.StatusLowBattery = this.Battery.StatusLowBattery + this.Battery.Service.updateCharacteristic(this.hap.Characteristic.StatusLowBattery, this.Battery.StatusLowBattery) + await this.debugLog(`updateCharacteristic StatusLowBattery: ${this.Battery.StatusLowBattery}`) } // State if (this.botDeviceType === 'switch' && this.Switch) { if (this.On === undefined) { - await this.debugLog(`On: ${this.On}`); + await this.debugLog(`On: ${this.On}`) } else { - this.Switch.Service.updateCharacteristic(this.hap.Characteristic.On, this.On); - await this.debugLog(`updateCharacteristic On: ${this.On}`); + this.Switch.Service.updateCharacteristic(this.hap.Characteristic.On, this.On) + await this.debugLog(`updateCharacteristic On: ${this.On}`) } } else if (this.botDeviceType === 'garagedoor' && this.GarageDoor) { if (this.On === undefined) { - await this.debugLog(`On: ${this.On}`); + await this.debugLog(`On: ${this.On}`) } else { if (this.On) { - this.GarageDoor.Service.updateCharacteristic(this.hap.Characteristic.TargetDoorState, this.hap.Characteristic.TargetDoorState.OPEN); - this.GarageDoor.Service.updateCharacteristic(this.hap.Characteristic.CurrentDoorState, this.hap.Characteristic.CurrentDoorState.OPEN); - await this.debugLog(`updateCharacteristic TargetDoorState: Open, CurrentDoorState: Open (${this.On})`); + this.GarageDoor.Service.updateCharacteristic(this.hap.Characteristic.TargetDoorState, this.hap.Characteristic.TargetDoorState.OPEN) + this.GarageDoor.Service.updateCharacteristic(this.hap.Characteristic.CurrentDoorState, this.hap.Characteristic.CurrentDoorState.OPEN) + await this.debugLog(`updateCharacteristic TargetDoorState: Open, CurrentDoorState: Open (${this.On})`) } else { - this.GarageDoor.Service.updateCharacteristic(this.hap.Characteristic.TargetDoorState, this.hap.Characteristic.TargetDoorState.CLOSED); - this.GarageDoor.Service.updateCharacteristic(this.hap.Characteristic.CurrentDoorState, this.hap.Characteristic.CurrentDoorState.CLOSED); - await this.debugLog(`updateCharacteristicc TargetDoorState: Closed, CurrentDoorState: Closed (${this.On})`); + this.GarageDoor.Service.updateCharacteristic(this.hap.Characteristic.TargetDoorState, this.hap.Characteristic.TargetDoorState.CLOSED) + this.GarageDoor.Service.updateCharacteristic(this.hap.Characteristic.CurrentDoorState, this.hap.Characteristic.CurrentDoorState.CLOSED) + await this.debugLog(`updateCharacteristicc TargetDoorState: Closed, CurrentDoorState: Closed (${this.On})`) } } - await this.debugLog(`Garage Door On: ${this.On}`); + await this.debugLog(`Garage Door On: ${this.On}`) } else if (this.botDeviceType === 'door' && this.Door) { if (this.On === undefined) { - await this.debugLog(`On: ${this.On}`); + await this.debugLog(`On: ${this.On}`) } else { if (this.On) { - this.Door.Service.updateCharacteristic(this.hap.Characteristic.TargetPosition, 100); - this.Door.Service.updateCharacteristic(this.hap.Characteristic.CurrentPosition, 100); - this.Door.Service.updateCharacteristic(this.hap.Characteristic.PositionState, this.hap.Characteristic.PositionState.STOPPED); - await this.debugLog(`updateCharacteristicc TargetPosition: 100, CurrentPosition: 100 (${this.On})`); + this.Door.Service.updateCharacteristic(this.hap.Characteristic.TargetPosition, 100) + this.Door.Service.updateCharacteristic(this.hap.Characteristic.CurrentPosition, 100) + this.Door.Service.updateCharacteristic(this.hap.Characteristic.PositionState, this.hap.Characteristic.PositionState.STOPPED) + await this.debugLog(`updateCharacteristicc TargetPosition: 100, CurrentPosition: 100 (${this.On})`) } else { - this.Door.Service.updateCharacteristic(this.hap.Characteristic.TargetPosition, 0); - this.Door.Service.updateCharacteristic(this.hap.Characteristic.CurrentPosition, 0); - this.Door.Service.updateCharacteristic(this.hap.Characteristic.PositionState, this.hap.Characteristic.PositionState.STOPPED); - await this.debugLog(`updateCharacteristicc TargetPosition: 0, CurrentPosition: 0 (${this.On})`); + this.Door.Service.updateCharacteristic(this.hap.Characteristic.TargetPosition, 0) + this.Door.Service.updateCharacteristic(this.hap.Characteristic.CurrentPosition, 0) + this.Door.Service.updateCharacteristic(this.hap.Characteristic.PositionState, this.hap.Characteristic.PositionState.STOPPED) + await this.debugLog(`updateCharacteristicc TargetPosition: 0, CurrentPosition: 0 (${this.On})`) } } - await this.debugLog(`Door On: ${this.On}`); + await this.debugLog(`Door On: ${this.On}`) } else if (this.botDeviceType === 'window' && this.Window) { if (this.On === undefined) { - await this.debugLog(`On: ${this.On}`); + await this.debugLog(`On: ${this.On}`) } else { if (this.On) { - this.Window.Service.updateCharacteristic(this.hap.Characteristic.TargetPosition, 100); - this.Window.Service.updateCharacteristic(this.hap.Characteristic.CurrentPosition, 100); - this.Window.Service.updateCharacteristic(this.hap.Characteristic.PositionState, this.hap.Characteristic.PositionState.STOPPED); - await this.debugLog(`updateCharacteristicc TargetPosition: 100, CurrentPosition: 100 (${this.On})`); + this.Window.Service.updateCharacteristic(this.hap.Characteristic.TargetPosition, 100) + this.Window.Service.updateCharacteristic(this.hap.Characteristic.CurrentPosition, 100) + this.Window.Service.updateCharacteristic(this.hap.Characteristic.PositionState, this.hap.Characteristic.PositionState.STOPPED) + await this.debugLog(`updateCharacteristicc TargetPosition: 100, CurrentPosition: 100 (${this.On})`) } else { - this.Window.Service.updateCharacteristic(this.hap.Characteristic.TargetPosition, 0); - this.Window.Service.updateCharacteristic(this.hap.Characteristic.CurrentPosition, 0); - this.Window.Service.updateCharacteristic(this.hap.Characteristic.PositionState, this.hap.Characteristic.PositionState.STOPPED); - await this.debugLog(`updateCharacteristicc TargetPosition: 0, CurrentPosition: 0 (${this.On})`); + this.Window.Service.updateCharacteristic(this.hap.Characteristic.TargetPosition, 0) + this.Window.Service.updateCharacteristic(this.hap.Characteristic.CurrentPosition, 0) + this.Window.Service.updateCharacteristic(this.hap.Characteristic.PositionState, this.hap.Characteristic.PositionState.STOPPED) + await this.debugLog(`updateCharacteristicc TargetPosition: 0, CurrentPosition: 0 (${this.On})`) } } - await this.debugLog(`Window On: ${this.On}`); + await this.debugLog(`Window On: ${this.On}`) } else if (this.botDeviceType === 'windowcovering' && this.WindowCovering) { if (this.On === undefined) { - await this.debugLog(`On: ${this.On}`); + await this.debugLog(`On: ${this.On}`) } else { if (this.On) { - this.WindowCovering.Service.updateCharacteristic(this.hap.Characteristic.TargetPosition, 100); - this.WindowCovering.Service.updateCharacteristic(this.hap.Characteristic.CurrentPosition, 100); - this.WindowCovering.Service.updateCharacteristic(this.hap.Characteristic.PositionState, this.hap.Characteristic.PositionState.STOPPED); - await this.debugLog(`updateCharacteristicc TargetPosition: 100, CurrentPosition: 100 (${this.On})`); + this.WindowCovering.Service.updateCharacteristic(this.hap.Characteristic.TargetPosition, 100) + this.WindowCovering.Service.updateCharacteristic(this.hap.Characteristic.CurrentPosition, 100) + this.WindowCovering.Service.updateCharacteristic(this.hap.Characteristic.PositionState, this.hap.Characteristic.PositionState.STOPPED) + await this.debugLog(`updateCharacteristicc TargetPosition: 100, CurrentPosition: 100 (${this.On})`) } else { - this.WindowCovering.Service.updateCharacteristic(this.hap.Characteristic.TargetPosition, 0); - this.WindowCovering.Service.updateCharacteristic(this.hap.Characteristic.CurrentPosition, 0); - this.WindowCovering.Service.updateCharacteristic(this.hap.Characteristic.PositionState, this.hap.Characteristic.PositionState.STOPPED); - await this.debugLog(`updateCharacteristicc TargetPosition: 0, CurrentPosition: 0 (${this.On})`); + this.WindowCovering.Service.updateCharacteristic(this.hap.Characteristic.TargetPosition, 0) + this.WindowCovering.Service.updateCharacteristic(this.hap.Characteristic.CurrentPosition, 0) + this.WindowCovering.Service.updateCharacteristic(this.hap.Characteristic.PositionState, this.hap.Characteristic.PositionState.STOPPED) + await this.debugLog(`updateCharacteristicc TargetPosition: 0, CurrentPosition: 0 (${this.On})`) } } - await this.debugLog(`Window Covering On: ${this.On}`); + await this.debugLog(`Window Covering On: ${this.On}`) } else if (this.botDeviceType === 'lock' && this.LockMechanism) { if (this.On === undefined) { - await this.debugLog(`On: ${this.On}`); + await this.debugLog(`On: ${this.On}`) } else { if (this.On) { - this.LockMechanism.Service.updateCharacteristic(this.hap.Characteristic.LockTargetState, - this.hap.Characteristic.LockTargetState.UNSECURED); - this.LockMechanism.Service.updateCharacteristic(this.hap.Characteristic.LockCurrentState, - this.hap.Characteristic.LockCurrentState.UNSECURED); - await this.debugLog(`updateCharacteristicc LockTargetState: UNSECURED, LockCurrentState: UNSECURED (${this.On})`); + this.LockMechanism.Service.updateCharacteristic(this.hap.Characteristic.LockTargetState, this.hap.Characteristic.LockTargetState.UNSECURED) + this.LockMechanism.Service.updateCharacteristic(this.hap.Characteristic.LockCurrentState, this.hap.Characteristic.LockCurrentState.UNSECURED) + await this.debugLog(`updateCharacteristicc LockTargetState: UNSECURED, LockCurrentState: UNSECURED (${this.On})`) } else { - this.LockMechanism.Service.updateCharacteristic(this.hap.Characteristic.LockTargetState, - this.hap.Characteristic.LockTargetState.SECURED); - this.LockMechanism.Service.updateCharacteristic(this.hap.Characteristic.LockCurrentState, - this.hap.Characteristic.LockCurrentState.SECURED); - await this.debugLog(`updateCharacteristic LockTargetState: SECURED, LockCurrentState: SECURED (${this.On})`); + this.LockMechanism.Service.updateCharacteristic(this.hap.Characteristic.LockTargetState, this.hap.Characteristic.LockTargetState.SECURED) + this.LockMechanism.Service.updateCharacteristic(this.hap.Characteristic.LockCurrentState, this.hap.Characteristic.LockCurrentState.SECURED) + await this.debugLog(`updateCharacteristic LockTargetState: SECURED, LockCurrentState: SECURED (${this.On})`) } } - await this.debugLog(`Lock On: ${this.On}`); + await this.debugLog(`Lock On: ${this.On}`) } else if (this.botDeviceType === 'faucet' && this.Faucet) { if (this.On === undefined) { - await this.debugLog(`On: ${this.On}`); + await this.debugLog(`On: ${this.On}`) } else { if (this.On) { - this.Faucet.Service.updateCharacteristic(this.hap.Characteristic.Active, this.hap.Characteristic.Active.ACTIVE); - await this.debugLog(`updateCharacteristic Active: ${this.On}`); + this.Faucet.Service.updateCharacteristic(this.hap.Characteristic.Active, this.hap.Characteristic.Active.ACTIVE) + await this.debugLog(`updateCharacteristic Active: ${this.On}`) } else { - this.Faucet.Service.updateCharacteristic(this.hap.Characteristic.Active, this.hap.Characteristic.Active.INACTIVE); - await this.debugLog(`updateCharacteristic Active: ${this.On}`); + this.Faucet.Service.updateCharacteristic(this.hap.Characteristic.Active, this.hap.Characteristic.Active.INACTIVE) + await this.debugLog(`updateCharacteristic Active: ${this.On}`) } } - await this.debugLog(`Faucet On: ${this.On}`); + await this.debugLog(`Faucet On: ${this.On}`) } else if (this.botDeviceType === 'fan' && this.Fan) { if (this.On === undefined) { - await this.debugLog(`On: ${this.On}`); + await this.debugLog(`On: ${this.On}`) } else { if (this.On) { - this.Fan.Service.updateCharacteristic(this.hap.Characteristic.Active, this.hap.Characteristic.Active.ACTIVE); - await this.debugLog(`updateCharacteristic Active: ${this.On}`); + this.Fan.Service.updateCharacteristic(this.hap.Characteristic.Active, this.hap.Characteristic.Active.ACTIVE) + await this.debugLog(`updateCharacteristic Active: ${this.On}`) } else { - this.Fan.Service.updateCharacteristic(this.hap.Characteristic.Active, this.hap.Characteristic.Active.INACTIVE); - await this.debugLog(`updateCharacteristic Active: ${this.On}`); + this.Fan.Service.updateCharacteristic(this.hap.Characteristic.Active, this.hap.Characteristic.Active.INACTIVE) + await this.debugLog(`updateCharacteristic Active: ${this.On}`) } } - await this.debugLog(`Fan On: ${this.On}`); + await this.debugLog(`Fan On: ${this.On}`) } else if (this.botDeviceType === 'stateful' && this.StatefulProgrammableSwitch) { if (this.On === undefined) { - await this.debugLog(`On: ${this.On}`); + await this.debugLog(`On: ${this.On}`) } else { if (this.On) { - this.StatefulProgrammableSwitch.Service.updateCharacteristic(this.hap.Characteristic.ProgrammableSwitchEvent, - this.hap.Characteristic.ProgrammableSwitchEvent.SINGLE_PRESS); - this.StatefulProgrammableSwitch.Service.updateCharacteristic(this.hap.Characteristic.ProgrammableSwitchOutputState, 1); - await this.debugLog(`updateCharacteristic ProgrammableSwitchEvent: ProgrammableSwitchOutputState: (${this.On})`); + this.StatefulProgrammableSwitch.Service.updateCharacteristic(this.hap.Characteristic.ProgrammableSwitchEvent, this.hap.Characteristic.ProgrammableSwitchEvent.SINGLE_PRESS) + this.StatefulProgrammableSwitch.Service.updateCharacteristic(this.hap.Characteristic.ProgrammableSwitchOutputState, 1) + await this.debugLog(`updateCharacteristic ProgrammableSwitchEvent: ProgrammableSwitchOutputState: (${this.On})`) } else { - this.StatefulProgrammableSwitch.Service.updateCharacteristic(this.hap.Characteristic.ProgrammableSwitchEvent, - this.hap.Characteristic.ProgrammableSwitchEvent.SINGLE_PRESS); - this.StatefulProgrammableSwitch.Service.updateCharacteristic(this.hap.Characteristic.ProgrammableSwitchOutputState, 0); - await this.debugLog(`updateCharacteristic ProgrammableSwitchEvent: ProgrammableSwitchOutputState: (${this.On})`); + this.StatefulProgrammableSwitch.Service.updateCharacteristic(this.hap.Characteristic.ProgrammableSwitchEvent, this.hap.Characteristic.ProgrammableSwitchEvent.SINGLE_PRESS) + this.StatefulProgrammableSwitch.Service.updateCharacteristic(this.hap.Characteristic.ProgrammableSwitchOutputState, 0) + await this.debugLog(`updateCharacteristic ProgrammableSwitchEvent: ProgrammableSwitchOutputState: (${this.On})`) } } - await this.debugLog(`StatefulProgrammableSwitch On: ${this.On}`); + await this.debugLog(`StatefulProgrammableSwitch On: ${this.On}`) } else if (this.botDeviceType === 'outlet' && this.Outlet) { if (this.On === undefined) { - await this.debugLog(`On: ${this.On}`); + await this.debugLog(`On: ${this.On}`) } else { - this.Outlet.Service.updateCharacteristic(this.hap.Characteristic.On, this.On); - await this.debugLog(`updateCharacteristic On: ${this.On}`); + this.Outlet.Service.updateCharacteristic(this.hap.Characteristic.On, this.On) + await this.debugLog(`updateCharacteristic On: ${this.On}`) } } else { - await this.errorLog(`botDeviceType: ${this.botDeviceType}, On: ${this.On}`); + await this.errorLog(`botDeviceType: ${this.botDeviceType}, On: ${this.On}`) } } async removeOutletService(accessory: PlatformAccessory): Promise { // If outletService still present, then remove first - accessory.context.Outlet = accessory.context.Outlet ?? {}; + accessory.context.Outlet = accessory.context.Outlet ?? {} this.Outlet = { Name: accessory.context.Outlet.Name ?? accessory.displayName, Service: accessory.getService(this.hap.Service.Outlet) as Service, - }; - accessory.context.Outlet = this.Outlet as object; - await this.warnLog('Removing any leftover Outlet Service'); - accessory.removeService(this.Outlet.Service); + } + accessory.context.Outlet = this.Outlet as object + await this.debugWarnLog('Removing any leftover Outlet Service') + accessory.removeService(this.Outlet.Service) } async removeGarageDoorService(accessory: PlatformAccessory): Promise { // If garageDoorService still present, then remove first - accessory.context.GarageDoor = accessory.context.GarageDoor ?? {}; + accessory.context.GarageDoor = accessory.context.GarageDoor ?? {} this.GarageDoor = { Name: accessory.context.GarageDoor.Name ?? accessory.displayName, Service: accessory.getService(this.hap.Service.GarageDoorOpener) as Service, - }; - accessory.context.GarageDoor = this.GarageDoor as object; - await this.warnLog('Removing any leftover Garage Door Service'); - accessory.removeService(this.GarageDoor.Service); + } + accessory.context.GarageDoor = this.GarageDoor as object + await this.debugWarnLog('Removing any leftover Garage Door Service') + accessory.removeService(this.GarageDoor.Service) } async removeDoorService(accessory: PlatformAccessory): Promise { // If doorService still present, then remove first - accessory.context.Door = accessory.context.Door ?? {}; + accessory.context.Door = accessory.context.Door ?? {} this.Door = { Name: accessory.context.Door.Name ?? accessory.displayName, Service: accessory.getService(this.hap.Service.Door) as Service, - }; - accessory.context.Door = this.Door as object; - await this.warnLog('Removing any leftover Door Service'); - accessory.removeService(this.Door.Service); + } + accessory.context.Door = this.Door as object + await this.debugWarnLog('Removing any leftover Door Service') + accessory.removeService(this.Door.Service) } async removeLockService(accessory: PlatformAccessory): Promise { // If lockService still present, then remove first - accessory.context.LockMechanism = accessory.context.LockMechanism ?? {}; + accessory.context.LockMechanism = accessory.context.LockMechanism ?? {} this.LockMechanism = { Name: accessory.context.LockMechanism.Name ?? accessory.displayName, Service: accessory.getService(this.hap.Service.LockMechanism) as Service, - }; - accessory.context.LockMechanism = this.LockMechanism as object; - this.warnLog('Removing any leftover Lock Service'); - accessory.removeService(this.LockMechanism.Service); + } + accessory.context.LockMechanism = this.LockMechanism as object + await this.debugWarnLog('Removing any leftover Lock Service') + accessory.removeService(this.LockMechanism.Service) } async removeFaucetService(accessory: PlatformAccessory): Promise { // If faucetService still present, then remove first - accessory.context.Faucet = accessory.context.Faucet ?? {}; + accessory.context.Faucet = accessory.context.Faucet ?? {} this.Faucet = { Name: accessory.context.Faucet.Name ?? accessory.displayName, Service: accessory.getService(this.hap.Service.Valve) as Service, - }; - accessory.context.Faucet = this.Faucet as object; - await this.warnLog('Removing any leftover Faucet Service'); - accessory.removeService(this.Faucet.Service); + } + accessory.context.Faucet = this.Faucet as object + await this.debugWarnLog('Removing any leftover Faucet Service') + accessory.removeService(this.Faucet.Service) } async removeFanService(accessory: PlatformAccessory): Promise { // If fanService still present, then remove first - accessory.context.Fan = accessory.context.Fan ?? {}; + accessory.context.Fan = accessory.context.Fan ?? {} this.Fan = { Name: accessory.context.Fan.Name ?? accessory.displayName, Service: accessory.getService(this.hap.Service.Fan) as Service, - }; - accessory.context.Fan = this.Fan as object; - this.warnLog('Removing any leftover Fan Service'); - accessory.removeService(this.Fan.Service); + } + accessory.context.Fan = this.Fan as object + await this.debugWarnLog('Removing any leftover Fan Service') + accessory.removeService(this.Fan.Service) } async removeWindowService(accessory: PlatformAccessory): Promise { // If windowService still present, then remove first - accessory.context.Window = accessory.context.Window ?? {}; + accessory.context.Window = accessory.context.Window ?? {} this.Window = { Name: accessory.context.Window.Name ?? accessory.displayName, Service: accessory.getService(this.hap.Service.Window) as Service, - }; - accessory.context.Window = this.Window as object; - await this.warnLog('Removing any leftover Window Service'); - accessory.removeService(this.Window.Service); + } + accessory.context.Window = this.Window as object + await this.debugWarnLog('Removing any leftover Window Service') + accessory.removeService(this.Window.Service) } async removeWindowCoveringService(accessory: PlatformAccessory): Promise { // If windowCoveringService still present, then remove first - accessory.context.WindowCovering = accessory.context.WindowCovering ?? {}; + accessory.context.WindowCovering = accessory.context.WindowCovering ?? {} this.WindowCovering = { Name: accessory.context.WindowCovering.Name ?? accessory.displayName, Service: accessory.getService(this.hap.Service.WindowCovering) as Service, - }; - accessory.context.WindowCovering = this.WindowCovering as object; - await this.warnLog('Removing any leftover Window Covering Service'); - accessory.removeService(this.WindowCovering.Service); + } + accessory.context.WindowCovering = this.WindowCovering as object + await this.debugWarnLog('Removing any leftover Window Covering Service') + accessory.removeService(this.WindowCovering.Service) } async removeStatefulProgrammableSwitchService(accessory: PlatformAccessory): Promise { // If statefulProgrammableSwitchService still present, then remove first - accessory.context.StatefulProgrammableSwitch = accessory.context.StatefulProgrammableSwitch ?? {}; + accessory.context.StatefulProgrammableSwitch = accessory.context.StatefulProgrammableSwitch ?? {} this.StatefulProgrammableSwitch = { Name: accessory.context.StatefulProgrammableSwitch.Name ?? accessory.displayName, Service: accessory.getService(this.hap.Service.StatefulProgrammableSwitch) as Service, - }; - accessory.context.StatefulProgrammableSwitch = this.StatefulProgrammableSwitch as object; - await this.warnLog('Removing any leftover Stateful Programmable Switch Service'); - accessory.removeService(this.StatefulProgrammableSwitch.Service); + } + accessory.context.StatefulProgrammableSwitch = this.StatefulProgrammableSwitch as object + await this.debugWarnLog('Removing any leftover Stateful Programmable Switch Service') + accessory.removeService(this.StatefulProgrammableSwitch.Service) } async removeSwitchService(accessory: PlatformAccessory): Promise { // If switchService still present, then remove first - accessory.context.Switch = accessory.context.Switch ?? {}; + accessory.context.Switch = accessory.context.Switch ?? {} this.Switch = { Name: accessory.context.Switch.Name ?? accessory.displayName, Service: accessory.getService(this.hap.Service.Switch) as Service, - }; - accessory.context.Switch = this.Switch as object; - await this.warnLog('Removing any leftover Switch Service'); - accessory.removeService(this.Switch.Service); + } + accessory.context.Switch = this.Switch as object + await this.debugWarnLog('Removing any leftover Switch Service') + accessory.removeService(this.Switch.Service) } async getBotConfigSettings(device: device & devicesConfig) { - //Bot Device Type - this.botDeviceType = this.accessory.context.botDeviceType === device.bot?.deviceType ? this.accessory.context.botDeviceType - : device.bot?.deviceType ?? 'outlet'; - const botDeviceType = this.accessory.context.botDeviceType ? `Using Device Type: ${this.botDeviceType}, from Accessory Cache.` - : device.bot?.deviceType ? `Using Device Type: ${this.botDeviceType}` - : `No Device Type Set, deviceType: ${this.device.bot?.deviceType}, Using default deviceType: ${this.botDeviceType}`; - await this.debugWarnLog(botDeviceType); - this.accessory.context.botDeviceType = this.botDeviceType; + // Bot Device Type + this.botDeviceType = device.bot?.deviceType ?? 'outlet' + const botDeviceType = device.bot?.deviceType + ? `Using Device Type: ${this.botDeviceType}` + : `No Device Type Set, deviceType: ${this.device.bot?.deviceType}, Using default deviceType: ${this.botDeviceType}` + await this.debugWarnLog(botDeviceType) + this.accessory.context.botDeviceType = this.botDeviceType // Bot Mode - this.botMode = this.accessory.context.botMode ?? device.bot?.mode ?? 'switch'; - const botMode = this.accessory.context.botMode ? `Using Bot Mode: ${this.botMode}, from Accessory Cache.` - : device.bot?.mode ? `Using Bot Mode: ${this.botMode}` : `No Bot Mode Set, Using default Bot Mode: ${this.botMode}`; - await this.debugWarnLog(botMode); - this.accessory.context.botMode = this.botMode; + this.botMode = device.bot?.mode ?? 'switch' + if (!device.bot?.mode) { + this.botMode = 'switch' + this.warnLog(`${this.device.deviceType}: ${this.accessory.displayName} does not have bot mode set in the Plugin's SwitchBot Device Settings, defaulting to "${this.botMode}" mode. You may experience issues.`) + } else if (['switch', 'press', 'multipress'].includes(device.bot.mode)) { + this.botMode = device.bot.mode + this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Using Bot Mode: ${this.botMode}`) + } else { + throw new Error(`${this.device.deviceType}: ${this.accessory.displayName} Invalid Bot Mode: ${device.bot.mode}`) + } + const botModeLog = device.bot?.mode + ? `Using Bot Mode: ${this.botMode}` + : `No Bot Mode Set, Using default Bot Mode: ${this.botMode}` + await this.debugWarnLog(botModeLog) + this.accessory.context.botMode = this.botMode // Bot Double Press - this.doublePress = this.accessory.context.doublePress ?? device.bot?.doublePress ?? 1; - const doublePress = this.accessory.context.doublePress ? `Using Double Press: ${this.doublePress}, from Accessory Cache.` - : device.bot?.doublePress ? `Using Double Press: ${this.doublePress}` - : `No Double Press Set, Using default Double Press: ${this.doublePress}`; - await this.debugWarnLog(doublePress); - this.accessory.context.doublePress = this.doublePress; + this.doublePress = device.bot?.doublePress ?? 1 + const doublePress = device.bot?.doublePress + ? `Using Double Press: ${this.doublePress}` + : `No Double Press Set, Using default Double Press: ${this.doublePress}` + await this.debugWarnLog(doublePress) + this.accessory.context.doublePress = this.doublePress // Bot Press PushRate - this.pushRatePress = this.accessory.context.pushRatePress ?? device.bot?.pushRatePress ?? 15; - const pushRatePress = this.accessory.context.pushRatePress ? `Using Push Rate Press: ${this.pushRatePress}, from Accessory Cache.` - : device.bot?.pushRatePress ? `Using Bot Push Rate Press: ${this.pushRatePress}` - : `No Push Rate Press Set, Using default Push Rate Press: ${this.pushRatePress}`; - await this.debugWarnLog(pushRatePress); - this.accessory.context.pushRatePress = this.pushRatePress; + this.pushRatePress = device.bot?.pushRatePress ?? 15 + const pushRatePress = device.bot?.pushRatePress + ? `Using Bot Push Rate Press: ${this.pushRatePress}` + : `No Push Rate Press Set, Using default Push Rate Press: ${this.pushRatePress}` + await this.debugWarnLog(pushRatePress) + this.accessory.context.pushRatePress = this.pushRatePress // Bot Allow Push - this.allowPush = this.accessory.context.allowPush ?? device.bot?.allowPush ?? false; - const allowPush = this.accessory.context.allowPush ? `Using Allow Push: ${this.allowPush}, from Accessory Cache.` - : device.bot?.allowPush ? `Using Allow Push: ${this.allowPush}` - : `No Allow Push Set, Using default Allow Push: ${this.allowPush}`; - await this.debugWarnLog(allowPush); - this.accessory.context.allowPush = this.allowPush; + this.allowPush = device.bot?.allowPush ?? false + const allowPush = device.bot?.allowPush + ? `Using Allow Push: ${this.allowPush}` + : `No Allow Push Set, Using default Allow Push: ${this.allowPush}` + await this.debugWarnLog(allowPush) + this.accessory.context.allowPush = this.allowPush // Bot Multi Press Count - this.multiPressCount = 0; - await this.debugWarnLog(`Multi Press Count: ${this.multiPressCount}`); + this.multiPressCount = 0 + await this.debugWarnLog(`Multi Press Count: ${this.multiPressCount}`) } async BLEPushConnection() { if (this.platform.config.credentials?.token && this.device.connectionType === 'BLE/OpenAPI') { - await this.warnLog('Using OpenAPI Connection to Push Changes'); - await this.openAPIpushChanges(); + await this.warnLog('Using OpenAPI Connection to Push Changes') + await this.openAPIpushChanges() } } async BLERefreshConnection(switchbot: any): Promise { - await this.errorLog(`wasn't able to establish BLE Connection, node-switchbot: ${switchbot}`); + await this.errorLog(`wasn't able to establish BLE Connection, node-switchbot: ${switchbot}`) if (this.platform.config.credentials?.token && this.device.connectionType === 'BLE/OpenAPI') { - await this.warnLog('Using OpenAPI Connection to Refresh Status'); - await this.openAPIRefreshStatus(); + await this.warnLog('Using OpenAPI Connection to Refresh Status') + await this.openAPIRefreshStatus() } } @@ -1195,111 +1195,110 @@ export class Bot extends deviceBase { if (this.device.offline) { if (this.botDeviceType === 'garagedoor') { if (this.GarageDoor) { - this.GarageDoor.Service.updateCharacteristic(this.hap.Characteristic.TargetDoorState, this.hap.Characteristic.TargetDoorState.CLOSED); - this.GarageDoor.Service.updateCharacteristic(this.hap.Characteristic.CurrentDoorState, this.hap.Characteristic.CurrentDoorState.CLOSED); - this.GarageDoor.Service.updateCharacteristic(this.hap.Characteristic.ObstructionDetected, false); + this.GarageDoor.Service.updateCharacteristic(this.hap.Characteristic.TargetDoorState, this.hap.Characteristic.TargetDoorState.CLOSED) + this.GarageDoor.Service.updateCharacteristic(this.hap.Characteristic.CurrentDoorState, this.hap.Characteristic.CurrentDoorState.CLOSED) + this.GarageDoor.Service.updateCharacteristic(this.hap.Characteristic.ObstructionDetected, false) } } else if (this.botDeviceType === 'door') { if (this.Door) { - this.Door.Service.updateCharacteristic(this.hap.Characteristic.TargetPosition, 0); - this.Door.Service.updateCharacteristic(this.hap.Characteristic.CurrentPosition, 0); - this.Door.Service.updateCharacteristic(this.hap.Characteristic.PositionState, this.hap.Characteristic.PositionState.STOPPED); + this.Door.Service.updateCharacteristic(this.hap.Characteristic.TargetPosition, 0) + this.Door.Service.updateCharacteristic(this.hap.Characteristic.CurrentPosition, 0) + this.Door.Service.updateCharacteristic(this.hap.Characteristic.PositionState, this.hap.Characteristic.PositionState.STOPPED) } } else if (this.botDeviceType === 'window') { if (this.Window) { - this.Window.Service.updateCharacteristic(this.hap.Characteristic.TargetPosition, 0); - this.Window.Service.updateCharacteristic(this.hap.Characteristic.CurrentPosition, 0); - this.Window.Service.updateCharacteristic(this.hap.Characteristic.PositionState, this.hap.Characteristic.PositionState.STOPPED); + this.Window.Service.updateCharacteristic(this.hap.Characteristic.TargetPosition, 0) + this.Window.Service.updateCharacteristic(this.hap.Characteristic.CurrentPosition, 0) + this.Window.Service.updateCharacteristic(this.hap.Characteristic.PositionState, this.hap.Characteristic.PositionState.STOPPED) } } else if (this.botDeviceType === 'windowcovering') { if (this.WindowCovering) { - this.WindowCovering.Service.updateCharacteristic(this.hap.Characteristic.TargetPosition, 0); - this.WindowCovering.Service.updateCharacteristic(this.hap.Characteristic.CurrentPosition, 0); - this.WindowCovering.Service.updateCharacteristic(this.hap.Characteristic.PositionState, this.hap.Characteristic.PositionState.STOPPED); + this.WindowCovering.Service.updateCharacteristic(this.hap.Characteristic.TargetPosition, 0) + this.WindowCovering.Service.updateCharacteristic(this.hap.Characteristic.CurrentPosition, 0) + this.WindowCovering.Service.updateCharacteristic(this.hap.Characteristic.PositionState, this.hap.Characteristic.PositionState.STOPPED) } } else if (this.botDeviceType === 'lock') { if (this.LockMechanism) { - this.LockMechanism.Service.updateCharacteristic(this.hap.Characteristic.LockTargetState, this.hap.Characteristic.LockTargetState.SECURED); - this.LockMechanism.Service.updateCharacteristic(this.hap.Characteristic.LockCurrentState, this.hap.Characteristic.LockCurrentState.SECURED); + this.LockMechanism.Service.updateCharacteristic(this.hap.Characteristic.LockTargetState, this.hap.Characteristic.LockTargetState.SECURED) + this.LockMechanism.Service.updateCharacteristic(this.hap.Characteristic.LockCurrentState, this.hap.Characteristic.LockCurrentState.SECURED) } } else if (this.botDeviceType === 'faucet') { if (this.Faucet) { - this.Faucet.Service.updateCharacteristic(this.hap.Characteristic.Active, this.hap.Characteristic.Active.INACTIVE); + this.Faucet.Service.updateCharacteristic(this.hap.Characteristic.Active, this.hap.Characteristic.Active.INACTIVE) } } else if (this.botDeviceType === 'fan') { if (this.Fan) { - this.Fan.Service.updateCharacteristic(this.hap.Characteristic.On, false); + this.Fan.Service.updateCharacteristic(this.hap.Characteristic.On, false) } } else if (this.botDeviceType === 'stateful') { if (this.StatefulProgrammableSwitch) { - this.StatefulProgrammableSwitch.Service.updateCharacteristic(this.hap.Characteristic.ProgrammableSwitchEvent, - this.hap.Characteristic.ProgrammableSwitchEvent.SINGLE_PRESS); - this.StatefulProgrammableSwitch.Service.updateCharacteristic(this.hap.Characteristic.ProgrammableSwitchOutputState, 0); + this.StatefulProgrammableSwitch.Service.updateCharacteristic(this.hap.Characteristic.ProgrammableSwitchEvent, this.hap.Characteristic.ProgrammableSwitchEvent.SINGLE_PRESS) + this.StatefulProgrammableSwitch.Service.updateCharacteristic(this.hap.Characteristic.ProgrammableSwitchOutputState, 0) } } else if (this.botDeviceType === 'switch') { if (this.Switch) { - this.Switch.Service.updateCharacteristic(this.hap.Characteristic.On, false); + this.Switch.Service.updateCharacteristic(this.hap.Characteristic.On, false) } } else { if (this.Outlet) { - this.Outlet.Service.updateCharacteristic(this.hap.Characteristic.On, false); + this.Outlet.Service.updateCharacteristic(this.hap.Characteristic.On, false) } } } } async apiError(e: any): Promise { - this.Battery.Service.updateCharacteristic(this.hap.Characteristic.BatteryLevel, e); - this.Battery.Service.updateCharacteristic(this.hap.Characteristic.StatusLowBattery, e); + this.Battery.Service.updateCharacteristic(this.hap.Characteristic.BatteryLevel, e) + this.Battery.Service.updateCharacteristic(this.hap.Characteristic.StatusLowBattery, e) if (this.botDeviceType === 'garagedoor') { if (this.GarageDoor) { - this.GarageDoor.Service.updateCharacteristic(this.hap.Characteristic.TargetDoorState, e); - this.GarageDoor.Service.updateCharacteristic(this.hap.Characteristic.CurrentDoorState, e); - this.GarageDoor.Service.updateCharacteristic(this.hap.Characteristic.ObstructionDetected, e); + this.GarageDoor.Service.updateCharacteristic(this.hap.Characteristic.TargetDoorState, e) + this.GarageDoor.Service.updateCharacteristic(this.hap.Characteristic.CurrentDoorState, e) + this.GarageDoor.Service.updateCharacteristic(this.hap.Characteristic.ObstructionDetected, e) } } else if (this.botDeviceType === 'door') { if (this.Door) { - this.Door.Service.updateCharacteristic(this.hap.Characteristic.TargetPosition, e); - this.Door.Service.updateCharacteristic(this.hap.Characteristic.CurrentPosition, e); - this.Door.Service.updateCharacteristic(this.hap.Characteristic.PositionState, e); + this.Door.Service.updateCharacteristic(this.hap.Characteristic.TargetPosition, e) + this.Door.Service.updateCharacteristic(this.hap.Characteristic.CurrentPosition, e) + this.Door.Service.updateCharacteristic(this.hap.Characteristic.PositionState, e) } } else if (this.botDeviceType === 'window') { if (this.Window) { - this.Window.Service.updateCharacteristic(this.hap.Characteristic.TargetPosition, e); - this.Window.Service.updateCharacteristic(this.hap.Characteristic.CurrentPosition, e); - this.Window.Service.updateCharacteristic(this.hap.Characteristic.PositionState, e); + this.Window.Service.updateCharacteristic(this.hap.Characteristic.TargetPosition, e) + this.Window.Service.updateCharacteristic(this.hap.Characteristic.CurrentPosition, e) + this.Window.Service.updateCharacteristic(this.hap.Characteristic.PositionState, e) } } else if (this.botDeviceType === 'windowcovering') { if (this.WindowCovering) { - this.WindowCovering.Service.updateCharacteristic(this.hap.Characteristic.TargetPosition, e); - this.WindowCovering.Service.updateCharacteristic(this.hap.Characteristic.CurrentPosition, e); - this.WindowCovering.Service.updateCharacteristic(this.hap.Characteristic.PositionState, e); + this.WindowCovering.Service.updateCharacteristic(this.hap.Characteristic.TargetPosition, e) + this.WindowCovering.Service.updateCharacteristic(this.hap.Characteristic.CurrentPosition, e) + this.WindowCovering.Service.updateCharacteristic(this.hap.Characteristic.PositionState, e) } } else if (this.botDeviceType === 'lock') { if (this.LockMechanism) { - this.LockMechanism.Service.updateCharacteristic(this.hap.Characteristic.LockTargetState, e); - this.LockMechanism.Service.updateCharacteristic(this.hap.Characteristic.LockCurrentState, e); + this.LockMechanism.Service.updateCharacteristic(this.hap.Characteristic.LockTargetState, e) + this.LockMechanism.Service.updateCharacteristic(this.hap.Characteristic.LockCurrentState, e) } } else if (this.botDeviceType === 'faucet') { if (this.Faucet) { - this.Faucet.Service.updateCharacteristic(this.hap.Characteristic.Active, e); + this.Faucet.Service.updateCharacteristic(this.hap.Characteristic.Active, e) } } else if (this.botDeviceType === 'fan') { if (this.Fan) { - this.Fan.Service.updateCharacteristic(this.hap.Characteristic.On, e); + this.Fan.Service.updateCharacteristic(this.hap.Characteristic.On, e) } } else if (this.botDeviceType === 'stateful') { if (this.StatefulProgrammableSwitch) { - this.StatefulProgrammableSwitch.Service.updateCharacteristic(this.hap.Characteristic.ProgrammableSwitchEvent, e); - this.StatefulProgrammableSwitch.Service.updateCharacteristic(this.hap.Characteristic.ProgrammableSwitchOutputState, e); + this.StatefulProgrammableSwitch.Service.updateCharacteristic(this.hap.Characteristic.ProgrammableSwitchEvent, e) + this.StatefulProgrammableSwitch.Service.updateCharacteristic(this.hap.Characteristic.ProgrammableSwitchOutputState, e) } } else if (this.botDeviceType === 'switch') { if (this.Switch) { - this.Switch.Service.updateCharacteristic(this.hap.Characteristic.On, e); + this.Switch.Service.updateCharacteristic(this.hap.Characteristic.On, e) } } else { if (this.Outlet) { - this.Outlet.Service.updateCharacteristic(this.hap.Characteristic.On, e); + this.Outlet.Service.updateCharacteristic(this.hap.Characteristic.On, e) } } } diff --git a/src/device/ceilinglight.ts b/src/device/ceilinglight.ts index 3729e217..af1c3fe2 100644 --- a/src/device/ceilinglight.ts +++ b/src/device/ceilinglight.ts @@ -2,18 +2,24 @@ * * ceilinglight.ts: @switchbot/homebridge-switchbot. */ -import { deviceBase } from './device.js'; -import { hs2rgb, m2hs } from '../utils.js'; -import { SwitchBotBLEModel, SwitchBotBLEModelName } from 'node-switchbot'; -import { Subject, debounceTime, interval, skipWhile, take, tap } from 'rxjs'; - -import type { devicesConfig } from '../settings.js'; -import type { device } from '../types/devicelist.js'; -import type { SwitchBotPlatform } from '../platform.js'; -import type { ceilingLightServiceData, ceilingLightProServiceData } from '../types/bledevicestatus.js'; -import type { ceilingLightStatus, ceilingLightProStatus } from '../types/devicestatus.js'; -import type { ceilingLightProWebhookContext, ceilingLightWebhookContext } from '../types/devicewebhookstatus.js'; -import type { Service, PlatformAccessory, CharacteristicValue, ControllerConstructor, Controller, ControllerServiceMap } from 'homebridge'; +import type { CharacteristicValue, Controller, ControllerConstructor, ControllerServiceMap, PlatformAccessory, Service } from 'homebridge' + +import type { SwitchBotPlatform } from '../platform.js' +import type { devicesConfig } from '../settings.js' +import type { ceilingLightProServiceData, ceilingLightServiceData } from '../types/bledevicestatus.js' +import type { device } from '../types/devicelist.js' +import type { ceilingLightProStatus, ceilingLightStatus } from '../types/devicestatus.js' +import type { ceilingLightProWebhookContext, ceilingLightWebhookContext } from '../types/devicewebhookstatus.js' + +/* +* For Testing Locally: +* import { SwitchBotBLEModel, SwitchBotBLEModelName } from '/Users/Shared/GitHub/OpenWonderLabs/node-switchbot/dist/index.js'; +*/ +import { SwitchBotBLEModel, SwitchBotBLEModelName } from 'node-switchbot' +import { debounceTime, interval, skipWhile, Subject, take, tap } from 'rxjs' + +import { hs2rgb, m2hs } from '../utils.js' +import { deviceBase } from './device.js' /** * Platform Accessory @@ -23,51 +29,51 @@ import type { Service, PlatformAccessory, CharacteristicValue, ControllerConstru export class CeilingLight extends deviceBase { // Services private LightBulb: { - Name: CharacteristicValue; - Service: Service; - On: CharacteristicValue; - Hue: CharacteristicValue; - Saturation: CharacteristicValue; - Brightness: CharacteristicValue; - ColorTemperature?: CharacteristicValue; - }; + Name: CharacteristicValue + Service: Service + On: CharacteristicValue + Hue: CharacteristicValue + Saturation: CharacteristicValue + Brightness: CharacteristicValue + ColorTemperature?: CharacteristicValue + } // OpenAPI - deviceStatus!: ceilingLightStatus | ceilingLightProStatus; + deviceStatus!: ceilingLightStatus | ceilingLightProStatus - //Webhook - webhookContext!: ceilingLightWebhookContext | ceilingLightProWebhookContext; + // Webhook + webhookContext!: ceilingLightWebhookContext | ceilingLightProWebhookContext // BLE - serviceData!: ceilingLightServiceData | ceilingLightProServiceData; + serviceData!: ceilingLightServiceData | ceilingLightProServiceData // Adaptive Lighting - adaptiveLighting!: boolean; - adaptiveLightingShift!: number; - AdaptiveLightingController?: ControllerConstructor | Controller; + adaptiveLighting!: boolean + adaptiveLightingShift!: number + AdaptiveLightingController?: ControllerConstructor | Controller // Updates - ceilingLightUpdateInProgress!: boolean; - doCeilingLightUpdate!: Subject; + ceilingLightUpdateInProgress!: boolean + doCeilingLightUpdate!: Subject constructor( readonly platform: SwitchBotPlatform, accessory: PlatformAccessory, device: device & devicesConfig, ) { - super(platform, accessory, device); + super(platform, accessory, device) // Set category - accessory.category = this.hap.Categories.LIGHTBULB; + accessory.category = this.hap.Categories.LIGHTBULB // default placeholders - this.getAdaptiveLightingSettings(accessory, device); + this.getAdaptiveLightingSettings(accessory, device) // this is subject we use to track when we need to POST changes to the SwitchBot API - this.doCeilingLightUpdate = new Subject(); - this.ceilingLightUpdateInProgress = false; + this.doCeilingLightUpdate = new Subject() + this.ceilingLightUpdateInProgress = false // Initialize LightBulb Service - accessory.context.LightBulb = accessory.context.LightBulb ?? {}; + accessory.context.LightBulb = accessory.context.LightBulb ?? {} this.LightBulb = { Name: accessory.displayName, Service: accessory.getService(this.hap.Service.Lightbulb) ?? accessory.addService(this.hap.Service.Lightbulb) as Service, @@ -76,197 +82,190 @@ export class CeilingLight extends deviceBase { Saturation: accessory.context.Saturation ?? 0, Brightness: accessory.context.Brightness ?? 0, ColorTemperature: accessory.context.ColorTemperature ?? 140, - }; - accessory.context.LightBulb = this.LightBulb as object; + } + accessory.context.LightBulb = this.LightBulb as object if (this.adaptiveLighting && this.adaptiveLightingShift === -1 && this.LightBulb) { - accessory.removeService(this.LightBulb.Service); - this.LightBulb.Service = accessory.addService(this.hap.Service.Lightbulb); - accessory.context.adaptiveLighting = false; - this.debugLog(`adaptiveLighting: ${this.adaptiveLighting}`); + accessory.removeService(this.LightBulb.Service) + this.LightBulb.Service = accessory.addService(this.hap.Service.Lightbulb) + accessory.context.adaptiveLighting = false + this.debugLog(`adaptiveLighting: ${this.adaptiveLighting}`) } else if (this.adaptiveLighting && this.adaptiveLightingShift >= 0 && this.LightBulb) { this.AdaptiveLightingController = new platform.api.hap.AdaptiveLightingController(this.LightBulb.Service, { controllerMode: this.hap.AdaptiveLightingControllerMode.AUTOMATIC, customTemperatureAdjustment: this.adaptiveLightingShift, - }); - accessory.configureController(this.AdaptiveLightingController); - accessory.context.adaptiveLighting = true; + }) + accessory.configureController(this.AdaptiveLightingController) + accessory.context.adaptiveLighting = true this.debugLog(`adaptiveLighting: ${this.adaptiveLighting}, adaptiveLightingShift: ${this.adaptiveLightingShift}`, - ); + ) } else { - accessory.context.adaptiveLighting = false; - this.debugLog(`adaptiveLighting: ${accessory.context.adaptiveLighting}`); + accessory.context.adaptiveLighting = false + this.debugLog(`adaptiveLighting: ${accessory.context.adaptiveLighting}`) } // Initialize LightBulb Characteristics - this.LightBulb.Service - .setCharacteristic(this.hap.Characteristic.Name, this.LightBulb.Name) - .getCharacteristic(this.hap.Characteristic.On) - .onGet(() => { - return this.LightBulb.On; - }) - .onSet(this.OnSet.bind(this)); + this.LightBulb.Service.setCharacteristic(this.hap.Characteristic.Name, this.LightBulb.Name).getCharacteristic(this.hap.Characteristic.On).onGet(() => { + return this.LightBulb.On + }).onSet(this.OnSet.bind(this)) // Initialize LightBulb Brightness - this.LightBulb.Service - .getCharacteristic(this.hap.Characteristic.Brightness) - .setProps({ - minStep: device.ceilinglight?.set_minStep ?? 1, - minValue: 0, - maxValue: 100, - validValueRanges: [0, 100], - }) - .onGet(() => { - return this.LightBulb.Brightness; - }) - .onSet(this.BrightnessSet.bind(this)); + this.LightBulb.Service.getCharacteristic(this.hap.Characteristic.Brightness).setProps({ + minStep: device.ceilinglight?.set_minStep ?? 1, + minValue: 0, + maxValue: 100, + validValueRanges: [0, 100], + }).onGet(() => { + return this.LightBulb.Brightness + }).onSet(this.BrightnessSet.bind(this)) // Initialize LightBulb ColorTemperature - this.LightBulb.Service - .getCharacteristic(this.hap.Characteristic.ColorTemperature) - .setProps({ - minValue: 140, - maxValue: 500, - validValueRanges: [140, 500], - }) - .onGet(() => { - return this.LightBulb.ColorTemperature!; - }) - .onSet(this.ColorTemperatureSet.bind(this)); + this.LightBulb.Service.getCharacteristic(this.hap.Characteristic.ColorTemperature).setProps({ + minValue: 140, + maxValue: 500, + validValueRanges: [140, 500], + }).onGet(() => { + return this.LightBulb.ColorTemperature! + }).onSet(this.ColorTemperatureSet.bind(this)) // Initialize LightBulb Hue - this.LightBulb.Service - .getCharacteristic(this.hap.Characteristic.Hue) - .setProps({ - minValue: 0, - maxValue: 360, - validValueRanges: [0, 360], - }) - .onGet(() => { - return this.LightBulb.Hue; - }) - .onSet(this.HueSet.bind(this)); + this.LightBulb.Service.getCharacteristic(this.hap.Characteristic.Hue).setProps({ + minValue: 0, + maxValue: 360, + validValueRanges: [0, 360], + }).onGet(() => { + return this.LightBulb.Hue + }).onSet(this.HueSet.bind(this)) // Initialize LightBulb Saturation - this.LightBulb.Service - .getCharacteristic(this.hap.Characteristic.Saturation) - .setProps({ - minValue: 0, - maxValue: 100, - validValueRanges: [0, 100], - }) - .onGet(() => { - return this.LightBulb.Saturation; - }) - .onSet(this.SaturationSet.bind(this)); + this.LightBulb.Service.getCharacteristic(this.hap.Characteristic.Saturation).setProps({ + minValue: 0, + maxValue: 100, + validValueRanges: [0, 100], + }).onGet(() => { + return this.LightBulb.Saturation + }).onSet(this.SaturationSet.bind(this)) // Retrieve initial values and updateHomekit - this.debugLog('Retrieve initial values and update Homekit'); - this.refreshStatus(); + try { + this.debugLog('Retrieve initial values and update Homekit') + this.refreshStatus() + } catch (e: any) { + this.errorLog(`failed to retrieve initial values and update Homekit, Error: ${e}`) + } - //regisiter webhook event handler - this.debugLog('Registering Webhook Event Handler'); - this.registerWebhook(); + // regisiter webhook event handler if enabled + try { + this.debugLog('Registering Webhook Event Handler') + this.registerWebhook() + } catch (e: any) { + this.errorLog(`failed to registerWebhook, Error: ${e}`) + } + + // regisiter platform BLE event handler if enabled + try { + this.debugLog('Registering Platform BLE Event Handler') + this.registerPlatformBLE() + } catch (e: any) { + this.errorLog(`failed to registerPlatformBLE, Error: ${e}`) + } // Start an update interval interval(this.deviceRefreshRate * 1000) .pipe(skipWhile(() => this.ceilingLightUpdateInProgress)) .subscribe(async () => { - await this.refreshStatus(); - }); + await this.refreshStatus() + }) // Watch for Bulb change events // We put in a debounce of 100ms so we don't make duplicate calls this.doCeilingLightUpdate .pipe( tap(() => { - this.ceilingLightUpdateInProgress = true; + this.ceilingLightUpdateInProgress = true }), debounceTime(this.devicePushRate * 1000), ) .subscribe(async () => { try { - await this.pushChanges(); + await this.pushChanges() } catch (e: any) { - await this.apiError(e); - await this.errorLog(`failed pushChanges with ${device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`); + await this.apiError(e) + await this.errorLog(`failed pushChanges with ${device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`) } - this.ceilingLightUpdateInProgress = false; - }); + this.ceilingLightUpdateInProgress = false + }) } /** * Parse the device status from the SwitchBotBLE API */ async BLEparseStatus(): Promise { - await this.debugLog('BLEparseStatus'); - await this.debugLog(`(power, brightness, colorTemperature) = BLE:(${this.serviceData.state}, ${this.serviceData.brightness}, ` - + `${this.serviceData.color_temperature}), current:(${this.LightBulb.On}, ${this.LightBulb.Brightness}, ${this.LightBulb.ColorTemperature})`); + await this.debugLog('BLEparseStatus') + await this.debugLog(`(power, brightness, colorTemperature) = BLE:(${this.serviceData.state}, ${this.serviceData.brightness}, ${this.serviceData.color_temperature}), current:(${this.LightBulb.On}, ${this.LightBulb.Brightness}, ${this.LightBulb.ColorTemperature})`) // On - this.LightBulb.On = this.serviceData.state; - await this.debugLog(`On: ${this.LightBulb.On}`); + this.LightBulb.On = this.serviceData.state + await this.debugLog(`On: ${this.LightBulb.On}`) // ColorTemperature - const miredColorTemperature = Math.round(1000000 / this.serviceData.color_temperature); - this.LightBulb.ColorTemperature = Math.max(Math.min(miredColorTemperature, 500), 140); - await this.debugLog(`ColorTemperature: ${this.LightBulb.ColorTemperature}`); + const miredColorTemperature = Math.round(1000000 / this.serviceData.color_temperature) + this.LightBulb.ColorTemperature = Math.max(Math.min(miredColorTemperature, 500), 140) + await this.debugLog(`ColorTemperature: ${this.LightBulb.ColorTemperature}`) } /** * Parse the device status from the SwitchBot OpenAPI */ async openAPIparseStatus(): Promise { - await this.debugLog('openAPIparseStatus'); - await this.debugLog(`(power, brightness, colorTemperature) = OpenAPI:(${this.deviceStatus.power}, ${this.deviceStatus.brightness}, ` - + `${this.deviceStatus.colorTemperature}), current:(${this.LightBulb.On}, ${this.LightBulb.Brightness}, ${this.LightBulb.ColorTemperature})`); + await this.debugLog('openAPIparseStatus') + await this.debugLog(`(power, brightness, colorTemperature) = OpenAPI:(${this.deviceStatus.power}, ${this.deviceStatus.brightness}, ${this.deviceStatus.colorTemperature}), current:(${this.LightBulb.On}, ${this.LightBulb.Brightness}, ${this.LightBulb.ColorTemperature})`) // On - this.LightBulb.On = this.deviceStatus.power; - await this.debugLog(`On: ${this.LightBulb.On}`); + this.LightBulb.On = this.deviceStatus.power + await this.debugLog(`On: ${this.LightBulb.On}`) // Brightness - this.LightBulb.Brightness = this.deviceStatus.brightness; - await this.debugLog(`Brightness: ${this.LightBulb.Brightness}`); + this.LightBulb.Brightness = this.deviceStatus.brightness + await this.debugLog(`Brightness: ${this.LightBulb.Brightness}`) // ColorTemperature - const miredColorTemperature = Math.round(1000000 / this.deviceStatus.colorTemperature); - this.LightBulb.ColorTemperature = Math.max(Math.min(miredColorTemperature, 500), 140); - await this.debugLog(`ColorTemperature: ${this.LightBulb.ColorTemperature}`); + const miredColorTemperature = Math.round(1000000 / this.deviceStatus.colorTemperature) + this.LightBulb.ColorTemperature = Math.max(Math.min(miredColorTemperature, 500), 140) + await this.debugLog(`ColorTemperature: ${this.LightBulb.ColorTemperature}`) // Firmware Version if (this.deviceStatus.version) { - const version = this.deviceStatus.version.toString(); - await this.debugLog(`Firmware Version: ${version.replace(/^V|-.*$/g, '')}`); - const deviceVersion = version.replace(/^V|-.*$/g, '') ?? '0.0.0'; + const version = this.deviceStatus.version.toString() + await this.debugLog(`Firmware Version: ${version.replace(/^V|-.*$/g, '')}`) + const deviceVersion = version.replace(/^V|-.*$/g, '') ?? '0.0.0' this.accessory .getService(this.hap.Service.AccessoryInformation)! .setCharacteristic(this.hap.Characteristic.HardwareRevision, deviceVersion) .setCharacteristic(this.hap.Characteristic.FirmwareRevision, deviceVersion) .getCharacteristic(this.hap.Characteristic.FirmwareRevision) - .updateValue(deviceVersion); - this.accessory.context.version = deviceVersion; - await this.debugSuccessLog(`version: ${this.accessory.context.version}`); + .updateValue(deviceVersion) + this.accessory.context.version = deviceVersion + await this.debugSuccessLog(`version: ${this.accessory.context.version}`) } } async parseStatusWebhook(): Promise { - await this.debugLog('parseStatusWebhook'); - await this.debugLog(`(powerState, brightness, colorTemperature) = Webhook:(${this.webhookContext.powerState}, ${this.webhookContext.brightness}, ` - + `${this.webhookContext.colorTemperature}), current:(${this.LightBulb.On}, ${this.LightBulb.Brightness}, ${this.LightBulb.ColorTemperature})`); + await this.debugLog('parseStatusWebhook') + await this.debugLog(`(powerState, brightness, colorTemperature) = Webhook:(${this.webhookContext.powerState}, ${this.webhookContext.brightness}, ${this.webhookContext.colorTemperature}), current:(${this.LightBulb.On}, ${this.LightBulb.Brightness}, ${this.LightBulb.ColorTemperature})`) // On - this.LightBulb.On = this.webhookContext.powerState === 'ON' ? true : false; - await this.debugLog(`On: ${this.LightBulb.On}`); + this.LightBulb.On = this.webhookContext.powerState === 'ON' + await this.debugLog(`On: ${this.LightBulb.On}`) // Brightness - this.LightBulb.Brightness = this.webhookContext.brightness; - await this.debugLog(`Brightness: ${this.LightBulb.Brightness}`); + this.LightBulb.Brightness = this.webhookContext.brightness + await this.debugLog(`Brightness: ${this.LightBulb.Brightness}`) // ColorTemperature - const miredColorTemperature = Math.round(1000000 / this.webhookContext.colorTemperature); - this.LightBulb.ColorTemperature = Math.max(Math.min(miredColorTemperature, 500), 140); - await this.debugLog(`ColorTemperature: ${this.LightBulb.ColorTemperature}`); + const miredColorTemperature = Math.round(1000000 / this.webhookContext.colorTemperature) + this.LightBulb.ColorTemperature = Math.max(Math.min(miredColorTemperature, 500), 140) + await this.debugLog(`ColorTemperature: ${this.LightBulb.ColorTemperature}`) } /** @@ -274,307 +273,304 @@ export class CeilingLight extends deviceBase { */ async refreshStatus(): Promise { if (!this.device.enableCloudService && this.OpenAPI) { - await this.errorLog(`refreshStatus enableCloudService: ${this.device.enableCloudService}`); - } else if (this.BLE || this.config.options?.BLE) { - await this.BLERefreshStatus(); + await this.errorLog(`refreshStatus enableCloudService: ${this.device.enableCloudService}`) + } else if (this.BLE) { + await this.BLERefreshStatus() } else if (this.OpenAPI && this.platform.config.credentials?.token) { - await this.openAPIRefreshStatus(); + await this.openAPIRefreshStatus() } else { - await this.offlineOff(); - await this.debugWarnLog(`Connection Type: ${this.device.connectionType}, refreshStatus will not happen.`); + await this.offlineOff() + await this.debugWarnLog(`Connection Type: ${this.device.connectionType}, refreshStatus will not happen.`) } } async BLERefreshStatus(): Promise { - await this.debugLog('BLERefreshStatus'); + await this.debugLog('BLERefreshStatus') + const switchbot = await this.switchbotBLE() + if (switchbot === undefined) { + await this.BLERefreshConnection(switchbot) + } else { + (async () => { + // Start to monitor advertisement packets + const serviceData = await this.monitorAdvertisementPackets(switchbot) as unknown as ceilingLightServiceData + // Update HomeKit + if ((serviceData.model === SwitchBotBLEModel.CeilingLight || SwitchBotBLEModel.CeilingLightPro) && (serviceData.modelName === SwitchBotBLEModelName.CeilingLight || SwitchBotBLEModelName.CeilingLightPro)) { + this.serviceData = serviceData + await this.BLEparseStatus() + await this.updateHomeKitCharacteristics() + } else { + await this.errorLog(`failed to get serviceData, serviceData: ${serviceData}`) + await this.BLERefreshConnection(switchbot) + } + })() + } + } + + async registerPlatformBLE(): Promise { + await this.debugLog('registerPlatformBLE') if (this.config.options?.BLE) { - await this.debugLog('is listening to Platform BLE.'); - this.device.bleMac = this.device.deviceId!.match(/.{1,2}/g)!.join(':').toLowerCase(); - await this.debugLog(`bleMac: ${this.device.bleMac}`); - this.platform.bleEventHandler[this.device.bleMac] = async (context: ceilingLightServiceData) => { + await this.debugLog('is listening to Platform BLE.') + this.device.bleMac = this.device.deviceId!.match(/.{1,2}/g)!.join(':').toLowerCase() + await this.debugLog(`bleMac: ${this.device.bleMac}`) + this.platform.bleEventHandler[this.device.bleMac] = async (context: ceilingLightServiceData | ceilingLightProServiceData) => { try { - await this.debugLog(`received BLE: ${JSON.stringify(context)}`); - this.serviceData = context; - await this.BLEparseStatus(); - await this.updateHomeKitCharacteristics(); + await this.debugLog(`received BLE: ${JSON.stringify(context)}`) + this.serviceData = context + await this.BLEparseStatus() + await this.updateHomeKitCharacteristics() } catch (e: any) { - await this.errorLog(`failed to handle BLE. Received: ${JSON.stringify(context)} Error: ${e}`); + await this.errorLog(`failed to handle BLE. Received: ${JSON.stringify(context)} Error: ${e}`) } - }; - } else { - await this.debugLog('is using Device BLE Scanning.'); - const switchbot = await this.switchbotBLE(); - if (switchbot === undefined) { - await this.BLERefreshConnection(switchbot); - } else { - (async () => { - // Start to monitor advertisement packets - const serviceData = await this.monitorAdvertisementPackets(switchbot) as unknown as ceilingLightServiceData; - // Update HomeKit - if ((serviceData.model === SwitchBotBLEModel.CeilingLight || SwitchBotBLEModel.CeilingLightPro) - && serviceData.modelName === SwitchBotBLEModelName.CeilingLight || SwitchBotBLEModelName.CeilingLightPro) { - this.serviceData = serviceData; - await this.BLEparseStatus(); - await this.updateHomeKitCharacteristics(); - } else { - await this.errorLog(`failed to get serviceData, serviceData: ${serviceData}`); - await this.BLERefreshConnection(switchbot); - } - })(); } + } else { + await this.debugLog('is not listening to Platform BLE') } } async openAPIRefreshStatus(): Promise { - await this.debugLog('openAPIRefreshStatus'); + await this.debugLog('openAPIRefreshStatus') try { - const { body, statusCode } = await this.deviceRefreshStatus(); - const deviceStatus: any = await body.json(); - await this.debugLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`);; + const { body, statusCode } = await this.deviceRefreshStatus() + const deviceStatus: any = await body.json() + await this.debugLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`) if (await this.successfulStatusCodes(statusCode, deviceStatus)) { - await this.debugSuccessLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`); - this.deviceStatus = deviceStatus.body; - await this.openAPIparseStatus(); - await this.updateHomeKitCharacteristics(); + await this.debugSuccessLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`) + this.deviceStatus = deviceStatus.body + await this.openAPIparseStatus() + await this.updateHomeKitCharacteristics() } else { - await this.debugWarnLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`); - await this.debugWarnLog(statusCode, deviceStatus); + await this.debugWarnLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`) + await this.debugWarnLog(statusCode, deviceStatus) } } catch (e: any) { - await this.apiError(e); - await this.errorLog(`failed openAPIRefreshStatus with ${this.device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`); + await this.apiError(e) + await this.errorLog(`failed openAPIRefreshStatus with ${this.device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`) } } async registerWebhook() { if (this.device.webhook) { - await this.debugLog('is listening webhook.'); + await this.debugLog('is listening webhook.') this.platform.webhookEventHandler[this.device.deviceId] = async (context: ceilingLightWebhookContext | ceilingLightProWebhookContext) => { try { - await this.debugLog(`received Webhook: ${JSON.stringify(context)}`); - this.webhookContext = context; - await this.parseStatusWebhook(); - await this.updateHomeKitCharacteristics(); + await this.debugLog(`received Webhook: ${JSON.stringify(context)}`) + this.webhookContext = context + await this.parseStatusWebhook() + await this.updateHomeKitCharacteristics() } catch (e: any) { - await this.errorLog(`failed to handle webhook. Received: ${JSON.stringify(context)} Error: ${e}`); + await this.errorLog(`failed to handle webhook. Received: ${JSON.stringify(context)} Error: ${e}`) } - }; + } } else { - await this.debugLog('is not listening webhook.'); + await this.debugLog('is not listening webhook.') } } /** * Pushes the requested changes to the SwitchBot API - * deviceType commandType Command command parameter Description - * Color Bulb - "command" "turnOff" "default" = set to OFF state - * Color Bulb - "command" "turnOn" "default" = set to ON state - * Color Bulb - "command" "toggle" "default" = toggle state - * Color Bulb - "command" "setBrightness" "{1-100}" = set brightness - * Color Bulb - "command" "setColor" "{0-255}:{0-255}:{0-255}" = set RGB color value - * Color Bulb - "command" "setColorTemperature" "{2700-6500}" = set color temperature + * deviceType commandType Command command parameter Description + * Color Bulb - "command" "turnOff" "default" = set to OFF state + * Color Bulb - "command" "turnOn" "default" = set to ON state + * Color Bulb - "command" "toggle" "default" = toggle state + * Color Bulb - "command" "setBrightness" "{1-100}" = set brightness + * Color Bulb - "command" "setColor" "{0-255}:{0-255}:{0-255}" = set RGB color value + * Color Bulb - "command" "setColorTemperature" "{2700-6500}" = set color temperature * */ async pushChanges(): Promise { if (!this.device.enableCloudService && this.OpenAPI) { - await this.errorLog(`pushChanges enableCloudService: ${this.device.enableCloudService}`); + await this.errorLog(`pushChanges enableCloudService: ${this.device.enableCloudService}`) } else if (this.BLE) { - await this.BLEpushChanges(); + await this.BLEpushChanges() } else if (this.OpenAPI && this.platform.config.credentials?.token) { - await this.openAPIpushChanges(); + await this.openAPIpushChanges() if (this.LightBulb.On) { - await this.debugLog(`On: ${this.LightBulb.On}`); + await this.debugLog(`On: ${this.LightBulb.On}`) // Push Brightness Update - await this.debugLog(`Brightness: ${this.LightBulb.Brightness}`); - await this.pushBrightnessChanges(); + await this.debugLog(`Brightness: ${this.LightBulb.Brightness}`) + await this.pushBrightnessChanges() // Push ColorTemperature Update - await this.debugLog(`ColorTemperature: ${this.LightBulb.ColorTemperature}`); - await this.pushColorTemperatureChanges(); + await this.debugLog(`ColorTemperature: ${this.LightBulb.ColorTemperature}`) + await this.pushColorTemperatureChanges() // Push Hue & Saturation Update - await this.debugLog(`Hue: ${this.LightBulb.Hue}, Saturation: ${this.LightBulb.Saturation}`); - await this.pushHueSaturationChanges(); + await this.debugLog(`Hue: ${this.LightBulb.Hue}, Saturation: ${this.LightBulb.Saturation}`) + await this.pushHueSaturationChanges() } else { - await this.debugLog('BLE (Brightness), (ColorTemperature), (Hue), & (Saturation) changes will not happen, as the device is OFF.'); + await this.debugLog('BLE (Brightness), (ColorTemperature), (Hue), & (Saturation) changes will not happen, as the device is OFF.') } } else { - await this.offlineOff(); - await this.debugWarnLog(`Connection Type: ${this.device.connectionType}, pushChanges will not happen.`); + await this.offlineOff() + await this.debugWarnLog(`Connection Type: ${this.device.connectionType}, pushChanges will not happen.`) } // Refresh the status from the API interval(15000) .pipe(skipWhile(() => this.ceilingLightUpdateInProgress)) .pipe(take(1)) .subscribe(async () => { - await this.refreshStatus(); - }); + await this.refreshStatus() + }) } async BLEpushChanges(): Promise { - await this.debugLog('BLEpushChanges'); + await this.debugLog('BLEpushChanges') if (this.LightBulb.On !== this.accessory.context.On) { - await this.debugLog(`BLEpushChanges On: ${this.LightBulb.On} OnCached: ${this.accessory.context.On}`); - const switchbot = await this.platform.connectBLE(this.accessory, this.device); - await this.convertBLEAddress(); + await this.debugLog(`BLEpushChanges On: ${this.LightBulb.On} OnCached: ${this.accessory.context.On}`) + const switchbot = await this.platform.connectBLE(this.accessory, this.device) + await this.convertBLEAddress() if (switchbot !== false) { switchbot .discover({ model: this.device.bleModel, id: this.device.bleMac }) .then(async (device_list: any) => { - this.infoLog(`On: ${this.LightBulb.On}`); + this.infoLog(`On: ${this.LightBulb.On}`) return await this.retryBLE({ max: await this.maxRetryBLE(), fn: async () => { if (this.LightBulb.On) { - return await device_list[0].turnOn({ id: this.device.bleMac }); + return await device_list[0].turnOn({ id: this.device.bleMac }) } else { - return await device_list[0].turnOff({ id: this.device.bleMac }); + return await device_list[0].turnOff({ id: this.device.bleMac }) } }, - }); + }) }) .then(async () => { - await this.successLog(`On: ${this.LightBulb.On} sent over SwitchBot BLE, sent successfully`); - this.LightBulb.On = false; + await this.successLog(`On: ${this.LightBulb.On} sent over SwitchBot BLE, sent successfully`) + this.LightBulb.On = false }) .catch(async (e: any) => { - await this.apiError(e); - await this.errorLog(`failed BLEpushChanges with ${this.device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`); - await this.BLEPushConnection(); - }); + await this.apiError(e) + await this.errorLog(`failed BLEpushChanges with ${this.device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`) + await this.BLEPushConnection() + }) } else { - await this.errorLog(`wasn't able to establish BLE Connection, node-switchbot: ${switchbot}`); - await this.BLEPushConnection(); + await this.errorLog(`wasn't able to establish BLE Connection, node-switchbot: ${switchbot}`) + await this.BLEPushConnection() } } else { - await this.debugLog(`No changes (BLEpushChanges): On: ${this.LightBulb.On}, OnCached: ${this.accessory.context.On}`); + await this.debugLog(`No changes (BLEpushChanges): On: ${this.LightBulb.On}, OnCached: ${this.accessory.context.On}`) } } async openAPIpushChanges(): Promise { - await this.debugLog('openAPIpushChanges'); + await this.debugLog('openAPIpushChanges') if (this.LightBulb.On !== this.accessory.context.On) { - const command = this.LightBulb.On ? 'turnOn' : 'turnOff'; + const command = this.LightBulb.On ? 'turnOn' : 'turnOff' const bodyChange = JSON.stringify({ command: `${command}`, parameter: 'default', commandType: 'command', - }); - await this.debugLog(`SwitchBot OpenAPI bodyChange: ${JSON.stringify(bodyChange)}`); + }) + await this.debugLog(`SwitchBot OpenAPI bodyChange: ${JSON.stringify(bodyChange)}`) try { - const { body, statusCode } = await this.pushChangeRequest(bodyChange); - const deviceStatus: any = await body.json(); - await this.debugLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`); + const { body, statusCode } = await this.pushChangeRequest(bodyChange) + const deviceStatus: any = await body.json() + await this.debugLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`) if (await this.successfulStatusCodes(statusCode, deviceStatus)) { - await this.debugSuccessLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`); - await this.updateHomeKitCharacteristics(); + await this.debugSuccessLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`) + await this.updateHomeKitCharacteristics() } else { - await this.statusCode(statusCode); - await this.statusCode(deviceStatus.statusCode); + await this.statusCode(statusCode) + await this.statusCode(deviceStatus.statusCode) } } catch (e: any) { - await this.apiError(e); - await this.errorLog(`failed openAPIpushChanges with ${this.device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`); + await this.apiError(e) + await this.errorLog(`failed openAPIpushChanges with ${this.device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`) } } else { - await this.debugLog(`No changes (openAPIpushChanges), On: ${this.LightBulb.On}, OnCached: ${this.accessory.context.On}`); + await this.debugLog(`No changes (openAPIpushChanges), On: ${this.LightBulb.On}, OnCached: ${this.accessory.context.On}`) } } async pushHueSaturationChanges(): Promise { - await this.debugLog('pushHueSaturationChanges'); + await this.debugLog('pushHueSaturationChanges') if ((this.LightBulb.Hue !== this.accessory.context.Hue) || (this.LightBulb.Saturation !== this.accessory.context.Saturation)) { - await this.debugLog(`Hue: ${JSON.stringify(this.LightBulb.Hue)}`); - await this.debugLog(`Saturation: ${JSON.stringify(this.LightBulb.Saturation)}`); - const [red, green, blue] = hs2rgb(Number(this.LightBulb.Hue), Number(this.LightBulb.Saturation)); - await this.debugLog(`rgb: ${JSON.stringify([red, green, blue])}`); + await this.debugLog(`Hue: ${JSON.stringify(this.LightBulb.Hue)}`) + await this.debugLog(`Saturation: ${JSON.stringify(this.LightBulb.Saturation)}`) + const [red, green, blue] = hs2rgb(Number(this.LightBulb.Hue), Number(this.LightBulb.Saturation)) + await this.debugLog(`rgb: ${JSON.stringify([red, green, blue])}`) const bodyChange = JSON.stringify({ command: 'setColor', parameter: `${red}:${green}:${blue}`, commandType: 'command', - }); - await this.debugLog(`(pushHueSaturationChanges) SwitchBot OpenAPI bodyChange: ${JSON.stringify(bodyChange)}`); + }) + await this.debugLog(`(pushHueSaturationChanges) SwitchBot OpenAPI bodyChange: ${JSON.stringify(bodyChange)}`) try { - const { body, statusCode } = await this.pushChangeRequest(bodyChange); - const deviceStatus: any = await body.json(); - await this.debugLog(`(pushHueSaturationChanges) statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`); + const { body, statusCode } = await this.pushChangeRequest(bodyChange) + const deviceStatus: any = await body.json() + await this.debugLog(`(pushHueSaturationChanges) statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`) if (await this.successfulStatusCodes(statusCode, deviceStatus)) { - await this.debugSuccessLog(`(pushHueSaturationChanges) statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`); - await this.updateHomeKitCharacteristics(); + await this.debugSuccessLog(`(pushHueSaturationChanges) statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`) + await this.updateHomeKitCharacteristics() } else { - await this.statusCode(statusCode); - await this.statusCode(deviceStatus.statusCode); + await this.statusCode(statusCode) + await this.statusCode(deviceStatus.statusCode) } } catch (e: any) { - await this.apiError(e); - await this.errorLog(`failed pushHueSaturationChanges with ${this.device.connectionType} Connection,` - + ` Error Message: ${JSON.stringify(e.message)}`); + await this.apiError(e) + await this.errorLog(`failed pushHueSaturationChanges with ${this.device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`) } } else { - await this.debugLog(`No changes (pushHueSaturationChanges), Hue: ${this.LightBulb.Hue}, HueCached: ${this.accessory.context.Hue},` - + ` Saturation: ${this.LightBulb.Saturation}, SaturationCached: ${this.accessory.context.Saturation}`); + await this.debugLog(`No changes (pushHueSaturationChanges), Hue: ${this.LightBulb.Hue}, HueCached: ${this.accessory.context.Hue}, Saturation: ${this.LightBulb.Saturation}, SaturationCached: ${this.accessory.context.Saturation}`) } } async pushColorTemperatureChanges(): Promise { - await this.debugLog('pushColorTemperatureChanges'); + await this.debugLog('pushColorTemperatureChanges') if (this.LightBulb.ColorTemperature !== this.accessory.context.ColorTemperature) { - const kelvin = Math.round(1000000 * Number(this.LightBulb.ColorTemperature)); - this.accessory.context.kelvin = kelvin; + const kelvin = Math.round(1000000 * Number(this.LightBulb.ColorTemperature)) + this.accessory.context.kelvin = kelvin const bodyChange = JSON.stringify({ command: 'setColorTemperature', parameter: `${kelvin}`, commandType: 'command', - }); - await this.debugLog(`(pushColorTemperatureChanges) SwitchBot OpenAPI bodyChange: ${JSON.stringify(bodyChange)}`); + }) + await this.debugLog(`(pushColorTemperatureChanges) SwitchBot OpenAPI bodyChange: ${JSON.stringify(bodyChange)}`) try { - const { body, statusCode } = await this.pushChangeRequest(bodyChange); - const deviceStatus: any = await body.json(); - await this.debugLog(`(pushColorTemperatureChanges) statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`); + const { body, statusCode } = await this.pushChangeRequest(bodyChange) + const deviceStatus: any = await body.json() + await this.debugLog(`(pushColorTemperatureChanges) statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`) if (await this.successfulStatusCodes(statusCode, deviceStatus)) { - await this.debugSuccessLog(`(pushColorTemperatureChanges) statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`); - await this.updateHomeKitCharacteristics(); + await this.debugSuccessLog(`(pushColorTemperatureChanges) statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`) + await this.updateHomeKitCharacteristics() } else { - await this.statusCode(statusCode); - await this.statusCode(deviceStatus.statusCode); + await this.statusCode(statusCode) + await this.statusCode(deviceStatus.statusCode) } } catch (e: any) { - await this.apiError(e); - await this.errorLog(`failed pushColorTemperatureChanges with ${this.device.connectionType} Connection,` - + ` Error Message: ${JSON.stringify(e.message)}`); + await this.apiError(e) + await this.errorLog(`failed pushColorTemperatureChanges with ${this.device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`) } } else { - await this.debugLog(`No changes (pushColorTemperatureChanges), ColorTemperature: ${this.LightBulb.ColorTemperature},` - + ` ColorTemperatureCached: ${this.accessory.context.ColorTemperature}`); + await this.debugLog(`No changes (pushColorTemperatureChanges), ColorTemperature: ${this.LightBulb.ColorTemperature}, ColorTemperatureCached: ${this.accessory.context.ColorTemperature}`) } } async pushBrightnessChanges(): Promise { - await this.debugLog('pushBrightnessChanges'); + await this.debugLog('pushBrightnessChanges') if (this.LightBulb.Brightness !== this.accessory.context.Brightness) { const bodyChange = JSON.stringify({ command: 'setBrightness', parameter: `${this.LightBulb.Brightness}`, commandType: 'command', - }); - await this.debugLog(`(pushBrightnessChanges) SwitchBot OpenAPI bodyChange: ${JSON.stringify(bodyChange)}`); + }) + await this.debugLog(`(pushBrightnessChanges) SwitchBot OpenAPI bodyChange: ${JSON.stringify(bodyChange)}`) try { - const { body, statusCode } = await this.pushChangeRequest(bodyChange); - const deviceStatus: any = await body.json(); - await this.debugLog(`(pushBrightnessChanges) statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`); + const { body, statusCode } = await this.pushChangeRequest(bodyChange) + const deviceStatus: any = await body.json() + await this.debugLog(`(pushBrightnessChanges) statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`) if (await this.successfulStatusCodes(statusCode, deviceStatus)) { - await this.debugSuccessLog(`(pushBrightnessChanges) statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`); - await this.updateHomeKitCharacteristics(); + await this.debugSuccessLog(`(pushBrightnessChanges) statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`) + await this.updateHomeKitCharacteristics() } else { - await this.statusCode(statusCode); - await this.statusCode(deviceStatus.statusCode); + await this.statusCode(statusCode) + await this.statusCode(deviceStatus.statusCode) } } catch (e: any) { - await this.apiError(e); - await this.errorLog(`failed pushBrightnessChanges with ${this.device.connectionType} Connection,` - + ` Error Message: ${JSON.stringify(e.message)}`); + await this.apiError(e) + await this.errorLog(`failed pushBrightnessChanges with ${this.device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`) } } else { - await this.debugLog(`No changes (pushBrightnessChanges), Brightness: ${this.LightBulb.Brightness},` - + ` BrightnessCached: ${this.accessory.context.Brightness}`); + await this.debugLog(`No changes (pushBrightnessChanges), Brightness: ${this.LightBulb.Brightness}, BrightnessCached: ${this.accessory.context.Brightness}`) } } @@ -583,13 +579,13 @@ export class CeilingLight extends deviceBase { */ async OnSet(value: CharacteristicValue): Promise { if (this.LightBulb.On !== this.accessory.context.On) { - await this.infoLog(`Set On: ${value}`); + await this.infoLog(`Set On: ${value}`) } else { - await this.debugLog(`No Changes, Set On: ${value}`); + await this.debugLog(`No Changes, Set On: ${value}`) } - this.LightBulb.On = value; - this.doCeilingLightUpdate.next(); + this.LightBulb.On = value + this.doCeilingLightUpdate.next() } /** @@ -597,16 +593,16 @@ export class CeilingLight extends deviceBase { */ async BrightnessSet(value: CharacteristicValue): Promise { if (this.LightBulb.On && (this.LightBulb.Brightness !== this.accessory.context.Brightness)) { - await this.infoLog(`Set Brightness: ${value}`); + await this.infoLog(`Set Brightness: ${value}`) } else { if (this.LightBulb.On) { - this.debugLog(`No Changes, Brightness: ${value}`); + this.debugLog(`No Changes, Brightness: ${value}`) } else { - this.debugLog(`Set Brightness: ${value}, On: ${this.LightBulb.On}`); + this.debugLog(`Set Brightness: ${value}, On: ${this.LightBulb.On}`) } } - this.LightBulb.Brightness = value; - this.doCeilingLightUpdate.next(); + this.LightBulb.Brightness = value + this.doCeilingLightUpdate.next() } /** @@ -614,34 +610,34 @@ export class CeilingLight extends deviceBase { */ async ColorTemperatureSet(value: CharacteristicValue): Promise { if (this.LightBulb.On && (this.LightBulb.ColorTemperature !== this.accessory.context.ColorTemperature)) { - this.infoLog(`Set ColorTemperature: ${value}`); + this.infoLog(`Set ColorTemperature: ${value}`) } else { if (this.LightBulb.On) { - this.debugLog(`No Changes, ColorTemperature: ${value}`); + this.debugLog(`No Changes, ColorTemperature: ${value}`) } else { - this.debugLog(`Set ColorTemperature: ${value}, On: ${this.LightBulb.On}`); + this.debugLog(`Set ColorTemperature: ${value}, On: ${this.LightBulb.On}`) } } - const minKelvin = 2000; - const maxKelvin = 9000; + const minKelvin = 2000 + const maxKelvin = 9000 // Convert mired to kelvin to nearest 100 (SwitchBot seems to need this) - const kelvin = Math.round(1000000 / Number(value) / 100) * 100; + const kelvin = Math.round(1000000 / Number(value) / 100) * 100 // Check and increase/decrease kelvin to range of device - const k = Math.min(Math.max(kelvin, minKelvin), maxKelvin); + const k = Math.min(Math.max(kelvin, minKelvin), maxKelvin) if (!this.accessory.context.On || this.accessory.context.kelvin === k) { - return; + return } // Updating the hue/sat to the corresponding values mimics native adaptive lighting - const hs = m2hs(value); - this.LightBulb.Service.updateCharacteristic(this.hap.Characteristic.Hue, hs[0]); - this.LightBulb.Service.updateCharacteristic(this.hap.Characteristic.Saturation, hs[1]); + const hs = m2hs(value) + this.LightBulb.Service.updateCharacteristic(this.hap.Characteristic.Hue, hs[0]) + this.LightBulb.Service.updateCharacteristic(this.hap.Characteristic.Saturation, hs[1]) - this.LightBulb.ColorTemperature = value; - this.doCeilingLightUpdate.next(); + this.LightBulb.ColorTemperature = value + this.doCeilingLightUpdate.next() } /** @@ -649,19 +645,19 @@ export class CeilingLight extends deviceBase { */ async HueSet(value: CharacteristicValue): Promise { if (this.LightBulb.On && (this.LightBulb.Hue !== this.accessory.context.Hue)) { - this.infoLog(`Set Hue: ${value}`); + this.infoLog(`Set Hue: ${value}`) } else { if (this.LightBulb.On) { - this.debugLog(`No Changes, Hue: ${value}`); + this.debugLog(`No Changes, Hue: ${value}`) } else { - this.debugLog(`Set Hue: ${value}, On: ${this.LightBulb.On}`); + this.debugLog(`Set Hue: ${value}, On: ${this.LightBulb.On}`) } } - this.LightBulb.Service.updateCharacteristic(this.hap.Characteristic.ColorTemperature, 140); + this.LightBulb.Service.updateCharacteristic(this.hap.Characteristic.ColorTemperature, 140) - this.LightBulb.Hue = value; - this.doCeilingLightUpdate.next(); + this.LightBulb.Hue = value + this.doCeilingLightUpdate.next() } /** @@ -669,79 +665,74 @@ export class CeilingLight extends deviceBase { */ async SaturationSet(value: CharacteristicValue): Promise { if (this.LightBulb.On && (this.LightBulb.Saturation !== this.accessory.context.Saturation)) { - this.infoLog(`Set Saturation: ${value}`); + this.infoLog(`Set Saturation: ${value}`) } else { if (this.LightBulb.On) { - this.debugLog(`No Changes, Saturation: ${value}`); + this.debugLog(`No Changes, Saturation: ${value}`) } else { - this.debugLog(`Set Saturation: ${value}, On: ${this.LightBulb.On}`); + this.debugLog(`Set Saturation: ${value}, On: ${this.LightBulb.On}`) } } - this.LightBulb.Service.updateCharacteristic(this.hap.Characteristic.ColorTemperature, 140); + this.LightBulb.Service.updateCharacteristic(this.hap.Characteristic.ColorTemperature, 140) - this.LightBulb.Saturation = value; - this.doCeilingLightUpdate.next(); + this.LightBulb.Saturation = value + this.doCeilingLightUpdate.next() } async updateHomeKitCharacteristics(): Promise { // On - await this.updateCharacteristic(this.LightBulb.Service, this.hap.Characteristic.On, - this.LightBulb.On, 'On'); + await this.updateCharacteristic(this.LightBulb.Service, this.hap.Characteristic.On, this.LightBulb.On, 'On') // Brightness - await this.updateCharacteristic(this.LightBulb.Service, this.hap.Characteristic.Brightness, - this.LightBulb.Brightness, 'Brightness'); + await this.updateCharacteristic(this.LightBulb.Service, this.hap.Characteristic.Brightness, this.LightBulb.Brightness, 'Brightness') // ColorTemperature - await this.updateCharacteristic(this.LightBulb.Service, this.hap.Characteristic.ColorTemperature, - this.LightBulb.ColorTemperature, 'ColorTemperature'); + await this.updateCharacteristic(this.LightBulb.Service, this.hap.Characteristic.ColorTemperature, this.LightBulb.ColorTemperature, 'ColorTemperature') // Hue - await this.updateCharacteristic(this.LightBulb.Service, this.hap.Characteristic.Hue, - this.LightBulb.Hue, 'Hue'); + await this.updateCharacteristic(this.LightBulb.Service, this.hap.Characteristic.Hue, this.LightBulb.Hue, 'Hue') // Saturation - await this.updateCharacteristic(this.LightBulb.Service, this.hap.Characteristic.Saturation, - this.LightBulb.Saturation, 'Saturation'); + await this.updateCharacteristic(this.LightBulb.Service, this.hap.Characteristic.Saturation, this.LightBulb.Saturation, 'Saturation') } async getAdaptiveLightingSettings(accessory: PlatformAccessory, device: device & devicesConfig): Promise { // Adaptive Lighting - this.adaptiveLighting = accessory.context.adaptiveLighting ?? true; - await this.debugLog(`adaptiveLighting: ${this.adaptiveLighting}`); + this.adaptiveLighting = accessory.context.adaptiveLighting ?? true + await this.debugLog(`adaptiveLighting: ${this.adaptiveLighting}`) // Adaptive Lighting Shift if (device.ceilinglight?.adaptiveLightingShift) { - this.adaptiveLightingShift = device.ceilinglight.adaptiveLightingShift; - this.debugLog(`adaptiveLightingShift: ${this.adaptiveLightingShift}`); + this.adaptiveLightingShift = device.ceilinglight.adaptiveLightingShift + this.debugLog(`adaptiveLightingShift: ${this.adaptiveLightingShift}`) } else { - this.adaptiveLightingShift = 0; - this.debugLog(`adaptiveLightingShift: ${this.adaptiveLightingShift}`); + this.adaptiveLightingShift = 0 + this.debugLog(`adaptiveLightingShift: ${this.adaptiveLightingShift}`) } } async BLEPushConnection() { if (this.platform.config.credentials?.token && this.device.connectionType === 'BLE/OpenAPI') { - await this.warnLog('Using OpenAPI Connection to Push Changes'); - await this.openAPIpushChanges(); + await this.warnLog('Using OpenAPI Connection to Push Changes') + await this.openAPIpushChanges() } } async BLERefreshConnection(switchbot: any): Promise { - await this.errorLog(`wasn't able to establish BLE Connection, node-switchbot: ${switchbot}`); + await this.errorLog(`wasn't able to establish BLE Connection, node-switchbot: ${switchbot}`) if (this.platform.config.credentials?.token && this.device.connectionType === 'BLE/OpenAPI') { - await this.warnLog('Using OpenAPI Connection to Refresh Status'); - await this.openAPIRefreshStatus(); + await this.warnLog('Using OpenAPI Connection to Refresh Status') + await this.openAPIRefreshStatus() } } async offlineOff(): Promise { if (this.device.offline) { - this.LightBulb.Service.updateCharacteristic(this.hap.Characteristic.On, false); + this.LightBulb.Service.updateCharacteristic(this.hap.Characteristic.On, false) } } async apiError(e: any): Promise { - this.LightBulb.Service.updateCharacteristic(this.hap.Characteristic.On, e); - this.LightBulb.Service.updateCharacteristic(this.hap.Characteristic.Hue, e); - this.LightBulb.Service.updateCharacteristic(this.hap.Characteristic.Brightness, e); - this.LightBulb.Service.updateCharacteristic(this.hap.Characteristic.Saturation, e); - this.LightBulb.Service.updateCharacteristic(this.hap.Characteristic.ColorTemperature, e); + this.LightBulb.Service.updateCharacteristic(this.hap.Characteristic.On, e) + this.LightBulb.Service.updateCharacteristic(this.hap.Characteristic.Hue, e) + this.LightBulb.Service.updateCharacteristic(this.hap.Characteristic.Brightness, e) + this.LightBulb.Service.updateCharacteristic(this.hap.Characteristic.Saturation, e) + this.LightBulb.Service.updateCharacteristic(this.hap.Characteristic.ColorTemperature, e) } } diff --git a/src/device/colorbulb.ts b/src/device/colorbulb.ts index 7b242d4d..33e1862b 100644 --- a/src/device/colorbulb.ts +++ b/src/device/colorbulb.ts @@ -2,18 +2,24 @@ * * blindtilt.ts: @switchbot/homebridge-switchbot. */ -import { deviceBase } from './device.js'; -import { hs2rgb, rgb2hs, m2hs } from '../utils.js'; -import { SwitchBotBLEModel, SwitchBotBLEModelName } from 'node-switchbot'; -import { Subject, debounceTime, interval, skipWhile, take, tap } from 'rxjs'; - -import type { devicesConfig } from '../settings.js'; -import type { device } from '../types/devicelist.js'; -import type { SwitchBotPlatform } from '../platform.js'; -import type { colorBulbServiceData } from '../types/bledevicestatus.js'; -import type { colorBulbStatus } from '../types/devicestatus.js'; -import type { colorBulbWebhookContext } from '../types/devicewebhookstatus.js'; -import type { Service, PlatformAccessory, CharacteristicValue, ControllerConstructor, Controller, ControllerServiceMap } from 'homebridge'; +import type { CharacteristicValue, Controller, ControllerConstructor, ControllerServiceMap, PlatformAccessory, Service } from 'homebridge' + +import type { SwitchBotPlatform } from '../platform.js' +import type { devicesConfig } from '../settings.js' +import type { colorBulbServiceData } from '../types/bledevicestatus.js' +import type { device } from '../types/devicelist.js' +import type { colorBulbStatus } from '../types/devicestatus.js' +import type { colorBulbWebhookContext } from '../types/devicewebhookstatus.js' + +/* +* For Testing Locally: +* import { SwitchBotBLEModel, SwitchBotBLEModelName } from '/Users/Shared/GitHub/OpenWonderLabs/node-switchbot/dist/index.js'; +*/ +import { SwitchBotBLEModel, SwitchBotBLEModelName } from 'node-switchbot' +import { debounceTime, interval, skipWhile, Subject, take, tap } from 'rxjs' + +import { hs2rgb, m2hs, rgb2hs } from '../utils.js' +import { deviceBase } from './device.js' /** * Platform Accessory @@ -23,51 +29,51 @@ import type { Service, PlatformAccessory, CharacteristicValue, ControllerConstru export class ColorBulb extends deviceBase { // Services private LightBulb: { - Name: CharacteristicValue; - Service: Service; - On: CharacteristicValue; - Hue: CharacteristicValue; - Saturation: CharacteristicValue; - Brightness: CharacteristicValue; - ColorTemperature?: CharacteristicValue; - }; + Name: CharacteristicValue + Service: Service + On: CharacteristicValue + Hue: CharacteristicValue + Saturation: CharacteristicValue + Brightness: CharacteristicValue + ColorTemperature?: CharacteristicValue + } // OpenAPI - deviceStatus!: colorBulbStatus; + deviceStatus!: colorBulbStatus - //Webhook - webhookContext!: colorBulbWebhookContext; + // Webhook + webhookContext!: colorBulbWebhookContext // BLE - serviceData!: colorBulbServiceData; + serviceData!: colorBulbServiceData // Adaptive Lighting - adaptiveLighting!: boolean; - adaptiveLightingShift!: number; - AdaptiveLightingController?: ControllerConstructor | Controller; + adaptiveLighting!: boolean + adaptiveLightingShift!: number + AdaptiveLightingController?: ControllerConstructor | Controller // Updates - colorBulbUpdateInProgress!: boolean; - doColorBulbUpdate!: Subject; + colorBulbUpdateInProgress!: boolean + doColorBulbUpdate!: Subject constructor( readonly platform: SwitchBotPlatform, accessory: PlatformAccessory, device: device & devicesConfig, ) { - super(platform, accessory, device); + super(platform, accessory, device) // Set category - accessory.category = this.hap.Categories.LIGHTBULB; + accessory.category = this.hap.Categories.LIGHTBULB // default placeholders - this.getAdaptiveLightingSettings(accessory, device); + this.getAdaptiveLightingSettings(accessory, device) // this is subject we use to track when we need to POST changes to the SwitchBot API - this.doColorBulbUpdate = new Subject(); - this.colorBulbUpdateInProgress = false; + this.doColorBulbUpdate = new Subject() + this.colorBulbUpdateInProgress = false // Initialize LightBulb property - accessory.context.LightBulb = accessory.context.LightBulb ?? {}; + accessory.context.LightBulb = accessory.context.LightBulb ?? {} this.LightBulb = { Name: accessory.displayName, Service: accessory.getService(this.hap.Service.Lightbulb) ?? accessory.addService(this.hap.Service.Lightbulb) as Service, @@ -76,218 +82,212 @@ export class ColorBulb extends deviceBase { Saturation: accessory.context.Saturation ?? 0, Brightness: accessory.context.Brightness ?? 0, ColorTemperature: accessory.context.ColorTemperature ?? 140, - }; - accessory.context.LightBulb = this.LightBulb as object; + } + accessory.context.LightBulb = this.LightBulb as object if (this.adaptiveLighting && this.adaptiveLightingShift === -1 && this.LightBulb) { - accessory.removeService(this.LightBulb.Service); - this.LightBulb.Service = accessory.addService(this.hap.Service.Lightbulb); - accessory.context.adaptiveLighting = false; - this.debugLog(`adaptiveLighting: ${this.adaptiveLighting}`); + accessory.removeService(this.LightBulb.Service) + this.LightBulb.Service = accessory.addService(this.hap.Service.Lightbulb) + accessory.context.adaptiveLighting = false + this.debugLog(`adaptiveLighting: ${this.adaptiveLighting}`) } else if (this.adaptiveLighting && this.adaptiveLightingShift >= 0 && this.LightBulb) { this.AdaptiveLightingController = new platform.api.hap.AdaptiveLightingController(this.LightBulb.Service, { controllerMode: this.hap.AdaptiveLightingControllerMode.AUTOMATIC, customTemperatureAdjustment: this.adaptiveLightingShift, - }); - accessory.configureController(this.AdaptiveLightingController); - accessory.context.adaptiveLighting = true; + }) + accessory.configureController(this.AdaptiveLightingController) + accessory.context.adaptiveLighting = true this.debugLog(`adaptiveLighting: ${this.adaptiveLighting}, adaptiveLightingShift: ${this.adaptiveLightingShift}`, - ); + ) } else { - accessory.context.adaptiveLighting = false; - this.debugLog(`adaptiveLighting: ${accessory.context.adaptiveLighting}`); + accessory.context.adaptiveLighting = false + this.debugLog(`adaptiveLighting: ${accessory.context.adaptiveLighting}`) } // Initialize LightBulb Characteristics - this.LightBulb.Service - .setCharacteristic(this.hap.Characteristic.Name, this.LightBulb.Name) - .getCharacteristic(this.hap.Characteristic.On) - .onGet(() => { - return this.LightBulb.On; - }) - .onSet(this.OnSet.bind(this)); - - this.LightBulb.Service - .getCharacteristic(this.hap.Characteristic.Brightness) - .setProps({ - minStep: device.colorbulb?.set_minStep ?? 1, - minValue: 0, - maxValue: 100, - validValueRanges: [0, 100], - }) - .onGet(() => { - return this.LightBulb.Brightness; - }) - .onSet(this.BrightnessSet.bind(this)); - - this.LightBulb.Service - .getCharacteristic(this.hap.Characteristic.ColorTemperature) - .setProps({ - minValue: 140, - maxValue: 500, - validValueRanges: [140, 500], - }) - .onGet(() => { - return this.LightBulb.ColorTemperature!; - }) - .onSet(this.ColorTemperatureSet.bind(this)); - - this.LightBulb.Service - .getCharacteristic(this.hap.Characteristic.Hue) - .setProps({ - minValue: 0, - maxValue: 360, - validValueRanges: [0, 360], - }) - .onGet(() => { - return this.LightBulb.Hue; - }) - .onSet(this.HueSet.bind(this)); - - this.LightBulb.Service - .getCharacteristic(this.hap.Characteristic.Saturation) - .setProps({ - minValue: 0, - maxValue: 100, - validValueRanges: [0, 100], - }) - .onGet(() => { - return this.LightBulb.Saturation; - }) - .onSet(this.SaturationSet.bind(this)); + this.LightBulb.Service.setCharacteristic(this.hap.Characteristic.Name, this.LightBulb.Name).getCharacteristic(this.hap.Characteristic.On).onGet(() => { + return this.LightBulb.On + }).onSet(this.OnSet.bind(this)) + + this.LightBulb.Service.getCharacteristic(this.hap.Characteristic.Brightness).setProps({ + minStep: device.colorbulb?.set_minStep ?? 1, + minValue: 0, + maxValue: 100, + validValueRanges: [0, 100], + }).onGet(() => { + return this.LightBulb.Brightness + }).onSet(this.BrightnessSet.bind(this)) + + this.LightBulb.Service.getCharacteristic(this.hap.Characteristic.ColorTemperature).setProps({ + minValue: 140, + maxValue: 500, + validValueRanges: [140, 500], + }).onGet(() => { + return this.LightBulb.ColorTemperature! + }).onSet(this.ColorTemperatureSet.bind(this)) + + this.LightBulb.Service.getCharacteristic(this.hap.Characteristic.Hue).setProps({ + minValue: 0, + maxValue: 360, + validValueRanges: [0, 360], + }).onGet(() => { + return this.LightBulb.Hue + }).onSet(this.HueSet.bind(this)) + + this.LightBulb.Service.getCharacteristic(this.hap.Characteristic.Saturation).setProps({ + minValue: 0, + maxValue: 100, + validValueRanges: [0, 100], + }).onGet(() => { + return this.LightBulb.Saturation + }).onSet(this.SaturationSet.bind(this)) // Retrieve initial values and updateHomekit - this.debugLog('Retrieve initial values and update Homekit'); - this.refreshStatus(); + try { + this.debugLog('Retrieve initial values and update Homekit') + this.refreshStatus() + } catch (e: any) { + this.errorLog(`failed to retrieve initial values and update Homekit, Error: ${e}`) + } - //regisiter webhook event handler - this.debugLog('Registering Webhook Event Handler'); - this.registerWebhook(); + // regisiter webhook event handler if enabled + try { + this.debugLog('Registering Webhook Event Handler') + this.registerWebhook() + } catch (e: any) { + this.errorLog(`failed to registerWebhook, Error: ${e}`) + } + + // regisiter platform BLE event handler if enabled + try { + this.debugLog('Registering Platform BLE Event Handler') + this.registerPlatformBLE() + } catch (e: any) { + this.errorLog(`failed to registerPlatformBLE, Error: ${e}`) + } // Start an update interval interval(this.deviceRefreshRate * 1000) .pipe(skipWhile(() => this.colorBulbUpdateInProgress)) .subscribe(async () => { - await this.refreshStatus(); - }); + await this.refreshStatus() + }) // Watch for Bulb change events // We put in a debounce of 100ms so we don't make duplicate calls this.doColorBulbUpdate .pipe( tap(() => { - this.colorBulbUpdateInProgress = true; + this.colorBulbUpdateInProgress = true }), debounceTime(this.devicePushRate * 1000), ) .subscribe(async () => { try { - await this.pushChanges(); + await this.pushChanges() } catch (e: any) { - await this.apiError(e); - await this.errorLog(`failed pushChanges with ${device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`); + await this.apiError(e) + await this.errorLog(`failed pushChanges with ${device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`) } - this.colorBulbUpdateInProgress = false; - }); + this.colorBulbUpdateInProgress = false + }) } /** * Parse the device status from the SwitchBotBLE API */ async BLEparseStatus(): Promise { - await this.debugLog('BLEparseStatus'); + await this.debugLog('BLEparseStatus') // On - this.LightBulb.On = this.serviceData.power; - await this.debugLog(`On: ${this.LightBulb.On}`); + this.LightBulb.On = this.serviceData.power + await this.debugLog(`On: ${this.LightBulb.On}`) // Brightness - this.LightBulb.Brightness = this.serviceData.brightness; - await this.debugLog(`Brightness: ${this.LightBulb.Brightness}`); + this.LightBulb.Brightness = this.serviceData.brightness + await this.debugLog(`Brightness: ${this.LightBulb.Brightness}`) // Color, Hue & Brightness - await this.debugLog(`red: ${this.serviceData.red}, green: ${this.serviceData.green}, blue: ${this.serviceData.blue}`); - const [hue, saturation] = rgb2hs(this.serviceData.red, this.serviceData.green, this.serviceData.blue); - await this.debugLog(`hs: ${JSON.stringify(rgb2hs(this.serviceData.red, this.serviceData.green, this.serviceData.blue))}`); + await this.debugLog(`red: ${this.serviceData.red}, green: ${this.serviceData.green}, blue: ${this.serviceData.blue}`) + const [hue, saturation] = rgb2hs(this.serviceData.red, this.serviceData.green, this.serviceData.blue) + await this.debugLog(`hs: ${JSON.stringify(rgb2hs(this.serviceData.red, this.serviceData.green, this.serviceData.blue))}`) // Hue - this.LightBulb.Hue = hue; - await this.debugLog(`Hue: ${this.LightBulb.Hue}`); + this.LightBulb.Hue = hue + await this.debugLog(`Hue: ${this.LightBulb.Hue}`) // Saturation - this.LightBulb.Saturation = saturation; - await this.debugLog(`Saturation: ${this.LightBulb.Saturation}`); + this.LightBulb.Saturation = saturation + await this.debugLog(`Saturation: ${this.LightBulb.Saturation}`) // ColorTemperature - const miredColorTemperature = Math.round(1000000 / this.serviceData.color_temperature); - this.LightBulb.ColorTemperature = Math.max(Math.min(miredColorTemperature, 500), 140); - await this.debugLog(`ColorTemperature: ${this.LightBulb.ColorTemperature}`); + const miredColorTemperature = Math.round(1000000 / this.serviceData.color_temperature) + this.LightBulb.ColorTemperature = Math.max(Math.min(miredColorTemperature, 500), 140) + await this.debugLog(`ColorTemperature: ${this.LightBulb.ColorTemperature}`) } /** * Parse the device status from the SwitchBot OpenAPI */ async openAPIparseStatus(): Promise { - await this.debugLog('openAPIparseStatus'); + await this.debugLog('openAPIparseStatus') // On - this.LightBulb.On = this.deviceStatus.power === 'on' ? true : false; - await this.debugLog(`On: ${this.LightBulb.On}`); + this.LightBulb.On = this.deviceStatus.power === 'on' + await this.debugLog(`On: ${this.LightBulb.On}`) // Brightness - this.LightBulb.Brightness = this.deviceStatus.brightness; - await this.debugLog(`Brightness: ${this.LightBulb.Brightness}`); + this.LightBulb.Brightness = this.deviceStatus.brightness + await this.debugLog(`Brightness: ${this.LightBulb.Brightness}`) // Color, Hue & Brightness - await this.debugLog(`color: ${JSON.stringify(this.deviceStatus.color)}`); - const [red, green, blue] = this.deviceStatus.color.split(':'); - await this.debugLog(`red: ${JSON.stringify(red)}, green: ${JSON.stringify(green)}, blue: ${JSON.stringify(blue)}`); - const [hue, saturation] = rgb2hs(red, green, blue); - await this.debugLog(`hs: ${JSON.stringify(rgb2hs(red, green, blue))}`); + await this.debugLog(`color: ${JSON.stringify(this.deviceStatus.color)}`) + const [red, green, blue] = this.deviceStatus.color.split(':') + await this.debugLog(`red: ${JSON.stringify(red)}, green: ${JSON.stringify(green)}, blue: ${JSON.stringify(blue)}`) + const [hue, saturation] = rgb2hs(red, green, blue) + await this.debugLog(`hs: ${JSON.stringify(rgb2hs(red, green, blue))}`) // Hue - this.LightBulb.Hue = hue; - await this.debugLog(`Hue: ${this.LightBulb.Hue}`); + this.LightBulb.Hue = hue + await this.debugLog(`Hue: ${this.LightBulb.Hue}`) // Saturation - this.LightBulb.Saturation = saturation; - await this.debugLog(`Saturation: ${this.LightBulb.Saturation}`); + this.LightBulb.Saturation = saturation + await this.debugLog(`Saturation: ${this.LightBulb.Saturation}`) // ColorTemperature - const miredColorTemperature = Math.round(1000000 / this.deviceStatus.colorTemperature); - this.LightBulb.ColorTemperature = Math.max(Math.min(miredColorTemperature, 500), 140); - await this.debugLog(`ColorTemperature: ${this.LightBulb.ColorTemperature}`); + const miredColorTemperature = Math.round(1000000 / this.deviceStatus.colorTemperature) + this.LightBulb.ColorTemperature = Math.max(Math.min(miredColorTemperature, 500), 140) + await this.debugLog(`ColorTemperature: ${this.LightBulb.ColorTemperature}`) // Firmware Version if (this.deviceStatus.version) { - const version = this.deviceStatus.version.toString(); - await this.debugLog(`Firmware Version: ${version.replace(/^V|-.*$/g, '')}`); - const deviceVersion = version.replace(/^V|-.*$/g, '') ?? '0.0.0'; + const version = this.deviceStatus.version.toString() + await this.debugLog(`Firmware Version: ${version.replace(/^V|-.*$/g, '')}`) + const deviceVersion = version.replace(/^V|-.*$/g, '') ?? '0.0.0' this.accessory .getService(this.hap.Service.AccessoryInformation)! .setCharacteristic(this.hap.Characteristic.HardwareRevision, deviceVersion) .setCharacteristic(this.hap.Characteristic.FirmwareRevision, deviceVersion) .getCharacteristic(this.hap.Characteristic.FirmwareRevision) - .updateValue(deviceVersion); - this.accessory.context.version = deviceVersion; - await this.debugSuccessLog(`version: ${this.accessory.context.version}`); + .updateValue(deviceVersion) + this.accessory.context.version = deviceVersion + await this.debugSuccessLog(`version: ${this.accessory.context.version}`) } } async parseStatusWebhook(): Promise { - await this.debugLog('parseStatusWebhook'); - await this.debugLog(`(powerState, brightness, color, colorTemperature) = Webhook:(${this.webhookContext.powerState},` - + ` ${this.webhookContext.brightness}, ${this.webhookContext.color}, ${this.webhookContext.colorTemperature}), current:(${this.LightBulb.On},` - + ` ${this.LightBulb.Brightness}, ${this.LightBulb.Hue}, ${this.LightBulb.Saturation}, ${this.LightBulb.ColorTemperature})`); + await this.debugLog('parseStatusWebhook') + await this.debugLog(`(powerState, brightness, color, colorTemperature) = Webhook:(${this.webhookContext.powerState}, ${this.webhookContext.brightness}, ${this.webhookContext.color}, ${this.webhookContext.colorTemperature}), current:(${this.LightBulb.On}, ${this.LightBulb.Brightness}, ${this.LightBulb.Hue}, ${this.LightBulb.Saturation}, ${this.LightBulb.ColorTemperature})`) // On - this.LightBulb.On = this.webhookContext.powerState === 'ON' ? true : false; - await this.debugLog(`On: ${this.LightBulb.On}`); + this.LightBulb.On = this.webhookContext.powerState === 'ON' + await this.debugLog(`On: ${this.LightBulb.On}`) // Brightness - this.LightBulb.Brightness = this.webhookContext.brightness; - await this.debugLog(`Brightness: ${this.LightBulb.Brightness}`); + this.LightBulb.Brightness = this.webhookContext.brightness + await this.debugLog(`Brightness: ${this.LightBulb.Brightness}`) // Color, Hue & Brightness - await this.debugLog(`color: ${JSON.stringify(this.webhookContext.color)}`); - const [red, green, blue] = this.webhookContext.color.split(':'); - await this.debugLog(`red: ${JSON.stringify(red)}, green: ${JSON.stringify(green)}, blue: ${JSON.stringify(blue)}`); - const [hue, saturation] = rgb2hs(red, green, blue); - await this.debugLog(`hs: ${JSON.stringify(rgb2hs(red, green, blue))}`); + await this.debugLog(`color: ${JSON.stringify(this.webhookContext.color)}`) + const [red, green, blue] = this.webhookContext.color.split(':') + await this.debugLog(`red: ${JSON.stringify(red)}, green: ${JSON.stringify(green)}, blue: ${JSON.stringify(blue)}`) + const [hue, saturation] = rgb2hs(red, green, blue) + await this.debugLog(`hs: ${JSON.stringify(rgb2hs(red, green, blue))}`) // Hue - this.LightBulb.Hue = hue; - await this.debugLog(`Hue: ${this.LightBulb.Hue}`); + this.LightBulb.Hue = hue + await this.debugLog(`Hue: ${this.LightBulb.Hue}`) // Saturation - this.LightBulb.Saturation = saturation; - await this.debugLog(`Saturation: ${this.LightBulb.Saturation}`); + this.LightBulb.Saturation = saturation + await this.debugLog(`Saturation: ${this.LightBulb.Saturation}`) // ColorTemperature - const miredColorTemperature = Math.round(1000000 / this.webhookContext.colorTemperature); - this.LightBulb.ColorTemperature = Math.max(Math.min(miredColorTemperature, 500), 140); - await this.debugLog(`ColorTemperature: ${this.LightBulb.ColorTemperature}`); + const miredColorTemperature = Math.round(1000000 / this.webhookContext.colorTemperature) + this.LightBulb.ColorTemperature = Math.max(Math.min(miredColorTemperature, 500), 140) + await this.debugLog(`ColorTemperature: ${this.LightBulb.ColorTemperature}`) } /** @@ -295,418 +295,411 @@ export class ColorBulb extends deviceBase { */ async refreshStatus(): Promise { if (!this.device.enableCloudService && this.OpenAPI) { - await this.errorLog(`refreshStatus enableCloudService: ${this.device.enableCloudService}`); - } else if (this.BLE || this.config.options?.BLE) { - await this.BLERefreshStatus(); + await this.errorLog(`refreshStatus enableCloudService: ${this.device.enableCloudService}`) + } else if (this.BLE) { + await this.BLERefreshStatus() } else if (this.OpenAPI && this.platform.config.credentials?.token) { - await this.openAPIRefreshStatus(); + await this.openAPIRefreshStatus() } else { - await this.offlineOff(); - await this.debugWarnLog(`Connection Type: ${this.device.connectionType}, refreshStatus will not happen.`); + await this.offlineOff() + await this.debugWarnLog(`Connection Type: ${this.device.connectionType}, refreshStatus will not happen.`) } } async BLERefreshStatus(): Promise { - await this.debugLog('BLERefreshStatus'); + await this.debugLog('BLERefreshStatus') + const switchbot = await this.switchbotBLE() + if (switchbot === undefined) { + await this.BLERefreshConnection(switchbot) + } else { + // Start to monitor advertisement packets + (async () => { + // Start to monitor advertisement packets + const serviceData = await this.monitorAdvertisementPackets(switchbot) as colorBulbServiceData + // Update HomeKit + if (serviceData.model === SwitchBotBLEModel.ColorBulb && serviceData.modelName === SwitchBotBLEModelName.ColorBulb) { + this.serviceData = serviceData + await this.BLEparseStatus() + await this.updateHomeKitCharacteristics() + } else { + await this.errorLog(`failed to get serviceData, serviceData: ${serviceData}`) + await this.BLERefreshConnection(switchbot) + } + })() + } + } + + async registerPlatformBLE(): Promise { + await this.debugLog('registerPlatformBLE') if (this.config.options?.BLE) { - await this.debugLog('is listening to Platform BLE.'); - this.device.bleMac = this.device.deviceId!.match(/.{1,2}/g)!.join(':').toLowerCase(); - await this.debugLog(`bleMac: ${this.device.bleMac}`); + await this.debugLog('is listening to Platform BLE.') + this.device.bleMac = this.device.deviceId!.match(/.{1,2}/g)!.join(':').toLowerCase() + await this.debugLog(`bleMac: ${this.device.bleMac}`) this.platform.bleEventHandler[this.device.bleMac] = async (context: colorBulbServiceData) => { try { - await this.debugLog(`received BLE: ${JSON.stringify(context)}`); - this.serviceData = context; - await this.BLEparseStatus(); - await this.updateHomeKitCharacteristics(); + await this.debugLog(`received BLE: ${JSON.stringify(context)}`) + this.serviceData = context + await this.BLEparseStatus() + await this.updateHomeKitCharacteristics() } catch (e: any) { - await this.errorLog(`failed to handle BLE. Received: ${JSON.stringify(context)} Error: ${e}`); + await this.errorLog(`failed to handle BLE. Received: ${JSON.stringify(context)} Error: ${e}`) } - }; - } else { - await this.debugLog('is using Device BLE Scanning.'); - const switchbot = await this.switchbotBLE(); - if (switchbot === undefined) { - await this.BLERefreshConnection(switchbot); - } else { - // Start to monitor advertisement packets - (async () => { - // Start to monitor advertisement packets - const serviceData = await this.monitorAdvertisementPackets(switchbot) as colorBulbServiceData; - // Update HomeKit - if (serviceData.model === SwitchBotBLEModel.ColorBulb && serviceData.modelName === SwitchBotBLEModelName.ColorBulb) { - this.serviceData = serviceData; - await this.BLEparseStatus(); - await this.updateHomeKitCharacteristics(); - } else { - await this.errorLog(`failed to get serviceData, serviceData: ${serviceData}`); - await this.BLERefreshConnection(switchbot); - } - })(); } + } else { + await this.debugLog('is not listening to Platform BLE') } } async openAPIRefreshStatus(): Promise { - await this.debugLog('openAPIRefreshStatus'); + await this.debugLog('openAPIRefreshStatus') try { - const { body, statusCode } = await this.deviceRefreshStatus(); - const deviceStatus: any = await body.json(); - await this.debugLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`);; + const { body, statusCode } = await this.deviceRefreshStatus() + const deviceStatus: any = await body.json() + await this.debugLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`) if (await this.successfulStatusCodes(statusCode, deviceStatus)) { - await this.debugSuccessLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`); - this.deviceStatus = deviceStatus.body; - await this.openAPIparseStatus(); - await this.updateHomeKitCharacteristics(); + await this.debugSuccessLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`) + this.deviceStatus = deviceStatus.body + await this.openAPIparseStatus() + await this.updateHomeKitCharacteristics() } else { - await this.debugWarnLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`); - await this.debugWarnLog(statusCode, deviceStatus); + await this.debugWarnLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`) + await this.debugWarnLog(statusCode, deviceStatus) } } catch (e: any) { - await this.apiError(e); - await this.errorLog(`failed openAPIRefreshStatus with ${this.device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`); + await this.apiError(e) + await this.errorLog(`failed openAPIRefreshStatus with ${this.device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`) } } async registerWebhook() { if (this.device.webhook) { - await this.debugLog('is listening webhook.'); + await this.debugLog('is listening webhook.') this.platform.webhookEventHandler[this.device.deviceId] = async (context: colorBulbWebhookContext) => { try { - await this.debugLog(`received Webhook: ${JSON.stringify(context)}`); - this.webhookContext = context; - await this.parseStatusWebhook(); - await this.updateHomeKitCharacteristics(); + await this.debugLog(`received Webhook: ${JSON.stringify(context)}`) + this.webhookContext = context + await this.parseStatusWebhook() + await this.updateHomeKitCharacteristics() } catch (e: any) { - await this.errorLog(`failed to handle webhook. Received: ${JSON.stringify(context)} Error: ${e}`); + await this.errorLog(`failed to handle webhook. Received: ${JSON.stringify(context)} Error: ${e}`) } - }; + } } else { - await this.debugLog('is not listening webhook.'); + await this.debugLog('is not listening webhook.') } } /** * Pushes the requested changes to the SwitchBot API - * deviceType commandType Command command parameter Description - * Color Bulb - "command" "turnOff" "default" = set to OFF state - * Color Bulb - "command" "turnOn" "default" = set to ON state - * Color Bulb - "command" "toggle" "default" = toggle state - * Color Bulb - "command" "setBrightness" "{1-100}" = set brightness - * Color Bulb - "command" "setColor" "{0-255}:{0-255}:{0-255}" = set RGB color value - * Color Bulb - "command" "setColorTemperature" "{2700-6500}" = set color temperature + * deviceType commandType Command command parameter Description + * Color Bulb - "command" "turnOff" "default" = set to OFF state + * Color Bulb - "command" "turnOn" "default" = set to ON state + * Color Bulb - "command" "toggle" "default" = toggle state + * Color Bulb - "command" "setBrightness" "{1-100}" = set brightness + * Color Bulb - "command" "setColor" "{0-255}:{0-255}:{0-255}" = set RGB color value + * Color Bulb - "command" "setColorTemperature" "{2700-6500}" = set color temperature * */ async pushChanges(): Promise { if (!this.device.enableCloudService && this.OpenAPI) { - await this.errorLog(`pushChanges enableCloudService: ${this.device.enableCloudService}`); + await this.errorLog(`pushChanges enableCloudService: ${this.device.enableCloudService}`) } else if (this.BLE) { - await this.BLEpushChanges(); + await this.BLEpushChanges() if (this.LightBulb.On) { - // Push Brightness Update - await this.debugLog(`Brightness: ${this.LightBulb.Brightness}`); - await this.BLEpushBrightnessChanges(); + // Push Brightness Update + await this.debugLog(`Brightness: ${this.LightBulb.Brightness}`) + await this.BLEpushBrightnessChanges() // Push ColorTemperature Update - await this.debugLog(`ColorTemperature: ${this.LightBulb.ColorTemperature}`); - await this.BLEpushColorTemperatureChanges(); + await this.debugLog(`ColorTemperature: ${this.LightBulb.ColorTemperature}`) + await this.BLEpushColorTemperatureChanges() // Push Hue & Saturation Update - await this.debugLog(`Hue: ${this.LightBulb.Hue}, Saturation: ${this.LightBulb.Saturation}`); - await this.BLEpushRGBChanges(); + await this.debugLog(`Hue: ${this.LightBulb.Hue}, Saturation: ${this.LightBulb.Saturation}`) + await this.BLEpushRGBChanges() } else { - await this.debugLog('BLE (Brightness), (ColorTemperature), (Hue), & (Saturation) changes will not happen, as the device is OFF.'); + await this.debugLog('BLE (Brightness), (ColorTemperature), (Hue), & (Saturation) changes will not happen, as the device is OFF.') } } else if (this.OpenAPI && this.platform.config.credentials?.token) { - await this.openAPIpushChanges(); + await this.openAPIpushChanges() if (this.LightBulb.On) { // Push Brightness Update - await this.debugLog(`Brightness: ${this.LightBulb.Brightness}`); - await this.pushBrightnessChanges(); + await this.debugLog(`Brightness: ${this.LightBulb.Brightness}`) + await this.pushBrightnessChanges() // Push ColorTemperature Update - await this.debugLog(`ColorTemperature: ${this.LightBulb.ColorTemperature}`); - await this.pushColorTemperatureChanges(); + await this.debugLog(`ColorTemperature: ${this.LightBulb.ColorTemperature}`) + await this.pushColorTemperatureChanges() // Push Hue & Saturation Update - await this.debugLog(`Hue: ${this.LightBulb.Hue}, Saturation: ${this.LightBulb.Saturation}`); - await this.pushHueSaturationChanges(); + await this.debugLog(`Hue: ${this.LightBulb.Hue}, Saturation: ${this.LightBulb.Saturation}`) + await this.pushHueSaturationChanges() } else { - await this.debugLog('openAPI (Brightness), (ColorTemperature), (Hue), & (Saturation) changes will not happen, as the device is OFF.'); + await this.debugLog('openAPI (Brightness), (ColorTemperature), (Hue), & (Saturation) changes will not happen, as the device is OFF.') } } else { - await this.offlineOff(); - await this.debugWarnLog(`Connection Type: ${this.device.connectionType}, pushChanges will not happen.`); + await this.offlineOff() + await this.debugWarnLog(`Connection Type: ${this.device.connectionType}, pushChanges will not happen.`) } // Refresh the status from the API interval(15000) .pipe(skipWhile(() => this.colorBulbUpdateInProgress)) .pipe(take(1)) .subscribe(async () => { - await this.refreshStatus(); - }); + await this.refreshStatus() + }) } async BLEpushChanges(): Promise { - await this.debugLog('BLEpushChanges'); + await this.debugLog('BLEpushChanges') if (this.LightBulb.On !== this.accessory.context.On) { - await this.debugLog(`BLEpushChanges On: ${this.LightBulb.On}, OnCached: ${this.accessory.context.On}`); - const switchbot = await this.platform.connectBLE(this.accessory, this.device); - await this.convertBLEAddress(); + await this.debugLog(`BLEpushChanges On: ${this.LightBulb.On}, OnCached: ${this.accessory.context.On}`) + const switchbot = await this.platform.connectBLE(this.accessory, this.device) + await this.convertBLEAddress() if (switchbot !== false) { switchbot .discover({ model: this.device.bleModel, id: this.device.bleMac }) .then(async (device_list: any) => { - await this.infoLog(`On: ${this.LightBulb.On}`); + await this.infoLog(`On: ${this.LightBulb.On}`) return await this.retryBLE({ max: await this.maxRetryBLE(), fn: async () => { if (this.LightBulb.On) { - return await device_list[0].turnOn({ id: this.device.bleMac }); + return await device_list[0].turnOn({ id: this.device.bleMac }) } else { - return await device_list[0].turnOff({ id: this.device.bleMac }); + return await device_list[0].turnOff({ id: this.device.bleMac }) } }, - }); + }) }) .then(async () => { - await this.successLog(`On: ${this.LightBulb.On} sent over SwitchBot BLE, sent successfully`); - await this.updateHomeKitCharacteristics(); + await this.successLog(`On: ${this.LightBulb.On} sent over SwitchBot BLE, sent successfully`) + await this.updateHomeKitCharacteristics() }) .catch(async (e: any) => { - await this.apiError(e); - await this.errorLog(`failed BLEpushChanges with ${this.device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`); - await this.BLEPushConnection(); - }); + await this.apiError(e) + await this.errorLog(`failed BLEpushChanges with ${this.device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`) + await this.BLEPushConnection() + }) } else { - await this.errorLog(`wasn't able to establish BLE Connection, node-switchbot: ${switchbot}`); - await this.BLEPushConnection(); + await this.errorLog(`wasn't able to establish BLE Connection, node-switchbot: ${switchbot}`) + await this.BLEPushConnection() } } else { - await this.debugLog(`No changes (BLEpushChanges), On: ${this.LightBulb.On}, OnCached: ${this.accessory.context.On}`); + await this.debugLog(`No changes (BLEpushChanges), On: ${this.LightBulb.On}, OnCached: ${this.accessory.context.On}`) } } async BLEpushBrightnessChanges(): Promise { - await this.debugLog('BLEpushBrightnessChanges'); + await this.debugLog('BLEpushBrightnessChanges') if (this.LightBulb.Brightness !== this.accessory.context.Brightness) { - const switchbot = await this.platform.connectBLE(this.accessory, this.device); - await this.convertBLEAddress(); + const switchbot = await this.platform.connectBLE(this.accessory, this.device) + await this.convertBLEAddress() if (switchbot !== false) { switchbot .discover({ model: this.device.bleModel, id: this.device.bleMac }) .then(async (device_list: any) => { - await this.infoLog(`Target Brightness: ${this.LightBulb.Brightness}`); - return await device_list[0].setBrightness(this.LightBulb.Brightness); + await this.infoLog(`Target Brightness: ${this.LightBulb.Brightness}`) + return await device_list[0].setBrightness(this.LightBulb.Brightness) }) .then(async () => { - await this.successLog(`Brightness: ${this.LightBulb.Brightness} sent over SwitchBot BLE, sent successfully`); - await this.updateHomeKitCharacteristics(); + await this.successLog(`Brightness: ${this.LightBulb.Brightness} sent over SwitchBot BLE, sent successfully`) + await this.updateHomeKitCharacteristics() }) .catch(async (e: any) => { - await this.apiError(e); - await this.errorLog(`failed BLEpushChanges with ${this.device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`); - await this.BLEPushConnection(); - }); + await this.apiError(e) + await this.errorLog(`failed BLEpushChanges with ${this.device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`) + await this.BLEPushConnection() + }) } else { - await this.errorLog(`wasn't able to establish BLE Connection, node-switchbot: ${switchbot}`); - await this.BLEPushConnection(); + await this.errorLog(`wasn't able to establish BLE Connection, node-switchbot: ${switchbot}`) + await this.BLEPushConnection() } } else { - await this.debugLog(`No changes (BLEpushBrightnessChanges), Brightness: ${this.LightBulb.Brightness},` - + ` BrightnessCached: ${this.accessory.context.Brightness}`); + await this.debugLog(`No changes (BLEpushBrightnessChanges), Brightness: ${this.LightBulb.Brightness}, BrightnessCached: ${this.accessory.context.Brightness}`) } } async BLEpushColorTemperatureChanges(): Promise { - await this.debugLog('BLEpushColorTemperatureChanges'); + await this.debugLog('BLEpushColorTemperatureChanges') if (this.LightBulb.ColorTemperature !== this.accessory.context.ColorTemperature) { - const kelvin = Math.round(1000000 / Number(this.LightBulb.ColorTemperature)); - this.accessory.context.kelvin = kelvin; - const switchbot = await this.platform.connectBLE(this.accessory, this.device); - await this.convertBLEAddress(); + const kelvin = Math.round(1000000 / Number(this.LightBulb.ColorTemperature)) + this.accessory.context.kelvin = kelvin + const switchbot = await this.platform.connectBLE(this.accessory, this.device) + await this.convertBLEAddress() if (switchbot !== false) { switchbot .discover({ model: this.device.bleModel, id: this.device.bleMac }) .then(async (device_list: any) => { - await this.infoLog(`ColorTemperature: ${this.LightBulb.ColorTemperature}`); - return await device_list[0].setColorTemperature(kelvin); + await this.infoLog(`ColorTemperature: ${this.LightBulb.ColorTemperature}`) + return await device_list[0].setColorTemperature(kelvin) }) .then(async () => { - await this.successLog(`ColorTemperature: ${this.LightBulb.ColorTemperature} sent over SwitchBot BLE, sent successfully`); - await this.updateHomeKitCharacteristics(); + await this.successLog(`ColorTemperature: ${this.LightBulb.ColorTemperature} sent over SwitchBot BLE, sent successfully`) + await this.updateHomeKitCharacteristics() }) .catch(async (e: any) => { - await this.apiError(e); - await this.errorLog(`failed BLEpushRGBChanges with ${this.device.connectionType} Connection,` - + ` Error Message: ${JSON.stringify(e.message)}`); - await this.BLEPushConnection(); - }); + await this.apiError(e) + await this.errorLog(`failed BLEpushRGBChanges with ${this.device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`) + await this.BLEPushConnection() + }) } else { - await this.errorLog(`wasn't able to establish BLE Connection, node-switchbot: ${switchbot}`); - await this.BLEPushConnection(); + await this.errorLog(`wasn't able to establish BLE Connection, node-switchbot: ${switchbot}`) + await this.BLEPushConnection() } } else { - await this.debugLog(`No changes (BLEpushColorTemperatureChanges), ColorTemperature: ${this.LightBulb.ColorTemperature},` - + ` ColorTemperatureCached: ${this.accessory.context.ColorTemperature}`); + await this.debugLog(`No changes (BLEpushColorTemperatureChanges), ColorTemperature: ${this.LightBulb.ColorTemperature}, ColorTemperatureCached: ${this.accessory.context.ColorTemperature}`) } } async BLEpushRGBChanges(): Promise { - await this.debugLog('BLEpushRGBChanges'); + await this.debugLog('BLEpushRGBChanges') if ((this.LightBulb.Hue !== this.accessory.context.Hue) || (this.LightBulb.Saturation !== this.accessory.context.Saturation)) { - await this.debugLog(`Hue: ${JSON.stringify(this.LightBulb.Hue)}, Saturation: ${JSON.stringify(this.LightBulb.Saturation)}`); - const [red, green, blue] = hs2rgb(this.LightBulb.Hue, this.LightBulb.Saturation); - await this.debugLog(`rgb: ${JSON.stringify([red, green, blue])}`); - const switchbot = await this.platform.connectBLE(this.accessory, this.device); - await this.convertBLEAddress(); + await this.debugLog(`Hue: ${JSON.stringify(this.LightBulb.Hue)}, Saturation: ${JSON.stringify(this.LightBulb.Saturation)}`) + const [red, green, blue] = hs2rgb(this.LightBulb.Hue, this.LightBulb.Saturation) + await this.debugLog(`rgb: ${JSON.stringify([red, green, blue])}`) + const switchbot = await this.platform.connectBLE(this.accessory, this.device) + await this.convertBLEAddress() if (switchbot !== false) { switchbot .discover({ model: this.device.bleModel, id: this.device.bleMac }) .then(async (device_list: any) => { - await this.infoLog(`RGB: ${(this.LightBulb.Brightness, red, green, blue)}`); - return await device_list[0].setRGB(this.LightBulb.Brightness, red, green, blue); + await this.infoLog(`RGB: ${(this.LightBulb.Brightness, red, green, blue)}`) + return await device_list[0].setRGB(this.LightBulb.Brightness, red, green, blue) }) .then(async () => { - await this.successLog(`RGB: ${(this.LightBulb.Brightness, red, green, blue)} sent over SwitchBot BLE, sent successfully`); - await this.updateHomeKitCharacteristics(); + await this.successLog(`RGB: ${(this.LightBulb.Brightness, red, green, blue)} sent over SwitchBot BLE, sent successfully`) + await this.updateHomeKitCharacteristics() }) .catch(async (e: any) => { - await this.apiError(e); - await this.errorLog(`failed BLEpushRGBChanges with ${this.device.connectionType} Connection,` - + ` Error Message: ${JSON.stringify(e.message)}`); - await this.BLEPushConnection(); - }); + await this.apiError(e) + await this.errorLog(`failed BLEpushRGBChanges with ${this.device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`) + await this.BLEPushConnection() + }) } else { - await this.errorLog(`wasn't able to establish BLE Connection, node-switchbot: ${switchbot}`); - await this.BLEPushConnection(); + await this.errorLog(`wasn't able to establish BLE Connection, node-switchbot: ${switchbot}`) + await this.BLEPushConnection() } } else { - await this.debugLog(`No changes (BLEpushRGBChanges), Hue: ${this.LightBulb.Hue}, HueCached: ${this.accessory.context.Hue},` - + ` Saturation: ${this.LightBulb.Saturation}, SaturationCached: ${this.accessory.context.Saturation}`); + await this.debugLog(`No changes (BLEpushRGBChanges), Hue: ${this.LightBulb.Hue}, HueCached: ${this.accessory.context.Hue}, Saturation: ${this.LightBulb.Saturation}, SaturationCached: ${this.accessory.context.Saturation}`) } } async openAPIpushChanges(): Promise { - await this.debugLog('openAPIpushChanges'); + await this.debugLog('openAPIpushChanges') if (this.LightBulb.On !== this.accessory.context.On) { - const command = this.LightBulb.On ? 'turnOn' : 'turnOff'; + const command = this.LightBulb.On ? 'turnOn' : 'turnOff' const bodyChange = JSON.stringify({ command: `${command}`, parameter: 'default', commandType: 'command', - }); - await this.debugLog(`SwitchBot OpenAPI bodyChange: ${JSON.stringify(bodyChange)}`); + }) + await this.debugLog(`SwitchBot OpenAPI bodyChange: ${JSON.stringify(bodyChange)}`) try { - const { body, statusCode } = await this.pushChangeRequest(bodyChange); - const deviceStatus: any = await body.json(); - await this.debugLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`); + const { body, statusCode } = await this.pushChangeRequest(bodyChange) + const deviceStatus: any = await body.json() + await this.debugLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`) if (await this.successfulStatusCodes(statusCode, deviceStatus)) { - await this.debugSuccessLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`); - await this.updateHomeKitCharacteristics(); + await this.debugSuccessLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`) + await this.updateHomeKitCharacteristics() } else { - await this.statusCode(statusCode); - await this.statusCode(deviceStatus.statusCode); + await this.statusCode(statusCode) + await this.statusCode(deviceStatus.statusCode) } } catch (e: any) { - await this.apiError(e); - await this.errorLog(`failed openAPIpushChanges with ${this.device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`); + await this.apiError(e) + await this.errorLog(`failed openAPIpushChanges with ${this.device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`) } } else { - await this.debugLog(`No changes (openAPIpushChanges), On: ${this.LightBulb.On}, OnCached: ${this.accessory.context.On}`); + await this.debugLog(`No changes (openAPIpushChanges), On: ${this.LightBulb.On}, OnCached: ${this.accessory.context.On}`) } } async pushHueSaturationChanges(): Promise { - await this.debugLog('pushHueSaturationChanges'); + await this.debugLog('pushHueSaturationChanges') if ((this.LightBulb.Hue !== this.accessory.context.Hue) || (this.LightBulb.Saturation !== this.accessory.context.Saturation)) { - await this.debugLog(`Hue: ${JSON.stringify(this.LightBulb.Hue)}, Saturation: ${JSON.stringify(this.LightBulb.Saturation)}`); - const [red, green, blue] = hs2rgb(this.LightBulb.Hue, this.LightBulb.Saturation); - await this.debugLog(`rgb: ${JSON.stringify([red, green, blue])}`); + await this.debugLog(`Hue: ${JSON.stringify(this.LightBulb.Hue)}, Saturation: ${JSON.stringify(this.LightBulb.Saturation)}`) + const [red, green, blue] = hs2rgb(this.LightBulb.Hue, this.LightBulb.Saturation) + await this.debugLog(`rgb: ${JSON.stringify([red, green, blue])}`) const bodyChange = JSON.stringify({ command: 'setColor', parameter: `${red}:${green}:${blue}`, commandType: 'command', - }); - await this.debugLog(`SwitchBot OpenAPI bodyChange: ${JSON.stringify(bodyChange)}`); + }) + await this.debugLog(`SwitchBot OpenAPI bodyChange: ${JSON.stringify(bodyChange)}`) try { - const { body, statusCode } = await this.pushChangeRequest(bodyChange); - const deviceStatus: any = await body.json(); - await this.debugLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`); + const { body, statusCode } = await this.pushChangeRequest(bodyChange) + const deviceStatus: any = await body.json() + await this.debugLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`) if (await this.successfulStatusCodes(statusCode, deviceStatus)) { - await this.debugSuccessLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`); - await this.updateHomeKitCharacteristics(); + await this.debugSuccessLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`) + await this.updateHomeKitCharacteristics() } else { - await this.statusCode(statusCode); - await this.statusCode(deviceStatus.statusCode); + await this.statusCode(statusCode) + await this.statusCode(deviceStatus.statusCode) } } catch (e: any) { - await this.apiError(e); - await this.errorLog(`failed pushHueSaturationChanges with ${this.device.connectionType} Connection,` - + ` Error Message: ${JSON.stringify(e.message)}`); + await this.apiError(e) + await this.errorLog(`failed pushHueSaturationChanges with ${this.device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`) } } else { - await this.debugLog(`No changes (pushHueSaturationChanges), Hue: ${this.LightBulb.Hue}, HueCached: ${this.accessory.context.Hue},` - + ` Saturation: ${this.LightBulb.Saturation}, SaturationCached: ${this.accessory.context.Saturation}`); + await this.debugLog(`No changes (pushHueSaturationChanges), Hue: ${this.LightBulb.Hue}, HueCached: ${this.accessory.context.Hue}, Saturation: ${this.LightBulb.Saturation}, SaturationCached: ${this.accessory.context.Saturation}`) } } async pushColorTemperatureChanges(): Promise { - await this.debugLog('pushColorTemperatureChanges'); + await this.debugLog('pushColorTemperatureChanges') if (this.LightBulb.ColorTemperature !== this.accessory.context.ColorTemperature) { - const kelvin = Math.round(1000000 / Number(this.LightBulb.ColorTemperature)); - this.accessory.context.kelvin = kelvin; + const kelvin = Math.round(1000000 / Number(this.LightBulb.ColorTemperature)) + this.accessory.context.kelvin = kelvin const bodyChange = JSON.stringify({ command: 'setColorTemperature', parameter: `${kelvin}`, commandType: 'command', - }); - await this.debugLog(`SwitchBot OpenAPI bodyChange: ${JSON.stringify(bodyChange)}`); + }) + await this.debugLog(`SwitchBot OpenAPI bodyChange: ${JSON.stringify(bodyChange)}`) try { - const { body, statusCode } = await this.pushChangeRequest(bodyChange); - const deviceStatus: any = await body.json(); - await this.debugLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`); + const { body, statusCode } = await this.pushChangeRequest(bodyChange) + const deviceStatus: any = await body.json() + await this.debugLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`) if (await this.successfulStatusCodes(statusCode, deviceStatus)) { - await this.debugSuccessLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`); - await this.updateHomeKitCharacteristics(); + await this.debugSuccessLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`) + await this.updateHomeKitCharacteristics() } else { - await this.statusCode(statusCode); - await this.statusCode(deviceStatus.statusCode); + await this.statusCode(statusCode) + await this.statusCode(deviceStatus.statusCode) } } catch (e: any) { - await this.apiError(e); - await this.errorLog(`failed pushColorTemperatureChanges with ${this.device.connectionType} Connection,` - + ` Error Message: ${JSON.stringify(e.message)}`); + await this.apiError(e) + await this.errorLog(`failed pushColorTemperatureChanges with ${this.device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`) } } else { - await this.debugLog(`No changes (pushColorTemperatureChanges), ColorTemperature: ${this.LightBulb.ColorTemperature},` - + ` ColorTemperatureCached: ${this.accessory.context.ColorTemperature}`); + await this.debugLog(`No changes (pushColorTemperatureChanges), ColorTemperature: ${this.LightBulb.ColorTemperature}, ColorTemperatureCached: ${this.accessory.context.ColorTemperature}`) } } async pushBrightnessChanges(): Promise { - await this.debugLog('pushBrightnessChanges'); + await this.debugLog('pushBrightnessChanges') if (this.LightBulb.Brightness !== this.accessory.context.Brightness) { const bodyChange = JSON.stringify({ command: 'setBrightness', parameter: `${this.LightBulb.Brightness}`, commandType: 'command', - }); - await this.debugLog(`SwitchBot OpenAPI bodyChange: ${JSON.stringify(bodyChange)}`); + }) + await this.debugLog(`SwitchBot OpenAPI bodyChange: ${JSON.stringify(bodyChange)}`) try { - const { body, statusCode } = await this.pushChangeRequest(bodyChange); - const deviceStatus: any = await body.json(); - await this.debugLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`); + const { body, statusCode } = await this.pushChangeRequest(bodyChange) + const deviceStatus: any = await body.json() + await this.debugLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`) if (await this.successfulStatusCodes(statusCode, deviceStatus)) { - await this.debugSuccessLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`); - await this.updateHomeKitCharacteristics(); + await this.debugSuccessLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`) + await this.updateHomeKitCharacteristics() } else { - await this.statusCode(statusCode); - await this.statusCode(deviceStatus.statusCode); + await this.statusCode(statusCode) + await this.statusCode(deviceStatus.statusCode) } } catch (e: any) { - await this.apiError(e); - await this.errorLog(`failed pushBrightnessChanges with ${this.device.connectionType} Connection,` - + ` Error Message: ${JSON.stringify(e.message)}`); + await this.apiError(e) + await this.errorLog(`failed pushBrightnessChanges with ${this.device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`) } } else { - await this.debugLog(`No changes (pushBrightnessChanges), Brightness: ${this.LightBulb.Brightness},` - + ` BrightnessCached: ${this.accessory.context.Brightness}`); + await this.debugLog(`No changes (pushBrightnessChanges), Brightness: ${this.LightBulb.Brightness}, BrightnessCached: ${this.accessory.context.Brightness}`) } } @@ -715,13 +708,13 @@ export class ColorBulb extends deviceBase { */ async OnSet(value: CharacteristicValue): Promise { if (this.LightBulb.On !== this.accessory.context.On) { - await this.infoLog(`Set On: ${value}`); + await this.infoLog(`Set On: ${value}`) } else { - await this.debugLog(`No Changes, On: ${value}`); + await this.debugLog(`No Changes, On: ${value}`) } - this.LightBulb.On = value; - this.doColorBulbUpdate.next(); + this.LightBulb.On = value + this.doColorBulbUpdate.next() } /** @@ -729,17 +722,17 @@ export class ColorBulb extends deviceBase { */ async BrightnessSet(value: CharacteristicValue): Promise { if (this.LightBulb.On && (this.LightBulb.Brightness !== this.accessory.context.Brightness)) { - await this.infoLog(`Set Brightness: ${value}`); + await this.infoLog(`Set Brightness: ${value}`) } else { if (this.LightBulb.On) { - this.debugLog(`No Changes, Brightness: ${value}`); + this.debugLog(`No Changes, Brightness: ${value}`) } else { - this.debugLog(`Brightness: ${value}, On: ${this.LightBulb.On}`); + this.debugLog(`Brightness: ${value}, On: ${this.LightBulb.On}`) } } - this.LightBulb.Brightness = value; - this.doColorBulbUpdate.next(); + this.LightBulb.Brightness = value + this.doColorBulbUpdate.next() } /** @@ -747,34 +740,34 @@ export class ColorBulb extends deviceBase { */ async ColorTemperatureSet(value: CharacteristicValue): Promise { if (this.LightBulb.On && (this.LightBulb.ColorTemperature !== this.accessory.context.ColorTemperature)) { - this.infoLog(`Set ColorTemperature: ${value}`); + this.infoLog(`Set ColorTemperature: ${value}`) } else { if (this.LightBulb.On) { - this.debugLog(`No Changes, ColorTemperature: ${value}`); + this.debugLog(`No Changes, ColorTemperature: ${value}`) } else { - this.debugLog(`Set ColorTemperature: ${value}, On: ${this.LightBulb.On}`); + this.debugLog(`Set ColorTemperature: ${value}, On: ${this.LightBulb.On}`) } } - const minKelvin = 2000; - const maxKelvin = 9000; + const minKelvin = 2000 + const maxKelvin = 9000 // Convert mired to kelvin to nearest 100 (SwitchBot seems to need this) - const kelvin = Math.round(1000000 / Number(value) / 100) * 100; + const kelvin = Math.round(1000000 / Number(value) / 100) * 100 // Check and increase/decrease kelvin to range of device - const k = Math.min(Math.max(kelvin, minKelvin), maxKelvin); + const k = Math.min(Math.max(kelvin, minKelvin), maxKelvin) if (!this.accessory.context.On || this.accessory.context.kelvin === k) { - return; + return } // Updating the hue/sat to the corresponding values mimics native adaptive lighting - const hs = m2hs(value); - this.LightBulb.Service.updateCharacteristic(this.hap.Characteristic.Hue, hs[0]); - this.LightBulb.Service.updateCharacteristic(this.hap.Characteristic.Saturation, hs[1]); + const hs = m2hs(value) + this.LightBulb.Service.updateCharacteristic(this.hap.Characteristic.Hue, hs[0]) + this.LightBulb.Service.updateCharacteristic(this.hap.Characteristic.Saturation, hs[1]) - this.LightBulb.ColorTemperature = value; - this.doColorBulbUpdate.next(); + this.LightBulb.ColorTemperature = value + this.doColorBulbUpdate.next() } /** @@ -782,19 +775,19 @@ export class ColorBulb extends deviceBase { */ async HueSet(value: CharacteristicValue): Promise { if (this.LightBulb.On && (this.LightBulb.Hue !== this.accessory.context.Hue)) { - this.infoLog(`Set Hue: ${value}`); + this.infoLog(`Set Hue: ${value}`) } else { if (this.LightBulb.On) { - this.debugLog(`No Changes, Hue: ${value}`); + this.debugLog(`No Changes, Hue: ${value}`) } else { - this.debugLog(`Set Hue: ${value}, On: ${this.LightBulb.On}`); + this.debugLog(`Set Hue: ${value}, On: ${this.LightBulb.On}`) } } - this.LightBulb.Service.updateCharacteristic(this.hap.Characteristic.ColorTemperature, 140); + this.LightBulb.Service.updateCharacteristic(this.hap.Characteristic.ColorTemperature, 140) - this.LightBulb.Hue = value; - this.doColorBulbUpdate.next(); + this.LightBulb.Hue = value + this.doColorBulbUpdate.next() } /** @@ -802,79 +795,74 @@ export class ColorBulb extends deviceBase { */ async SaturationSet(value: CharacteristicValue): Promise { if (this.LightBulb.On && (this.LightBulb.Saturation !== this.accessory.context.Saturation)) { - this.infoLog(`Set Saturation: ${value}`); + this.infoLog(`Set Saturation: ${value}`) } else { if (this.LightBulb.On) { - this.debugLog(`No Changes, Saturation: ${value}`); + this.debugLog(`No Changes, Saturation: ${value}`) } else { - this.debugLog(`Set Saturation: ${value}, On: ${this.LightBulb.On}`); + this.debugLog(`Set Saturation: ${value}, On: ${this.LightBulb.On}`) } } - this.LightBulb.Service.updateCharacteristic(this.hap.Characteristic.ColorTemperature, 140); + this.LightBulb.Service.updateCharacteristic(this.hap.Characteristic.ColorTemperature, 140) - this.LightBulb.Saturation = value; - this.doColorBulbUpdate.next(); + this.LightBulb.Saturation = value + this.doColorBulbUpdate.next() } async updateHomeKitCharacteristics(): Promise { // On - await this.updateCharacteristic(this.LightBulb.Service, this.hap.Characteristic.On, - this.LightBulb.On, 'On'); + await this.updateCharacteristic(this.LightBulb.Service, this.hap.Characteristic.On, this.LightBulb.On, 'On') // Brightness - await this.updateCharacteristic(this.LightBulb.Service, this.hap.Characteristic.Brightness, - this.LightBulb.Brightness, 'Brightness'); + await this.updateCharacteristic(this.LightBulb.Service, this.hap.Characteristic.Brightness, this.LightBulb.Brightness, 'Brightness') // ColorTemperature - await this.updateCharacteristic(this.LightBulb.Service, this.hap.Characteristic.ColorTemperature, - this.LightBulb.ColorTemperature, 'ColorTemperature'); + await this.updateCharacteristic(this.LightBulb.Service, this.hap.Characteristic.ColorTemperature, this.LightBulb.ColorTemperature, 'ColorTemperature') // Hue - await this.updateCharacteristic(this.LightBulb.Service, this.hap.Characteristic.Hue, - this.LightBulb.Hue, 'Hue'); + await this.updateCharacteristic(this.LightBulb.Service, this.hap.Characteristic.Hue, this.LightBulb.Hue, 'Hue') // Saturation - await this.updateCharacteristic(this.LightBulb.Service, this.hap.Characteristic.Saturation, - this.LightBulb.Saturation, 'Saturation'); + await this.updateCharacteristic(this.LightBulb.Service, this.hap.Characteristic.Saturation, this.LightBulb.Saturation, 'Saturation') } async getAdaptiveLightingSettings(accessory: PlatformAccessory, device: device & devicesConfig): Promise { // Adaptive Lighting - this.adaptiveLighting = accessory.context.adaptiveLighting ?? true; - await this.debugLog(`adaptiveLighting: ${this.adaptiveLighting}`); + this.adaptiveLighting = accessory.context.adaptiveLighting ?? true + await this.debugLog(`adaptiveLighting: ${this.adaptiveLighting}`) // Adaptive Lighting Shift if (device.colorbulb?.adaptiveLightingShift) { - this.adaptiveLightingShift = device.colorbulb.adaptiveLightingShift; - this.debugLog(`adaptiveLightingShift: ${this.adaptiveLightingShift}`); + this.adaptiveLightingShift = device.colorbulb.adaptiveLightingShift + this.debugLog(`adaptiveLightingShift: ${this.adaptiveLightingShift}`) } else { - this.adaptiveLightingShift = 0; - this.debugLog(`adaptiveLightingShift: ${this.adaptiveLightingShift}`); + this.adaptiveLightingShift = 0 + this.debugLog(`adaptiveLightingShift: ${this.adaptiveLightingShift}`) } } async BLEPushConnection() { if (this.platform.config.credentials?.token && this.device.connectionType === 'BLE/OpenAPI') { - await this.warnLog('Using OpenAPI Connection to Push Changes'); - await this.openAPIpushChanges(); + await this.warnLog('Using OpenAPI Connection to Push Changes') + await this.openAPIpushChanges() } } async BLERefreshConnection(switchbot: any): Promise { - await this.errorLog(`wasn't able to establish BLE Connection, node-switchbot: ${switchbot}`); + await this.errorLog(`wasn't able to establish BLE Connection, node-switchbot: ${switchbot}`) if (this.platform.config.credentials?.token && this.device.connectionType === 'BLE/OpenAPI') { - await this.warnLog('Using OpenAPI Connection to Refresh Status'); - await this.openAPIRefreshStatus(); + await this.warnLog('Using OpenAPI Connection to Refresh Status') + await this.openAPIRefreshStatus() } } async offlineOff(): Promise { if (this.device.offline) { - this.LightBulb.Service.updateCharacteristic(this.hap.Characteristic.On, false); + this.LightBulb.Service.updateCharacteristic(this.hap.Characteristic.On, false) } } async apiError(e: any): Promise { - this.LightBulb.Service.updateCharacteristic(this.hap.Characteristic.On, e); - this.LightBulb.Service.updateCharacteristic(this.hap.Characteristic.Hue, e); - this.LightBulb.Service.updateCharacteristic(this.hap.Characteristic.Brightness, e); - this.LightBulb.Service.updateCharacteristic(this.hap.Characteristic.Saturation, e); - this.LightBulb.Service.updateCharacteristic(this.hap.Characteristic.ColorTemperature, e); + this.LightBulb.Service.updateCharacteristic(this.hap.Characteristic.On, e) + this.LightBulb.Service.updateCharacteristic(this.hap.Characteristic.Hue, e) + this.LightBulb.Service.updateCharacteristic(this.hap.Characteristic.Brightness, e) + this.LightBulb.Service.updateCharacteristic(this.hap.Characteristic.Saturation, e) + this.LightBulb.Service.updateCharacteristic(this.hap.Characteristic.ColorTemperature, e) } } diff --git a/src/device/contact.ts b/src/device/contact.ts index a7e66d6b..a03c5beb 100644 --- a/src/device/contact.ts +++ b/src/device/contact.ts @@ -2,17 +2,23 @@ * * contact.ts: @switchbot/homebridge-switchbot. */ -import { deviceBase } from './device.js'; -import { Subject, interval, skipWhile } from 'rxjs'; -import { SwitchBotBLEModel, SwitchBotBLEModelName } from 'node-switchbot'; - -import type { devicesConfig } from '../settings.js'; -import type { device } from '../types/devicelist.js'; -import type { SwitchBotPlatform } from '../platform.js'; -import type { contactSensorServiceData } from '../types/bledevicestatus.js'; -import type { Service, PlatformAccessory, CharacteristicValue } from 'homebridge'; -import type { contactSensorStatus } from '../types/devicestatus.js'; -import type { contactSensorWebhookContext } from '../types/devicewebhookstatus.js'; +import type { CharacteristicValue, PlatformAccessory, Service } from 'homebridge' + +import type { SwitchBotPlatform } from '../platform.js' +import type { devicesConfig } from '../settings.js' +import type { contactSensorServiceData } from '../types/bledevicestatus.js' +import type { device } from '../types/devicelist.js' +import type { contactSensorStatus } from '../types/devicestatus.js' +import type { contactSensorWebhookContext } from '../types/devicewebhookstatus.js' + +/* +* For Testing Locally: +* import { SwitchBotBLEModel, SwitchBotBLEModelName } from '/Users/Shared/GitHub/OpenWonderLabs/node-switchbot/dist/index.js'; +*/ +import { SwitchBotBLEModel, SwitchBotBLEModelName } from 'node-switchbot' +import { interval, skipWhile, Subject } from 'rxjs' + +import { deviceBase } from './device.js' /** * Platform Accessory @@ -22,259 +28,258 @@ import type { contactSensorWebhookContext } from '../types/devicewebhookstatus.j export class Contact extends deviceBase { // Services private ContactSensor: { - Name: CharacteristicValue; - Service: Service; - ContactSensorState: CharacteristicValue; - }; + Name: CharacteristicValue + Service: Service + ContactSensorState: CharacteristicValue + } private Battery: { - Name: CharacteristicValue; - Service: Service; - BatteryLevel: CharacteristicValue; - StatusLowBattery: CharacteristicValue; - }; + Name: CharacteristicValue + Service: Service + BatteryLevel: CharacteristicValue + StatusLowBattery: CharacteristicValue + } private MotionSensor?: { - Name: CharacteristicValue; - Service: Service; - MotionDetected: CharacteristicValue; - }; + Name: CharacteristicValue + Service: Service + MotionDetected: CharacteristicValue + } private LightSensor?: { - Name: CharacteristicValue; - Service: Service; - CurrentAmbientLightLevel: CharacteristicValue; - }; + Name: CharacteristicValue + Service: Service + CurrentAmbientLightLevel: CharacteristicValue + } // OpenAPI - deviceStatus!: contactSensorStatus; + deviceStatus!: contactSensorStatus - //Webhook - webhookContext!: contactSensorWebhookContext; + // Webhook + webhookContext!: contactSensorWebhookContext // BLE - serviceData!: contactSensorServiceData; + serviceData!: contactSensorServiceData // Updates - contactUpdateInProgress!: boolean; - doContactUpdate!: Subject; + contactUpdateInProgress!: boolean + doContactUpdate!: Subject constructor( readonly platform: SwitchBotPlatform, accessory: PlatformAccessory, device: device & devicesConfig, ) { - super(platform, accessory, device); + super(platform, accessory, device) // Set category - accessory.category = this.hap.Categories.SENSOR; + accessory.category = this.hap.Categories.SENSOR // this is subject we use to track when we need to POST changes to the SwitchBot API - this.doContactUpdate = new Subject(); - this.contactUpdateInProgress = false; + this.doContactUpdate = new Subject() + this.contactUpdateInProgress = false // Initialize Contact Sensor Service - accessory.context.ContactSensor = accessory.context.ContactSensor ?? {}; + accessory.context.ContactSensor = accessory.context.ContactSensor ?? {} this.ContactSensor = { Name: accessory.displayName, Service: accessory.getService(this.hap.Service.ContactSensor) ?? accessory.addService(this.hap.Service.ContactSensor) as Service, ContactSensorState: accessory.context.ContactSensorState ?? this.hap.Characteristic.ContactSensorState.CONTACT_DETECTED, - }; - accessory.context.ContactSensor = this.ContactSensor as object; + } + accessory.context.ContactSensor = this.ContactSensor as object // Initialize ContactSensor Characteristics - this.ContactSensor.Service - .setCharacteristic(this.hap.Characteristic.Name, this.ContactSensor.Name) - .setCharacteristic(this.hap.Characteristic.StatusActive, true) - .getCharacteristic(this.hap.Characteristic.ContactSensorState) - .onGet(() => { - return this.ContactSensor.ContactSensorState; - }); + this.ContactSensor.Service.setCharacteristic(this.hap.Characteristic.Name, this.ContactSensor.Name).setCharacteristic(this.hap.Characteristic.StatusActive, true).getCharacteristic(this.hap.Characteristic.ContactSensorState).onGet(() => { + return this.ContactSensor.ContactSensorState + }) // Initialize Battery Service - accessory.context.Battery = accessory.context.Battery ?? {}; + accessory.context.Battery = accessory.context.Battery ?? {} this.Battery = { Name: `${accessory.displayName} Battery`, Service: accessory.getService(this.hap.Service.Battery) ?? accessory.addService(this.hap.Service.Battery) as Service, BatteryLevel: accessory.context.BatteryLevel ?? 100, StatusLowBattery: accessory.context.StatusLowBattery ?? this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL, - }; - accessory.context.Battery = this.Battery as object; + } + accessory.context.Battery = this.Battery as object // Initialize Battery Characteristics - this.Battery.Service - .setCharacteristic(this.hap.Characteristic.Name, this.Battery.Name) - .setCharacteristic(this.hap.Characteristic.ChargingState, this.hap.Characteristic.ChargingState.NOT_CHARGEABLE) - .getCharacteristic(this.hap.Characteristic.BatteryLevel) - .onGet(() => { - return this.Battery.BatteryLevel; - }); - - this.Battery.Service - .setCharacteristic(this.hap.Characteristic.StatusLowBattery, this.Battery.StatusLowBattery) - .getCharacteristic(this.hap.Characteristic.StatusLowBattery) - .onGet(() => { - return this.Battery.StatusLowBattery; - }); + this.Battery.Service.setCharacteristic(this.hap.Characteristic.Name, this.Battery.Name).setCharacteristic(this.hap.Characteristic.ChargingState, this.hap.Characteristic.ChargingState.NOT_CHARGEABLE).getCharacteristic(this.hap.Characteristic.BatteryLevel).onGet(() => { + return this.Battery.BatteryLevel + }) + + this.Battery.Service.setCharacteristic(this.hap.Characteristic.StatusLowBattery, this.Battery.StatusLowBattery).getCharacteristic(this.hap.Characteristic.StatusLowBattery).onGet(() => { + return this.Battery.StatusLowBattery + }) // Initialize Motion Sensor Service if (this.device.contact?.hide_motionsensor) { if (this.MotionSensor) { - this.debugLog('Removing Motion Sensor Service'); - this.MotionSensor.Service = accessory.getService(this.hap.Service.MotionSensor) as Service; - accessory.removeService(this.MotionSensor.Service); + this.debugLog('Removing Motion Sensor Service') + this.MotionSensor.Service = accessory.getService(this.hap.Service.MotionSensor) as Service + accessory.removeService(this.MotionSensor.Service) } } else { - accessory.context.MotionSensor = accessory.context.MotionSensor ?? {}; + accessory.context.MotionSensor = accessory.context.MotionSensor ?? {} this.MotionSensor = { Name: `${accessory.displayName} Motion Sensor`, Service: accessory.getService(this.hap.Service.MotionSensor) ?? accessory.addService(this.hap.Service.MotionSensor) as Service, MotionDetected: accessory.context.MotionDetected ?? false, - }; - accessory.context.MotionSensor = this.MotionSensor as object; + } + accessory.context.MotionSensor = this.MotionSensor as object // Motion Sensor Characteristics - this.MotionSensor.Service - .setCharacteristic(this.hap.Characteristic.Name, this.MotionSensor.Name) - .setCharacteristic(this.hap.Characteristic.StatusActive, true) - .getCharacteristic(this.hap.Characteristic.MotionDetected) - .onGet(() => { - return this.MotionSensor!.MotionDetected; - }); + this.MotionSensor.Service.setCharacteristic(this.hap.Characteristic.Name, this.MotionSensor.Name).setCharacteristic(this.hap.Characteristic.StatusActive, true).getCharacteristic(this.hap.Characteristic.MotionDetected).onGet(() => { + return this.MotionSensor!.MotionDetected + }) } // Initialize Light Sensor Service if (device.contact?.hide_lightsensor) { if (this.LightSensor) { - this.debugLog('Removing Light Sensor Service'); - this.LightSensor.Service = accessory.getService(this.hap.Service.LightSensor) as Service; - accessory.removeService(this.LightSensor.Service); + this.debugLog('Removing Light Sensor Service') + this.LightSensor.Service = accessory.getService(this.hap.Service.LightSensor) as Service + accessory.removeService(this.LightSensor.Service) } } else { - accessory.context.LightSensor = accessory.context.LightSensor ?? {}; + accessory.context.LightSensor = accessory.context.LightSensor ?? {} this.LightSensor = { Name: `${accessory.displayName} Light Sensor`, Service: accessory.getService(this.hap.Service.LightSensor) ?? accessory.addService(this.hap.Service.LightSensor) as Service, CurrentAmbientLightLevel: accessory.context.CurrentAmbientLightLevel ?? 0.0001, - }; - accessory.context.LightSensor = this.LightSensor as object; + } + accessory.context.LightSensor = this.LightSensor as object // Light Sensor Characteristics - this.LightSensor.Service - .setCharacteristic(this.hap.Characteristic.Name, this.LightSensor.Name) - .setCharacteristic(this.hap.Characteristic.StatusActive, true) - .getCharacteristic(this.hap.Characteristic.CurrentAmbientLightLevel) - .onGet(() => { - return this.LightSensor!.CurrentAmbientLightLevel; - }); + this.LightSensor.Service.setCharacteristic(this.hap.Characteristic.Name, this.LightSensor.Name).setCharacteristic(this.hap.Characteristic.StatusActive, true).getCharacteristic(this.hap.Characteristic.CurrentAmbientLightLevel).onGet(() => { + return this.LightSensor!.CurrentAmbientLightLevel + }) } // Retrieve initial values and updateHomekit - this.debugLog('Retrieve initial values and update Homekit'); - this.refreshStatus(); + try { + this.debugLog('Retrieve initial values and update Homekit') + this.refreshStatus() + } catch (e: any) { + this.errorLog(`failed to retrieve initial values and update Homekit, Error: ${e}`) + } - //regisiter webhook event handler - this.debugLog('Registering Webhook Event Handler'); - this.registerWebhook(); + // regisiter webhook event handler if enabled + try { + this.debugLog('Registering Webhook Event Handler') + this.registerWebhook() + } catch (e: any) { + this.errorLog(`failed to registerWebhook, Error: ${e}`) + } + + // regisiter platform BLE event handler if enabled + try { + this.debugLog('Registering Platform BLE Event Handler') + this.registerPlatformBLE() + } catch (e: any) { + this.errorLog(`failed to registerPlatformBLE, Error: ${e}`) + } // Start an update interval interval(this.deviceRefreshRate * 1000) .pipe(skipWhile(() => this.contactUpdateInProgress)) .subscribe(async () => { - await this.refreshStatus(); - }); + await this.refreshStatus() + }) } async BLEparseStatus(): Promise { - await this.debugLog('BLEparseStatus'); + await this.debugLog('BLEparseStatus') // ContactSensorState this.ContactSensor.ContactSensorState = this.serviceData.doorState === 'open' - ? this.hap.Characteristic.ContactSensorState.CONTACT_NOT_DETECTED : this.hap.Characteristic.ContactSensorState.CONTACT_DETECTED; - await this.debugLog(`ContactSensorState: ${this.ContactSensor.ContactSensorState}`); + ? this.hap.Characteristic.ContactSensorState.CONTACT_NOT_DETECTED + : this.hap.Characteristic.ContactSensorState.CONTACT_DETECTED + await this.debugLog(`ContactSensorState: ${this.ContactSensor.ContactSensorState}`) // MotionDetected if (!this.device.contact?.hide_motionsensor && this.MotionSensor?.Service) { - this.MotionSensor.MotionDetected = this.serviceData.movement; - await this.debugLog(`MotionDetected: ${this.MotionSensor.MotionDetected}`); + this.MotionSensor.MotionDetected = this.serviceData.movement + await this.debugLog(`MotionDetected: ${this.MotionSensor.MotionDetected}`) } // CurrentAmbientLightLevel if (!this.device.contact?.hide_lightsensor && this.LightSensor?.Service) { - const set_minLux = this.device.blindTilt?.set_minLux ?? 1; - const set_maxLux = this.device.blindTilt?.set_maxLux ?? 6001; - const lightLevel = this.serviceData.lightLevel === 'bright' ? set_maxLux : set_minLux; - this.LightSensor.CurrentAmbientLightLevel = await this.getLightLevel(lightLevel, set_minLux, set_maxLux, 2); - await this.debugLog(`LightLevel: ${this.serviceData.lightLevel}, CurrentAmbientLightLevel: ${this.LightSensor.CurrentAmbientLightLevel}`); + const set_minLux = this.device.blindTilt?.set_minLux ?? 1 + const set_maxLux = this.device.blindTilt?.set_maxLux ?? 6001 + const lightLevel = this.serviceData.lightLevel === 'bright' ? set_maxLux : set_minLux + this.LightSensor.CurrentAmbientLightLevel = await this.getLightLevel(lightLevel, set_minLux, set_maxLux, 2) + await this.debugLog(`LightLevel: ${this.serviceData.lightLevel}, CurrentAmbientLightLevel: ${this.LightSensor.CurrentAmbientLightLevel}`) } // BatteryLevel - this.Battery.BatteryLevel = this.serviceData.battery; - await this.debugLog(`BatteryLevel: ${this.Battery.BatteryLevel}`); + this.Battery.BatteryLevel = this.serviceData.battery + await this.debugLog(`BatteryLevel: ${this.Battery.BatteryLevel}`) // StatusLowBattery this.Battery.StatusLowBattery = this.Battery.BatteryLevel < 10 - ? this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW : this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL; - await this.debugLog(`StatusLowBattery: ${this.Battery.StatusLowBattery}`); + ? this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW + : this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL + await this.debugLog(`StatusLowBattery: ${this.Battery.StatusLowBattery}`) } async openAPIparseStatus(): Promise { - await this.debugLog('openAPIparseStatus'); + await this.debugLog('openAPIparseStatus') // Contact State this.ContactSensor.ContactSensorState = this.deviceStatus.openState === 'open' - ? this.hap.Characteristic.ContactSensorState.CONTACT_NOT_DETECTED : this.hap.Characteristic.ContactSensorState.CONTACT_DETECTED; - await this.debugLog(`ContactSensorState: ${this.ContactSensor.ContactSensorState}`); + ? this.hap.Characteristic.ContactSensorState.CONTACT_NOT_DETECTED + : this.hap.Characteristic.ContactSensorState.CONTACT_DETECTED + await this.debugLog(`ContactSensorState: ${this.ContactSensor.ContactSensorState}`) // MotionDetected if (!this.device.contact?.hide_motionsensor && this.MotionSensor?.Service) { - this.MotionSensor.MotionDetected = this.deviceStatus.moveDetected; - await this.debugLog(`MotionDetected: ${this.MotionSensor.MotionDetected}`); + this.MotionSensor.MotionDetected = this.deviceStatus.moveDetected + await this.debugLog(`MotionDetected: ${this.MotionSensor.MotionDetected}`) } // Light Level if (!this.device.contact?.hide_lightsensor && this.LightSensor?.Service) { - const set_minLux = this.device.blindTilt?.set_minLux ?? 1; - const set_maxLux = this.device.blindTilt?.set_maxLux ?? 6001; - const lightLevel = this.deviceStatus.brightness === 'bright' ? set_maxLux : set_minLux; - this.LightSensor.CurrentAmbientLightLevel = await this.getLightLevel(lightLevel, set_minLux, set_maxLux, 2); - await this.debugLog(`LightLevel: ${this.deviceStatus.brightness}, CurrentAmbientLightLevel: ${this.LightSensor.CurrentAmbientLightLevel}`); + const set_minLux = this.device.blindTilt?.set_minLux ?? 1 + const set_maxLux = this.device.blindTilt?.set_maxLux ?? 6001 + const lightLevel = this.deviceStatus.brightness === 'bright' ? set_maxLux : set_minLux + this.LightSensor.CurrentAmbientLightLevel = await this.getLightLevel(lightLevel, set_minLux, set_maxLux, 2) + await this.debugLog(`LightLevel: ${this.deviceStatus.brightness}, CurrentAmbientLightLevel: ${this.LightSensor.CurrentAmbientLightLevel}`) } // BatteryLevel - this.Battery.BatteryLevel = this.deviceStatus.battery; - await this.debugLog(`BatteryLevel: ${this.Battery.BatteryLevel}`); + this.Battery.BatteryLevel = this.deviceStatus.battery + await this.debugLog(`BatteryLevel: ${this.Battery.BatteryLevel}`) // StatusLowBattery this.Battery.StatusLowBattery = this.Battery.BatteryLevel < 10 - ? this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW : this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL; - await this.debugLog(`StatusLowBattery: ${this.Battery.StatusLowBattery}`); + ? this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW + : this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL + await this.debugLog(`StatusLowBattery: ${this.Battery.StatusLowBattery}`) // FirmwareVersion if (this.deviceStatus.version) { - const version = this.deviceStatus.version.toString(); - await this.debugLog(`Firmware Version: ${version.replace(/^V|-.*$/g, '')}`); - const deviceVersion = version.replace(/^V|-.*$/g, '') ?? '0.0.0'; + const version = this.deviceStatus.version.toString() + await this.debugLog(`Firmware Version: ${version.replace(/^V|-.*$/g, '')}`) + const deviceVersion = version.replace(/^V|-.*$/g, '') ?? '0.0.0' this.accessory .getService(this.hap.Service.AccessoryInformation)! .setCharacteristic(this.hap.Characteristic.HardwareRevision, deviceVersion) .setCharacteristic(this.hap.Characteristic.FirmwareRevision, deviceVersion) .getCharacteristic(this.hap.Characteristic.FirmwareRevision) - .updateValue(deviceVersion); - this.accessory.context.version = deviceVersion; - await this.debugLog(`version: ${this.accessory.context.version}`); + .updateValue(deviceVersion) + this.accessory.context.version = deviceVersion + await this.debugLog(`version: ${this.accessory.context.version}`) } } async parseStatusWebhook(): Promise { - await this.debugLog('parseStatusWebhook'); - await this.debugLog(`(detectionState, brightness, openState) = Webhook:(${this.webhookContext.detectionState}, ${this.webhookContext.brightness},` - + ` ${this.webhookContext.openState}), current:(${this.MotionSensor?.MotionDetected}, ${this.LightSensor?.CurrentAmbientLightLevel},` - + ` ${this.ContactSensor.ContactSensorState})`); + await this.debugLog('parseStatusWebhook') + await this.debugLog(`(detectionState, brightness, openState) = Webhook:(${this.webhookContext.detectionState}, ${this.webhookContext.brightness}, ${this.webhookContext.openState}), current:(${this.MotionSensor?.MotionDetected}, ${this.LightSensor?.CurrentAmbientLightLevel}, ${this.ContactSensor.ContactSensorState})`) // ContactSensorState - this.ContactSensor.ContactSensorState = this.webhookContext.openState === 'open' ? 1 : 0; - await this.debugLog(`ContactSensorState: ${this.ContactSensor.ContactSensorState}`); + this.ContactSensor.ContactSensorState = this.webhookContext.openState === 'open' ? 1 : 0 + await this.debugLog(`ContactSensorState: ${this.ContactSensor.ContactSensorState}`) if (!this.device.contact?.hide_motionsensor && this.MotionSensor?.Service) { // MotionDetected - this.MotionSensor.MotionDetected = this.webhookContext.detectionState === 'DETECTED' ? true : false; - await this.debugLog(`MotionDetected: ${this.MotionSensor.MotionDetected}`); + this.MotionSensor.MotionDetected = this.webhookContext.detectionState === 'DETECTED' + await this.debugLog(`MotionDetected: ${this.MotionSensor.MotionDetected}`) } if (!this.device.contact?.hide_lightsensor && this.LightSensor?.Service) { - const set_minLux = this.device.blindTilt?.set_minLux ?? 1; - const set_maxLux = this.device.blindTilt?.set_maxLux ?? 6001; - const lightLevel = this.webhookContext.brightness === 'bright' ? set_maxLux : set_minLux; - this.LightSensor.CurrentAmbientLightLevel = await this.getLightLevel(lightLevel, set_minLux, set_maxLux, 2); - await this.debugLog(`LightLevel: ${this.webhookContext.brightness}, CurrentAmbientLightLevel: ${this.LightSensor.CurrentAmbientLightLevel}`); + const set_minLux = this.device.blindTilt?.set_minLux ?? 1 + const set_maxLux = this.device.blindTilt?.set_maxLux ?? 6001 + const lightLevel = this.webhookContext.brightness === 'bright' ? set_maxLux : set_minLux + this.LightSensor.CurrentAmbientLightLevel = await this.getLightLevel(lightLevel, set_minLux, set_maxLux, 2) + await this.debugLog(`LightLevel: ${this.webhookContext.brightness}, CurrentAmbientLightLevel: ${this.LightSensor.CurrentAmbientLightLevel}`) } } @@ -283,93 +288,97 @@ export class Contact extends deviceBase { */ async refreshStatus(): Promise { if (!this.device.enableCloudService && this.OpenAPI) { - await this.errorLog(`refreshStatus enableCloudService: ${this.device.enableCloudService}`); - } else if (this.BLE || this.config.options?.BLE) { - await this.BLERefreshStatus(); + await this.errorLog(`refreshStatus enableCloudService: ${this.device.enableCloudService}`) + } else if (this.BLE) { + await this.BLERefreshStatus() } else if (this.OpenAPI && this.platform.config.credentials?.token) { - await this.openAPIRefreshStatus(); + await this.openAPIRefreshStatus() } else { - await this.offlineOff(); - await this.debugWarnLog(`Connection Type: ${this.device.connectionType}, refreshStatus will not happen.`); + await this.offlineOff() + await this.debugWarnLog(`Connection Type: ${this.device.connectionType}, refreshStatus will not happen.`) } } async BLERefreshStatus(): Promise { - await this.debugLog('BLERefreshStatus'); + await this.debugLog('BLERefreshStatus') + const switchbot = await this.switchbotBLE() + if (switchbot === undefined) { + await this.BLERefreshConnection(switchbot) + } else { + // Start to monitor advertisement packets + (async () => { + // Start to monitor advertisement packets + const serviceData = await this.monitorAdvertisementPackets(switchbot) as contactSensorServiceData + // Update HomeKit + if (serviceData.model === SwitchBotBLEModel.ContactSensor && serviceData.modelName === SwitchBotBLEModelName.ContactSensor) { + this.serviceData = serviceData + await this.BLEparseStatus() + await this.updateHomeKitCharacteristics() + } else { + await this.errorLog(`failed to get serviceData, serviceData: ${serviceData}`) + await this.BLERefreshConnection(switchbot) + } + })() + } + } + + async registerPlatformBLE(): Promise { + await this.debugLog('registerPlatformBLE') if (this.config.options?.BLE) { - await this.debugLog('is listening to Platform BLE.'); - this.device.bleMac = this.device.deviceId!.match(/.{1,2}/g)!.join(':').toLowerCase(); - await this.debugLog(`bleMac: ${this.device.bleMac}`); + await this.debugLog('is listening to Platform BLE.') + this.device.bleMac = this.device.deviceId!.match(/.{1,2}/g)!.join(':').toLowerCase() + await this.debugLog(`bleMac: ${this.device.bleMac}`) this.platform.bleEventHandler[this.device.bleMac] = async (context: contactSensorServiceData) => { try { - await this.debugLog(`received BLE: ${JSON.stringify(context)}`); - this.serviceData = context; - await this.BLEparseStatus(); - await this.updateHomeKitCharacteristics(); + await this.debugLog(`received BLE: ${JSON.stringify(context)}`) + this.serviceData = context + await this.BLEparseStatus() + await this.updateHomeKitCharacteristics() } catch (e: any) { - await this.errorLog(`failed to handle BLE. Received: ${JSON.stringify(context)} Error: ${e}`); + await this.errorLog(`failed to handle BLE. Received: ${JSON.stringify(context)} Error: ${e}`) } - }; - } else { - await this.debugLog('is using Device BLE Scanning.'); - const switchbot = await this.switchbotBLE(); - if (switchbot === undefined) { - await this.BLERefreshConnection(switchbot); - } else { - // Start to monitor advertisement packets - (async () => { - // Start to monitor advertisement packets - const serviceData = await this.monitorAdvertisementPackets(switchbot) as contactSensorServiceData; - // Update HomeKit - if (serviceData.model === SwitchBotBLEModel.ContactSensor && serviceData.modelName === SwitchBotBLEModelName.ContactSensor) { - this.serviceData = serviceData; - await this.BLEparseStatus(); - await this.updateHomeKitCharacteristics(); - } else { - await this.errorLog(`failed to get serviceData, serviceData: ${serviceData}`); - await this.BLERefreshConnection(switchbot); - } - })(); } + } else { + await this.debugLog('is not listening to Platform BLE') } } async openAPIRefreshStatus(): Promise { - await this.debugLog('openAPIRefreshStatus'); + await this.debugLog('openAPIRefreshStatus') try { - const { body, statusCode } = await this.deviceRefreshStatus(); - const deviceStatus: any = await body.json(); - await this.debugLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`);; + const { body, statusCode } = await this.deviceRefreshStatus() + const deviceStatus: any = await body.json() + await this.debugLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`) if (await this.successfulStatusCodes(statusCode, deviceStatus)) { - await this.debugSuccessLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`); - this.deviceStatus = deviceStatus.body; - await this.openAPIparseStatus(); - await this.updateHomeKitCharacteristics(); + await this.debugSuccessLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`) + this.deviceStatus = deviceStatus.body + await this.openAPIparseStatus() + await this.updateHomeKitCharacteristics() } else { - await this.debugWarnLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`); - await this.debugWarnLog(statusCode, deviceStatus); + await this.debugWarnLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`) + await this.debugWarnLog(statusCode, deviceStatus) } } catch (e: any) { - await this.apiError(e); - await this.errorLog(`failed openAPIRefreshStatus with ${this.device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`); + await this.apiError(e) + await this.errorLog(`failed openAPIRefreshStatus with ${this.device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`) } } async registerWebhook() { if (this.device.webhook) { - await this.debugLog('is listening webhook.'); + await this.debugLog('is listening webhook.') this.platform.webhookEventHandler[this.device.deviceId] = async (context: contactSensorWebhookContext) => { try { - await this.debugLog(`received Webhook: ${JSON.stringify(context)}`); - this.webhookContext = context; - await this.parseStatusWebhook(); - await this.updateHomeKitCharacteristics(); + await this.debugLog(`received Webhook: ${JSON.stringify(context)}`) + this.webhookContext = context + await this.parseStatusWebhook() + await this.updateHomeKitCharacteristics() } catch (e: any) { - await this.errorLog(`failed to handle webhook. Received: ${JSON.stringify(context)} Error: ${e}`); + await this.errorLog(`failed to handle webhook. Received: ${JSON.stringify(context)} Error: ${e}`) } - }; + } } else { - await this.debugLog('is not listening webhook.'); + await this.debugLog('is not listening webhook.') } } @@ -378,59 +387,53 @@ export class Contact extends deviceBase { */ async updateHomeKitCharacteristics(): Promise { // ContactSensorState - await this.updateCharacteristic(this.ContactSensor.Service, this.hap.Characteristic.ContactSensorState, - this.ContactSensor.ContactSensorState, 'ContactSensorState'); + await this.updateCharacteristic(this.ContactSensor.Service, this.hap.Characteristic.ContactSensorState, this.ContactSensor.ContactSensorState, 'ContactSensorState') // MotionDetected if (!this.device.contact?.hide_motionsensor && this.MotionSensor?.Service) { - await this.updateCharacteristic(this.MotionSensor.Service, this.hap.Characteristic.MotionDetected, - this.MotionSensor.MotionDetected, 'MotionDetected'); + await this.updateCharacteristic(this.MotionSensor.Service, this.hap.Characteristic.MotionDetected, this.MotionSensor.MotionDetected, 'MotionDetected') } // CurrentAmbientLightLevel if (!this.device.contact?.hide_lightsensor && this.LightSensor?.Service) { - await this.updateCharacteristic(this.LightSensor.Service, this.hap.Characteristic.CurrentAmbientLightLevel, - this.LightSensor.CurrentAmbientLightLevel, 'CurrentAmbientLightLevel'); + await this.updateCharacteristic(this.LightSensor.Service, this.hap.Characteristic.CurrentAmbientLightLevel, this.LightSensor.CurrentAmbientLightLevel, 'CurrentAmbientLightLevel') } // BatteryLevel - await this.updateCharacteristic(this.Battery.Service, this.hap.Characteristic.BatteryLevel, - this.Battery.BatteryLevel, 'BatteryLevel'); + await this.updateCharacteristic(this.Battery.Service, this.hap.Characteristic.BatteryLevel, this.Battery.BatteryLevel, 'BatteryLevel') // StatusLowBattery - await this.updateCharacteristic(this.Battery.Service, this.hap.Characteristic.StatusLowBattery, - this.Battery.StatusLowBattery, 'StatusLowBattery'); + await this.updateCharacteristic(this.Battery.Service, this.hap.Characteristic.StatusLowBattery, this.Battery.StatusLowBattery, 'StatusLowBattery') } async BLERefreshConnection(switchbot: any): Promise { - await this.errorLog(`wasn't able to establish BLE Connection, node-switchbot: ${switchbot}`); + await this.errorLog(`wasn't able to establish BLE Connection, node-switchbot: ${switchbot}`) if (this.platform.config.credentials?.token && this.device.connectionType === 'BLE/OpenAPI') { - await this.warnLog('Using OpenAPI Connection to Refresh Status'); - await this.openAPIRefreshStatus(); + await this.warnLog('Using OpenAPI Connection to Refresh Status') + await this.openAPIRefreshStatus() } } async offlineOff(): Promise { if (this.device.offline) { - this.ContactSensor.Service.updateCharacteristic(this.hap.Characteristic.ContactSensorState, - this.hap.Characteristic.ContactSensorState.CONTACT_DETECTED); + this.ContactSensor.Service.updateCharacteristic(this.hap.Characteristic.ContactSensorState, this.hap.Characteristic.ContactSensorState.CONTACT_DETECTED) if (!this.device.contact?.hide_motionsensor && this.MotionSensor?.Service) { - this.MotionSensor.Service.updateCharacteristic(this.hap.Characteristic.MotionDetected, false); + this.MotionSensor.Service.updateCharacteristic(this.hap.Characteristic.MotionDetected, false) } if (!this.device.contact?.hide_lightsensor && this.LightSensor?.Service) { - this.LightSensor.Service.updateCharacteristic(this.hap.Characteristic.CurrentAmbientLightLevel, 100); + this.LightSensor.Service.updateCharacteristic(this.hap.Characteristic.CurrentAmbientLightLevel, 100) } } } async apiError(e: any): Promise { - this.ContactSensor.Service.updateCharacteristic(this.hap.Characteristic.ContactSensorState, e); - this.ContactSensor.Service.updateCharacteristic(this.hap.Characteristic.StatusActive, e); + this.ContactSensor.Service.updateCharacteristic(this.hap.Characteristic.ContactSensorState, e) + this.ContactSensor.Service.updateCharacteristic(this.hap.Characteristic.StatusActive, e) if (!this.device.contact?.hide_motionsensor && this.MotionSensor?.Service) { - this.MotionSensor.Service.updateCharacteristic(this.hap.Characteristic.MotionDetected, e); - this.MotionSensor.Service.updateCharacteristic(this.hap.Characteristic.StatusActive, e); + this.MotionSensor.Service.updateCharacteristic(this.hap.Characteristic.MotionDetected, e) + this.MotionSensor.Service.updateCharacteristic(this.hap.Characteristic.StatusActive, e) } if (!this.device.contact?.hide_lightsensor && this.LightSensor?.Service) { - this.LightSensor.Service.updateCharacteristic(this.hap.Characteristic.CurrentAmbientLightLevel, e); - this.LightSensor.Service.updateCharacteristic(this.hap.Characteristic.StatusActive, e); + this.LightSensor.Service.updateCharacteristic(this.hap.Characteristic.CurrentAmbientLightLevel, e) + this.LightSensor.Service.updateCharacteristic(this.hap.Characteristic.StatusActive, e) } - this.Battery.Service.updateCharacteristic(this.hap.Characteristic.BatteryLevel, e); - this.Battery.Service.updateCharacteristic(this.hap.Characteristic.StatusLowBattery, e); + this.Battery.Service.updateCharacteristic(this.hap.Characteristic.BatteryLevel, e) + this.Battery.Service.updateCharacteristic(this.hap.Characteristic.StatusLowBattery, e) } } diff --git a/src/device/curtain.ts b/src/device/curtain.ts index f9f0fc5e..cdc86d2f 100644 --- a/src/device/curtain.ts +++ b/src/device/curtain.ts @@ -2,77 +2,98 @@ * * curtain.ts: @switchbot/homebridge-switchbot. */ -import { hostname } from 'os'; -import { deviceBase } from './device.js'; -import { SwitchBotBLEModel, SwitchBotBLEModelName } from 'node-switchbot'; -import { Subject, debounceTime, interval, skipWhile, take, tap } from 'rxjs'; - -import type { devicesConfig } from '../settings.js'; -import type { device } from '../types/devicelist.js'; -import type { SwitchBotPlatform } from '../platform.js'; -import type { curtainStatus } from '../types/devicestatus.js'; -import type { curtainServiceData, curtain3ServiceData } from '../types/bledevicestatus.js'; -import type { curtain3WebhookContext, curtainWebhookContext } from '../types/devicewebhookstatus.js'; -import type { Service, PlatformAccessory, CharacteristicValue, CharacteristicChange } from 'homebridge'; +import type { CharacteristicChange, CharacteristicValue, PlatformAccessory, Service } from 'homebridge' + +import type { SwitchBotPlatform } from '../platform.js' +import type { devicesConfig } from '../settings.js' +import type { curtain3ServiceData, curtainServiceData } from '../types/bledevicestatus.js' +import type { device } from '../types/devicelist.js' +import type { curtainStatus } from '../types/devicestatus.js' +import type { curtain3WebhookContext, curtainWebhookContext } from '../types/devicewebhookstatus.js' + +import { hostname } from 'node:os' + +/* +* For Testing Locally: +* import { SwitchBotBLEModel, SwitchBotBLEModelName } from '/Users/Shared/GitHub/OpenWonderLabs/node-switchbot/dist/index.js'; +*/ +import { SwitchBotBLEModel, SwitchBotBLEModelName } from 'node-switchbot' +import { debounceTime, interval, skipWhile, Subject, take, tap } from 'rxjs' + +import { deviceBase } from './device.js' export class Curtain extends deviceBase { // Services private WindowCovering: { - Name: CharacteristicValue; - Service: Service; - PositionState: CharacteristicValue; - TargetPosition: CharacteristicValue; - CurrentPosition: CharacteristicValue; - HoldPosition: CharacteristicValue; - }; + Name: CharacteristicValue + Service: Service + PositionState: CharacteristicValue + TargetPosition: CharacteristicValue + CurrentPosition: CharacteristicValue + HoldPosition: CharacteristicValue + } private Battery: { - Name: CharacteristicValue; - Service: Service; - BatteryLevel: CharacteristicValue; - StatusLowBattery: CharacteristicValue; - ChargingState: CharacteristicValue; - }; + Name: CharacteristicValue + Service: Service + BatteryLevel: CharacteristicValue + StatusLowBattery: CharacteristicValue + ChargingState: CharacteristicValue + } private LightSensor?: { - Name: CharacteristicValue; - Service: Service; - CurrentAmbientLightLevel?: CharacteristicValue; - }; + Name: CharacteristicValue + Service: Service + CurrentAmbientLightLevel?: CharacteristicValue + } + + private OpenModeSwitch?: { + Name: string + Service: Service + On: CharacteristicValue + } + + private CloseModeSwitch?: { + Name: string + Service: Service + On: CharacteristicValue + } // OpenAPI - deviceStatus!: curtainStatus; + deviceStatus!: curtainStatus - //Webhook - webhookContext!: curtainWebhookContext | curtain3WebhookContext; + // Webhook + webhookContext!: curtainWebhookContext | curtain3WebhookContext // BLE - serviceData!: curtainServiceData | curtain3ServiceData; + serviceData!: curtainServiceData | curtain3ServiceData // Target - setNewTarget!: boolean; - setNewTargetTimer!: NodeJS.Timeout; + setNewTarget!: boolean + setNewTargetTimer!: NodeJS.Timeout // Updates - curtainUpdateInProgress!: boolean; - doCurtainUpdate!: Subject; + curtainMoving!: boolean + curtainUpdateInProgress!: boolean + doCurtainUpdate!: Subject constructor( readonly platform: SwitchBotPlatform, accessory: PlatformAccessory, device: device & devicesConfig, ) { - super(platform, accessory, device); + super(platform, accessory, device) // Set category - accessory.category = this.hap.Categories.WINDOW_COVERING; + accessory.category = this.hap.Categories.WINDOW_COVERING // this is subject we use to track when we need to POST changes to the SwitchBot API - this.doCurtainUpdate = new Subject(); - this.curtainUpdateInProgress = false; - this.setNewTarget = false; + this.doCurtainUpdate = new Subject() + this.curtainMoving = false + this.curtainUpdateInProgress = false + this.setNewTarget = false // Initialize WindowCovering Service - accessory.context.WindowCovering = accessory.context.WindowCovering ?? {}; + accessory.context.WindowCovering = accessory.context.WindowCovering ?? {} this.WindowCovering = { Name: accessory.displayName, Service: accessory.getService(this.hap.Service.WindowCovering) ?? accessory.addService(this.hap.Service.WindowCovering) as Service, @@ -80,170 +101,217 @@ export class Curtain extends deviceBase { TargetPosition: accessory.context.TargetPosition ?? 100, CurrentPosition: accessory.context.CurrentPosition ?? 100, HoldPosition: accessory.context.HoldPosition ?? false, - }; - accessory.context.WindowCovering = this.WindowCovering as object; + } + accessory.context.WindowCovering = this.WindowCovering as object // Initialize WindowCovering Service - this.WindowCovering.Service. - setCharacteristic(this.hap.Characteristic.Name, this.WindowCovering.Name) - .setCharacteristic(this.hap.Characteristic.ObstructionDetected, false) - .getCharacteristic(this.hap.Characteristic.PositionState) - .onGet(() => { - return this.WindowCovering.PositionState; - }); + this.WindowCovering.Service.setCharacteristic(this.hap.Characteristic.Name, this.WindowCovering.Name).setCharacteristic(this.hap.Characteristic.ObstructionDetected, false).getCharacteristic(this.hap.Characteristic.PositionState).onGet(() => { + return this.WindowCovering.PositionState + }) // Initialize WindowCovering CurrentPosition - this.WindowCovering.Service - .getCharacteristic(this.hap.Characteristic.CurrentPosition) - .setProps({ - minStep: device.curtain?.set_minStep ?? 1, - minValue: 0, - maxValue: 100, - validValueRanges: [0, 100], - }) - .onGet(() => { - return this.WindowCovering.CurrentPosition; - }); + this.WindowCovering.Service.getCharacteristic(this.hap.Characteristic.CurrentPosition).setProps({ + minStep: device.curtain?.set_minStep ?? 1, + minValue: 0, + maxValue: 100, + validValueRanges: [0, 100], + }).onGet(() => { + return this.WindowCovering.CurrentPosition + }) // Initialize WindowCovering TargetPosition - this.WindowCovering.Service - .getCharacteristic(this.hap.Characteristic.TargetPosition) - .setProps({ - minStep: device.curtain?.set_minStep ?? 1, - minValue: 0, - maxValue: 100, - validValueRanges: [0, 100], - }) - .onGet(() => { - return this.WindowCovering.TargetPosition; - }) - .onSet(this.TargetPositionSet.bind(this)); + this.WindowCovering.Service.getCharacteristic(this.hap.Characteristic.TargetPosition).setProps({ + minStep: device.curtain?.set_minStep ?? 1, + minValue: 0, + maxValue: 100, + validValueRanges: [0, 100], + }).onGet(() => { + return this.WindowCovering.TargetPosition + }).onSet(this.TargetPositionSet.bind(this)) // Initialize WindowCovering TargetPosition - this.WindowCovering.Service - .getCharacteristic(this.hap.Characteristic.HoldPosition) - .onGet(() => { - return this.WindowCovering.HoldPosition; - }) - .onSet(this.HoldPositionSet.bind(this)); + this.WindowCovering.Service.getCharacteristic(this.hap.Characteristic.HoldPosition).onGet(() => { + return this.WindowCovering.HoldPosition + }).onSet(this.HoldPositionSet.bind(this)) // Initialize Battery Service - accessory.context.Battery = accessory.context.Battery ?? {}; + accessory.context.Battery = accessory.context.Battery ?? {} this.Battery = { Name: `${accessory.displayName} Battery`, Service: accessory.getService(this.hap.Service.Battery) ?? accessory.addService(this.hap.Service.Battery) as Service, BatteryLevel: accessory.context.BatteryLevel ?? 100, StatusLowBattery: accessory.context.StatusLowBattery ?? this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL, ChargingState: accessory.context.ChargingState ?? this.hap.Characteristic.ChargingState.NOT_CHARGING, - }; - accessory.context.Battery = this.Battery as object; + } + accessory.context.Battery = this.Battery as object // Initialize Battery Service - this.Battery.Service - .setCharacteristic(this.hap.Characteristic.Name, this.Battery.Name) - .getCharacteristic(this.hap.Characteristic.BatteryLevel) - .onGet(() => { - return this.Battery.BatteryLevel; - }); - - this.Battery.Service - .getCharacteristic(this.hap.Characteristic.StatusLowBattery) - .onGet(() => { - return this.Battery.StatusLowBattery; - }); - - this.Battery.Service - .getCharacteristic(this.hap.Characteristic.ChargingState) - .onGet(() => { - return this.Battery.ChargingState; - }); + this.Battery.Service.setCharacteristic(this.hap.Characteristic.Name, this.Battery.Name).getCharacteristic(this.hap.Characteristic.BatteryLevel).onGet(() => { + return this.Battery.BatteryLevel + }) + + this.Battery.Service.getCharacteristic(this.hap.Characteristic.StatusLowBattery).onGet(() => { + return this.Battery.StatusLowBattery + }) + + this.Battery.Service.getCharacteristic(this.hap.Characteristic.ChargingState).onGet(() => { + return this.Battery.ChargingState + }) // Initialize LightSensor Service - if (device.curtain?.hide_lightsensor) { + if (device.curtain?.hide_lightsensor || (device.deviceType !== 'curtain' && device.deviceType !== 'curtain3')) { if (this.LightSensor?.Service) { - this.debugLog('Removing Light Sensor Service'); - this.LightSensor.Service = this.accessory.getService(this.hap.Service.LightSensor) as Service; - accessory.removeService(this.LightSensor.Service); - accessory.context.LightSensor = {}; + this.debugLog('Removing Light Sensor Service') + this.LightSensor.Service = this.accessory.getService(this.hap.Service.LightSensor) as Service + accessory.removeService(this.LightSensor.Service) + accessory.context.LightSensor = {} } - } else { - accessory.context.LightSensor = accessory.context.LightSensor ?? {}; + } else if (device.deviceType === 'curtain' || device.deviceType === 'curtain3') { + accessory.context.LightSensor = accessory.context.LightSensor ?? {} this.LightSensor = { Name: `${accessory.displayName} Light Sensor`, Service: accessory.getService(this.hap.Service.LightSensor) ?? this.accessory.addService(this.hap.Service.LightSensor) as Service, CurrentAmbientLightLevel: accessory.context.CurrentAmbientLightLevel ?? 0.0001, - }; - accessory.context.LightSensor = this.LightSensor as object; + } + accessory.context.LightSensor = this.LightSensor as object // Initialize LightSensor Characteristic - this.LightSensor.Service - .setCharacteristic(this.hap.Characteristic.Name, this.LightSensor.Name) - .setCharacteristic(this.hap.Characteristic.StatusActive, true) - .getCharacteristic(this.hap.Characteristic.CurrentAmbientLightLevel) - .onGet(() => { - return this.LightSensor!.CurrentAmbientLightLevel!; - }); + this.LightSensor.Service.setCharacteristic(this.hap.Characteristic.Name, this.LightSensor.Name).setCharacteristic(this.hap.Characteristic.StatusActive, true).getCharacteristic(this.hap.Characteristic.CurrentAmbientLightLevel).onGet(() => { + return this.LightSensor!.CurrentAmbientLightLevel! + }) + } + + // Initialize Open Mode Switch Service + if (!device.curtain?.silentModeSwitch) { + if (this.OpenModeSwitch?.Service) { + this.debugLog('Removing Open Mode Switch Service') + this.OpenModeSwitch.Service = this.accessory.getService(this.hap.Service.Switch) as Service + accessory.removeService(this.OpenModeSwitch.Service) + accessory.context.OpenModeSwitch = {} + } + } else { + accessory.context.OpenModeSwitch = accessory.context.OpenModeSwitch ?? {} + this.debugLog('Adding Open Mode Switch Service') + const name = `${accessory.displayName} Silent Open Mode` + const uuid = this.api.hap.uuid.generate(name) + this.OpenModeSwitch = { + Name: name, + Service: accessory.getService(name) ?? accessory.addService(this.hap.Service.Switch, name, uuid) as Service, + On: accessory.context.OpenModeSwitch.On ?? false, + } + accessory.context.OpenModeSwitch = this.OpenModeSwitch as object + + // Initialize Open Mode Switch Service + this.OpenModeSwitch.Service.setCharacteristic(this.hap.Characteristic.Name, this.OpenModeSwitch.Name).getCharacteristic(this.hap.Characteristic.On).onGet(() => { + return this.OpenModeSwitch?.On ?? false + }) + + this.OpenModeSwitch.Service.getCharacteristic(this.hap.Characteristic.On).onSet(this.OpenModeSwitchSet.bind(this)) + } + + // Initialize Close Mode Switch Service + if (!device.curtain?.silentModeSwitch) { + if (this.CloseModeSwitch?.Service) { + this.debugLog('Removing Close Mode Switch Service') + this.CloseModeSwitch.Service = this.accessory.getService(this.hap.Service.Switch) as Service + accessory.removeService(this.CloseModeSwitch.Service) + accessory.context.CloseModeSwitch = {} + } + } else { + accessory.context.CloseModeSwitch = accessory.context.CloseModeSwitch ?? {} + this.debugLog('Adding Close Mode Switch Service') + const name = `${accessory.displayName} Silent Close Mode` + const uuid = this.api.hap.uuid.generate(name) + this.CloseModeSwitch = { + Name: name, + Service: this.accessory.getService(name) ?? accessory.addService(this.hap.Service.Switch, name, uuid) as Service, + On: accessory.context.CloseModeSwitch.On ?? false, + } + accessory.context.CloseModeSwitch = this.CloseModeSwitch as object + + // Initialize Close Mode Switch Service + this.CloseModeSwitch.Service.setCharacteristic(this.hap.Characteristic.Name, this.CloseModeSwitch.Name).getCharacteristic(this.hap.Characteristic.On).onGet(() => { + return this.CloseModeSwitch?.On ?? false + }) + + this.CloseModeSwitch.Service.getCharacteristic(this.hap.Characteristic.On).onSet(this.CloseModeSwitchSet.bind(this)) } // Retrieve initial values and updateHomekit - this.debugLog('Retrieve initial values and update Homekit'); - this.refreshStatus(); + try { + this.debugLog('Retrieve initial values and update Homekit') + this.refreshStatus() + } catch (e: any) { + this.errorLog(`failed to retrieve initial values and update Homekit, Error: ${e}`) + } - //regisiter webhook event handler - this.debugLog('Registering Webhook Event Handler'); - this.registerWebhook(); + // regisiter webhook event handler if enabled + try { + this.debugLog('Registering Webhook Event Handler') + this.registerWebhook() + } catch (e: any) { + this.errorLog(`failed to registerWebhook, Error: ${e}`) + } + + // regisiter platform BLE event handler if enabled + try { + this.debugLog('Registering Platform BLE Event Handler') + this.registerPlatformBLE() + } catch (e: any) { + this.errorLog(`failed to registerPlatformBLE, Error: ${e}`) + } // History - this.history(); + this.history() // Start an update interval interval(this.deviceRefreshRate * 1000) .pipe(skipWhile(() => this.curtainUpdateInProgress)) .subscribe(async () => { - await this.refreshStatus(); - }); + await this.refreshStatus() + }) // update slide progress interval(this.deviceUpdateRate * 1000) - //.pipe(skipWhile(() => this.curtainUpdateInProgress)) + .pipe(skipWhile(() => !this.curtainMoving)) .subscribe(async () => { if (this.WindowCovering.PositionState === this.hap.Characteristic.PositionState.STOPPED) { - return; + return } - await this.debugLog(`Refresh Status When Moving, PositionState: ${this.WindowCovering.PositionState}`); - await this.refreshStatus(); - }); + await this.debugLog(`Refresh Status When Moving, PositionState: ${this.WindowCovering.PositionState}`) + await this.refreshStatus() + }) // Watch for Curtain change events // We put in a debounce of 100ms so we don't make duplicate calls this.doCurtainUpdate .pipe( tap(() => { - this.curtainUpdateInProgress = true; + this.curtainUpdateInProgress = true }), debounceTime(this.devicePushRate * 1000), ) .subscribe(async () => { try { - await this.pushChanges(); + await this.pushChanges() } catch (e: any) { - await this.apiError(e); - await this.errorLog(`failed pushChanges with ${device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`); + await this.apiError(e) + await this.errorLog(`failed pushChanges with ${device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`) } - this.curtainUpdateInProgress = false; - }); + this.curtainUpdateInProgress = false + }) // Setup EVE history features - this.setupHistoryService(); + this.setupHistoryService() } async history() { if (this.device.history === true) { // initialize when this accessory is newly created. - this.accessory.context.lastActivation = this.accessory.context.lastActivation ?? 0; + this.accessory.context.lastActivation = this.accessory.context.lastActivation ?? 0 } else { // removes cached values if history is turned off - delete this.accessory.context.lastActivation; + delete this.accessory.context.lastActivation } } @@ -252,171 +320,171 @@ export class Curtain extends deviceBase { */ async setupHistoryService(): Promise { if (this.device.history !== true) { - return; + return } const mac = this.device .deviceId!.match(/.{1,2}/g)! .join(':') - .toLowerCase(); + .toLowerCase() this.historyService = new this.platform.fakegatoAPI('custom', this.accessory, { log: this.platform.log, storage: 'fs', filename: `${hostname().split('.')[0]}_${mac}_persist.json`, - }); - const motion: Service = - this.accessory.getService(this.hap.Service.MotionSensor) || - this.accessory.addService(this.hap.Service.MotionSensor, 'Motion'); - motion.addOptionalCharacteristic(this.platform.eve.Characteristics.LastActivation); + }) + const motion: Service + = this.accessory.getService(this.hap.Service.MotionSensor) + || this.accessory.addService(this.hap.Service.MotionSensor, 'Motion') + motion.addOptionalCharacteristic(this.platform.eve.Characteristics.LastActivation) motion.getCharacteristic(this.platform.eve.Characteristics.LastActivation).onGet(() => { const lastActivation = this.accessory.context.lastActivation ? Math.max(0, this.accessory.context.lastActivation - this.historyService.getInitialTime()) - : 0; - return lastActivation; - }); - await this.setMinMax(); + : 0 + return lastActivation + }) + await this.setMinMax() motion.getCharacteristic(this.hap.Characteristic.MotionDetected).on('change', (event: CharacteristicChange) => { if (event.newValue !== event.oldValue) { - const sensor = this.accessory.getService(this.hap.Service.MotionSensor); + const sensor = this.accessory.getService(this.hap.Service.MotionSensor) const entry = { time: Math.round(new Date().valueOf() / 1000), motion: event.newValue, - }; - this.accessory.context.lastActivation = entry.time; + } + this.accessory.context.lastActivation = entry.time sensor?.updateCharacteristic( this.platform.eve.Characteristics.LastActivation, - Math.max(0, this.accessory.context.lastActivation - this.historyService.getInitialTime())); - this.historyService.addEntry(entry); + Math.max(0, this.accessory.context.lastActivation - this.historyService.getInitialTime()), + ) + this.historyService.addEntry(entry) } - }); - this.updateHistory(); + }) + this.updateHistory() } async updateHistory(): Promise { - const motion = Number(this.WindowCovering.CurrentPosition) > 0 ? 1 : 0; + const motion = Number(this.WindowCovering.CurrentPosition) > 0 ? 1 : 0 this.historyService.addEntry({ time: Math.round(new Date().valueOf() / 1000), - motion: motion, - }); + motion, + }) setTimeout(async () => { - await this.updateHistory(); - }, 10 * 60 * 1000); + await this.updateHistory() + }, 10 * 60 * 1000) } async BLEparseStatus(): Promise { - await this.debugLog('BLEparseStatus'); - await this.debugLog(`(position, battery) = BLE:(${this.serviceData.position}, ${this.serviceData.battery}),` - + ` current:(${this.WindowCovering.CurrentPosition}, ${this.Battery.BatteryLevel})`); + await this.debugLog('BLEparseStatus') + await this.debugLog(`(position, battery) = BLE:(${this.serviceData.position}, ${this.serviceData.battery}), current:(${this.WindowCovering.CurrentPosition}, ${this.Battery.BatteryLevel})`) // CurrentPosition - this.WindowCovering.CurrentPosition = 100 -this.serviceData.position; - await this.getCurrentPostion(); + this.WindowCovering.CurrentPosition = 100 - this.serviceData.position + await this.getCurrentPostion() // CurrentAmbientLightLevel if (!this.device.curtain?.hide_lightsensor && this.LightSensor?.Service) { - const set_minLux = this.device.curtain?.set_minLux ?? 1; - const set_maxLux = this.device.curtain?.set_maxLux ?? 6001; - const lightLevel = this.serviceData.lightLevel; - this.LightSensor.CurrentAmbientLightLevel = await this.getLightLevel(lightLevel, set_minLux, set_maxLux, 19); - this.debugLog(`LightLevel: ${this.serviceData.lightLevel},` - + ` CurrentAmbientLightLevel: ${this.LightSensor.CurrentAmbientLightLevel}`); + const set_minLux = this.device.curtain?.set_minLux ?? 1 + const set_maxLux = this.device.curtain?.set_maxLux ?? 6001 + const lightLevel = this.serviceData.lightLevel + this.LightSensor.CurrentAmbientLightLevel = await this.getLightLevel(lightLevel, set_minLux, set_maxLux, 19) + this.debugLog(`LightLevel: ${this.serviceData.lightLevel}, CurrentAmbientLightLevel: ${this.LightSensor.CurrentAmbientLightLevel}`) } // BatteryLevel - this.Battery.BatteryLevel = this.serviceData.battery; - await this.debugLog(`BatteryLevel: ${this.Battery.BatteryLevel}`); + this.Battery.BatteryLevel = this.serviceData.battery + await this.debugLog(`BatteryLevel: ${this.Battery.BatteryLevel}`) // StatusLowBattery this.Battery.StatusLowBattery = this.Battery.BatteryLevel < 10 - ? this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW : this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL; - await this.debugLog(`StatusLowBattery: ${this.Battery.StatusLowBattery}`); + ? this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW + : this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL + await this.debugLog(`StatusLowBattery: ${this.Battery.StatusLowBattery}`) } async openAPIparseStatus(): Promise { - await this.debugLog('openAPIparseStatus'); - await this.debugLog(`(slidePosition, battery) = OpenAPI:(${this.deviceStatus.slidePosition}, ${this.deviceStatus.battery}),` - + ` current:(${this.WindowCovering.CurrentPosition}, ${this.Battery.BatteryLevel})`); + await this.debugLog('openAPIparseStatus') + await this.debugLog(`(slidePosition, battery, version) = OpenAPI:(${this.deviceStatus.slidePosition}, ${this.deviceStatus.battery}, ${this.deviceStatus.version}), current:(${this.WindowCovering.CurrentPosition}, ${this.Battery.BatteryLevel}, ${this.accessory.context.version})`) // CurrentPosition - this.WindowCovering.CurrentPosition = 100 - this.deviceStatus.slidePosition; - await this.setMinMax(); - await this.debugLog(`CurrentPosition: ${this.WindowCovering.CurrentPosition}`); - if (this.setNewTarget) { - await this.infoLog('Checking Status ...'); - } - if (this.setNewTarget && this.deviceStatus.moving) { - await this.setMinMax(); + this.WindowCovering.CurrentPosition = 100 - this.deviceStatus.slidePosition + await this.setMinMax() + await this.debugLog(`CurrentPosition: ${this.WindowCovering.CurrentPosition}`) + if (this.setNewTarget || this.deviceStatus.moving) { + await this.infoLog('Checking Status ...') + this.curtainMoving = true + await this.setMinMax() if (Number(this.WindowCovering.TargetPosition) > this.WindowCovering.CurrentPosition) { - await this.debugLog(`Closing, CurrentPosition: ${this.WindowCovering.CurrentPosition}`); - this.WindowCovering.PositionState = this.hap.Characteristic.PositionState.INCREASING; - this.WindowCovering.Service.getCharacteristic(this.hap.Characteristic.PositionState).updateValue(this.WindowCovering.PositionState); - await this.debugLog(`Increasing, PositionState: ${this.WindowCovering.PositionState}`); + await this.debugLog(`Closing, CurrentPosition: ${this.WindowCovering.CurrentPosition}`) + this.WindowCovering.PositionState = this.hap.Characteristic.PositionState.INCREASING + this.WindowCovering.Service.getCharacteristic(this.hap.Characteristic.PositionState).updateValue(this.WindowCovering.PositionState) + await this.debugLog(`Increasing, PositionState: ${this.WindowCovering.PositionState}`) } else if (Number(this.WindowCovering.TargetPosition) < this.WindowCovering.CurrentPosition) { - await this.debugLog(`Opening, CurrentPosition: ${this.WindowCovering.CurrentPosition}`); - this.WindowCovering.PositionState = this.hap.Characteristic.PositionState.DECREASING; - this.WindowCovering.Service.getCharacteristic(this.hap.Characteristic.PositionState).updateValue(this.WindowCovering.PositionState); - await this.debugLog(`Decreasing, PositionState: ${this.WindowCovering.PositionState}`); + await this.debugLog(`Opening, CurrentPosition: ${this.WindowCovering.CurrentPosition}`) + this.WindowCovering.PositionState = this.hap.Characteristic.PositionState.DECREASING + this.WindowCovering.Service.getCharacteristic(this.hap.Characteristic.PositionState).updateValue(this.WindowCovering.PositionState) + await this.debugLog(`Decreasing, PositionState: ${this.WindowCovering.PositionState}`) } else { - await this.debugLog(`Standby, CurrentPosition: ${this.WindowCovering.CurrentPosition}`); - this.WindowCovering.PositionState = this.hap.Characteristic.PositionState.STOPPED; - this.WindowCovering.Service.getCharacteristic(this.hap.Characteristic.PositionState).updateValue(this.WindowCovering.PositionState); - await this.debugLog(`Stopped, PositionState: ${this.WindowCovering.PositionState}`); + await this.debugLog(`Standby, CurrentPosition: ${this.WindowCovering.CurrentPosition}`) + this.WindowCovering.PositionState = this.hap.Characteristic.PositionState.STOPPED + this.WindowCovering.Service.getCharacteristic(this.hap.Characteristic.PositionState).updateValue(this.WindowCovering.PositionState) + await this.debugLog(`Stopped, PositionState: ${this.WindowCovering.PositionState}`) } } else { - await this.debugLog(`Standby, CurrentPosition: ${this.WindowCovering.CurrentPosition}`); - this.WindowCovering.TargetPosition = this.WindowCovering.CurrentPosition; - this.WindowCovering.PositionState = this.hap.Characteristic.PositionState.STOPPED; - await this.debugLog(`Stopped, PositionState: ${this.WindowCovering.PositionState}`); + await this.infoLog('Standby ...') + this.curtainMoving = false + await this.debugLog(`Standby, CurrentPosition: ${this.WindowCovering.CurrentPosition}`) + this.WindowCovering.TargetPosition = this.WindowCovering.CurrentPosition + this.WindowCovering.PositionState = this.hap.Characteristic.PositionState.STOPPED + await this.debugLog(`Stopped, PositionState: ${this.WindowCovering.PositionState}`) } - await this.debugLog(`CurrentPosition: ${this.WindowCovering.CurrentPosition}, TargetPosition: ${this.WindowCovering.TargetPosition},` - + ` PositionState: ${this.WindowCovering.PositionState},`); + await this.debugLog(`CurrentPosition: ${this.WindowCovering.CurrentPosition}, TargetPosition: ${this.WindowCovering.TargetPosition}, PositionState: ${this.WindowCovering.PositionState},`) // Brightness if (!this.device.curtain?.hide_lightsensor && this.LightSensor?.Service) { - const set_minLux = this.device.curtain?.set_minLux ?? 1; - const set_maxLux = this.device.curtain?.set_maxLux ?? 6001; - const lightLevel = this.deviceStatus.lightLevel === 'bright' ? set_maxLux : set_minLux; - this.LightSensor.CurrentAmbientLightLevel = await this.getLightLevel(lightLevel, set_minLux, set_maxLux, 2); - await this.debugLog(`CurrentAmbientLightLevel: ${this.LightSensor.CurrentAmbientLightLevel}`); + const set_minLux = this.device.curtain?.set_minLux ?? 1 + const set_maxLux = this.device.curtain?.set_maxLux ?? 6001 + const lightLevel = this.deviceStatus.lightLevel === 'bright' ? set_maxLux : set_minLux + this.LightSensor.CurrentAmbientLightLevel = await this.getLightLevel(lightLevel, set_minLux, set_maxLux, 2) + await this.debugLog(`CurrentAmbientLightLevel: ${this.LightSensor.CurrentAmbientLightLevel}`) } // BatteryLevel - this.Battery.BatteryLevel = this.deviceStatus.battery; - await this.debugLog(`BatteryLevel: ${this.Battery.BatteryLevel}`); + this.Battery.BatteryLevel = this.deviceStatus.battery + await this.debugLog(`BatteryLevel: ${this.Battery.BatteryLevel}`) // StatusLowBattery this.Battery.StatusLowBattery = this.Battery.BatteryLevel < 10 - ? this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW : this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL; - await this.debugLog(`StatusLowBattery: ${this.Battery.StatusLowBattery}`); + ? this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW + : this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL + await this.debugLog(`StatusLowBattery: ${this.Battery.StatusLowBattery}`) // Firmware Version if (this.deviceStatus.version) { - const version = this.deviceStatus.version.toString(); - await this.debugLog(`Firmware Version: ${version.replace(/^V|-.*$/g, '')}`); - const deviceVersion = version.replace(/^V|-.*$/g, '') ?? '0.0.0'; + const version = this.deviceStatus.version.toString() + await this.debugLog(`Firmware Version: ${version.replace(/^V|-.*$/g, '')}`) + const deviceVersion = version.replace(/^V|-.*$/g, '') ?? '0.0.0' this.accessory .getService(this.hap.Service.AccessoryInformation)! .setCharacteristic(this.hap.Characteristic.HardwareRevision, deviceVersion) .setCharacteristic(this.hap.Characteristic.FirmwareRevision, deviceVersion) .getCharacteristic(this.hap.Characteristic.FirmwareRevision) - .updateValue(deviceVersion); - this.accessory.context.version = deviceVersion; - await this.debugSuccessLog(`version: ${this.accessory.context.version}`); + .updateValue(deviceVersion) + this.accessory.context.version = deviceVersion + await this.debugSuccessLog(`version: ${this.accessory.context.version}`) } } async parseStatusWebhook(): Promise { - await this.debugLog('parseStatusWebhook'); - await this.debugLog(`(slidePosition, battery) = Webhook:(${this.webhookContext.slidePosition}, ${this.webhookContext.battery}),` - + ` current:(${this.WindowCovering.CurrentPosition}, ${this.Battery.BatteryLevel})`); + await this.debugLog('parseStatusWebhook') + await this.debugLog(`(slidePosition, battery) = Webhook:(${this.webhookContext.slidePosition}, ${this.webhookContext.battery}), current:(${this.WindowCovering.CurrentPosition}, ${this.Battery.BatteryLevel})`) // CurrentPosition - this.WindowCovering.CurrentPosition = 100 - this.webhookContext.slidePosition; - await this.getCurrentPostion(); + this.WindowCovering.CurrentPosition = 100 - this.webhookContext.slidePosition + await this.getCurrentPostion() // BatteryLevel - this.Battery.BatteryLevel = this.webhookContext.battery; - await this.debugLog(`BatteryLevel: ${this.Battery.BatteryLevel}`); + this.Battery.BatteryLevel = this.webhookContext.battery + await this.debugLog(`BatteryLevel: ${this.Battery.BatteryLevel}`) // StatusLowBattery this.Battery.StatusLowBattery = this.Battery.BatteryLevel < 10 - ? this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW : this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL; - await this.debugLog(`StatusLowBattery: ${this.Battery.StatusLowBattery}`); + ? this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW + : this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL + await this.debugLog(`StatusLowBattery: ${this.Battery.StatusLowBattery}`) } /** @@ -424,94 +492,98 @@ export class Curtain extends deviceBase { */ async refreshStatus(): Promise { if (!this.device.enableCloudService && this.OpenAPI) { - await this.errorLog(`refreshStatus enableCloudService: ${this.device.enableCloudService}`); - } else if (this.BLE || this.config.options?.BLE) { - await this.BLERefreshStatus(); + await this.errorLog(`refreshStatus enableCloudService: ${this.device.enableCloudService}`) + } else if (this.BLE) { + await this.BLERefreshStatus() } else if (this.OpenAPI && this.platform.config.credentials?.token) { - await this.openAPIRefreshStatus(); + await this.openAPIRefreshStatus() } else { - await this.offlineOff(); - await this.debugWarnLog(`Connection Type: ${this.device.connectionType}, refreshStatus will not happen.`); + await this.offlineOff() + await this.debugWarnLog(`Connection Type: ${this.device.connectionType}, refreshStatus will not happen.`) } } async BLERefreshStatus(): Promise { - await this.debugLog('BLERefreshStatus'); - if (this.config.options?.BLE) { - await this.debugLog('is listening to Platform BLE.'); - this.device.bleMac = this.device.deviceId!.match(/.{1,2}/g)!.join(':').toLowerCase(); - await this.debugLog(`bleMac: ${this.device.bleMac}`); - this.platform.bleEventHandler[this.device.bleMac] = async (context: curtainServiceData | curtain3ServiceData) => { - try { - await this.debugLog(`received BLE: ${JSON.stringify(context)}`); - this.serviceData = context; - await this.BLEparseStatus(); - await this.updateHomeKitCharacteristics(); - } catch (e: any) { - await this.errorLog(`failed to handle BLE. Received: ${JSON.stringify(context)} Error: ${e}`); - } - }; + await this.debugLog('BLERefreshStatus') + const switchbot = await this.switchbotBLE() + if (switchbot === undefined) { + await this.BLERefreshConnection(switchbot) } else { - await this.debugLog('is using Device BLE Scanning.'); - const switchbot = await this.switchbotBLE(); - if (switchbot === undefined) { - await this.BLERefreshConnection(switchbot); - } else { + // Start to monitor advertisement packets + (async () => { // Start to monitor advertisement packets - (async () => { - // Start to monitor advertisement packets - const serviceData = await this.monitorAdvertisementPackets(switchbot) as curtainServiceData | curtain3ServiceData; - // Update HomeKit - if ((serviceData.model === SwitchBotBLEModel.Curtain || SwitchBotBLEModel.Curtain3) + const serviceData = await this.monitorAdvertisementPackets(switchbot) as curtainServiceData | curtain3ServiceData + // Update HomeKit + if ((serviceData.model === SwitchBotBLEModel.Curtain || SwitchBotBLEModel.Curtain3) && (serviceData.modelName === SwitchBotBLEModelName.Curtain || SwitchBotBLEModelName.Curtain3)) { - this.serviceData = serviceData; - await this.BLEparseStatus(); - await this.updateHomeKitCharacteristics(); - } else { - await this.errorLog(`failed to get serviceData, serviceData: ${serviceData}`); - await this.BLERefreshConnection(switchbot); - } - })(); - } + this.serviceData = serviceData + await this.BLEparseStatus() + await this.updateHomeKitCharacteristics() + } else { + await this.errorLog(`failed to get serviceData, serviceData: ${serviceData}`) + await this.BLERefreshConnection(switchbot) + } + })() } } async openAPIRefreshStatus(): Promise { - await this.debugLog('openAPIRefreshStatus'); + await this.debugLog('openAPIRefreshStatus') try { - const { body, statusCode } = await this.deviceRefreshStatus(); - const deviceStatus: any = await body.json(); - await this.debugLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`);; + const { body, statusCode } = await this.deviceRefreshStatus() + const deviceStatus: any = await body.json() + await this.debugLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`) if (await this.successfulStatusCodes(statusCode, deviceStatus)) { - await this.debugSuccessLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`); - this.deviceStatus = deviceStatus.body; - await this.openAPIparseStatus(); - await this.updateHomeKitCharacteristics(); + await this.debugSuccessLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`) + this.deviceStatus = deviceStatus.body + await this.openAPIparseStatus() + await this.updateHomeKitCharacteristics() } else { - await this.debugWarnLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`); - await this.debugWarnLog(statusCode, deviceStatus); + await this.debugWarnLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`) + await this.debugWarnLog(statusCode, deviceStatus) } } catch (e: any) { - await this.apiError(e); - await this.errorLog(`failed openAPIRefreshStatus with ${this.device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`); + await this.apiError(e) + await this.errorLog(`failed openAPIRefreshStatus with ${this.device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`) } } async registerWebhook() { if (this.device.webhook) { - await this.debugLog('is listening webhook.'); + await this.debugLog('is listening webhook.') this.platform.webhookEventHandler[this.device.deviceId] = async (context: curtainWebhookContext | curtain3WebhookContext) => { try { - await this.debugLog(`received Webhook: ${JSON.stringify(context)}`); - this.webhookContext = context; - await this.parseStatusWebhook(); - await this.updateHomeKitCharacteristics(); + await this.debugLog(`received Webhook: ${JSON.stringify(context)}`) + this.webhookContext = context + await this.parseStatusWebhook() + await this.updateHomeKitCharacteristics() } catch (e: any) { - await this.errorLog(`failed to handle webhook. Received: ${JSON.stringify(context)} Error: ${e}`); + await this.errorLog(`failed to handle webhook. Received: ${JSON.stringify(context)} Error: ${e}`) } - }; + } } else { - await this.debugLog('is not listening webhook.'); + await this.debugLog('is not listening webhook.') + } + } + + async registerPlatformBLE(): Promise { + await this.debugLog('registerPlatformBLE') + if (this.config.options?.BLE) { + await this.debugLog('is listening to Platform BLE.') + this.device.bleMac = this.device.deviceId!.match(/.{1,2}/g)!.join(':').toLowerCase() + await this.debugLog(`bleMac: ${this.device.bleMac}`) + this.platform.bleEventHandler[this.device.bleMac] = async (context: curtainServiceData | curtain3ServiceData) => { + try { + await this.debugLog(`received BLE: ${JSON.stringify(context)}`) + this.serviceData = context + await this.BLEparseStatus() + await this.updateHomeKitCharacteristics() + } catch (e: any) { + await this.errorLog(`failed to handle BLE. Received: ${JSON.stringify(context)} Error: ${e}`) + } + } + } else { + await this.debugLog('is not listening to Platform BLE') } } @@ -520,32 +592,32 @@ export class Curtain extends deviceBase { */ async pushChanges(): Promise { if (!this.device.enableCloudService && this.OpenAPI) { - await this.errorLog(`pushChanges enableCloudService: ${this.device.enableCloudService}`); + await this.errorLog(`pushChanges enableCloudService: ${this.device.enableCloudService}`) } else if (this.BLE) { - await this.BLEpushChanges(); + await this.BLEpushChanges() } else if (this.OpenAPI && this.platform.config.credentials?.token) { - await this.openAPIpushChanges(); + await this.openAPIpushChanges() } else { - await this.offlineOff(); - await this.debugWarnLog(`Connection Type: ${this.device.connectionType}, pushChanges will not happen.`); + await this.offlineOff() + await this.debugWarnLog(`Connection Type: ${this.device.connectionType}, pushChanges will not happen.`) } // Refresh the status from the API interval(15000) .pipe(skipWhile(() => this.curtainUpdateInProgress)) .pipe(take(1)) .subscribe(async () => { - await this.refreshStatus(); - }); + await this.refreshStatus() + }) } async BLEpushChanges(): Promise { - await this.debugLog('BLEpushChanges'); + await this.debugLog('BLEpushChanges') if (this.WindowCovering.TargetPosition !== this.WindowCovering.CurrentPosition) { - const switchbot = await this.platform.connectBLE(this.accessory, this.device); - await this.convertBLEAddress(); - const { setPositionMode, Mode }: { setPositionMode: number; Mode: string; } = await this.setPerformance(); - const adjustedMode = setPositionMode === 1 ? 0x01 : 0xff; - await this.debugLog(`Mode: ${Mode}, setPositionMode: ${setPositionMode}`); + const switchbot = await this.platform.connectBLE(this.accessory, this.device) + await this.convertBLEAddress() + const { setPositionMode, Mode }: { setPositionMode: number, Mode: string } = await this.setPerformance() + const adjustedMode = setPositionMode === 1 ? 0x01 : 0xFF + await this.debugLog(`Mode: ${Mode}, setPositionMode: ${setPositionMode}`) if (switchbot !== false) { switchbot .discover({ model: this.device.bleModel, quick: true, id: this.device.bleMac }) @@ -553,70 +625,68 @@ export class Curtain extends deviceBase { return await this.retryBLE({ max: await this.maxRetryBLE(), fn: async () => { - return await device_list[0].runToPos(100 - Number(this.WindowCovering.TargetPosition), adjustedMode); + return await device_list[0].runToPos(100 - Number(this.WindowCovering.TargetPosition), adjustedMode) }, - }); + }) }) .then(async () => { - await this.successLog(`TargetPostion: ${this.WindowCovering.TargetPosition} sent over SwitchBot BLE, sent successfully`); - await this.updateHomeKitCharacteristics(); + await this.successLog(`TargetPostion: ${this.WindowCovering.TargetPosition} sent over SwitchBot BLE, sent successfully`) + await this.updateHomeKitCharacteristics() }) .catch(async (e: any) => { - await this.apiError(e); - await this.errorLog(`failed BLEpushChanges with ${this.device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`); - await this.BLEPushConnection(); - }); + await this.apiError(e) + await this.errorLog(`failed BLEpushChanges with ${this.device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`) + await this.BLEPushConnection() + }) } else { - await this.errorLog(`wasn't able to establish BLE Connection, node-switchbot: ${switchbot}`); - await this.BLEPushConnection(); + await this.errorLog(`wasn't able to establish BLE Connection, node-switchbot: ${switchbot}`) + await this.BLEPushConnection() } } else { - await this.debugLog(`No changes (BLEpushChanges), TargetPosition: ${this.WindowCovering.TargetPosition},` - + ` CurrentPosition: ${this.WindowCovering.CurrentPosition}`); + await this.debugLog(`No changes (BLEpushChanges), TargetPosition: ${this.WindowCovering.TargetPosition}, CurrentPosition: ${this.WindowCovering.CurrentPosition}`) } } async openAPIpushChanges(): Promise { - await this.debugLog('openAPIpushChanges'); + await this.debugLog('openAPIpushChanges') if (this.WindowCovering.TargetPosition !== this.WindowCovering.CurrentPosition || this.device.disableCaching) { - await this.debugLog(`Pushing ${this.WindowCovering.TargetPosition}`); - const adjustedTargetPosition = 100 - Number(this.WindowCovering.TargetPosition); - const { setPositionMode, Mode }: { setPositionMode: number; Mode: string; } = await this.setPerformance(); - await this.debugLog(`Mode: ${Mode}, setPositionMode: ${setPositionMode}`); - const adjustedMode = setPositionMode || 'ff'; - let bodyChange: string; + await this.debugLog(`Pushing ${this.WindowCovering.TargetPosition}`) + const adjustedTargetPosition = 100 - Number(this.WindowCovering.TargetPosition) + const { setPositionMode, Mode }: { setPositionMode: number, Mode: string } = await this.setPerformance() + await this.debugLog(`Mode: ${Mode}, setPositionMode: ${setPositionMode}`) + const adjustedMode = setPositionMode || 'ff' + let bodyChange: string if (this.WindowCovering.HoldPosition) { bodyChange = JSON.stringify({ command: 'pause', parameter: 'default', commandType: 'command', - }); + }) } else { bodyChange = JSON.stringify({ command: 'setPosition', parameter: `0,${adjustedMode},${adjustedTargetPosition}`, commandType: 'command', - }); + }) } - await this.debugLog(`SwitchBot OpenAPI bodyChange: ${JSON.stringify(bodyChange)}`); + await this.debugLog(`SwitchBot OpenAPI bodyChange: ${JSON.stringify(bodyChange)}`) try { - const { body, statusCode } = await this.pushChangeRequest(bodyChange); - const deviceStatus: any = await body.json(); - await this.debugLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`); + const { body, statusCode } = await this.pushChangeRequest(bodyChange) + const deviceStatus: any = await body.json() + await this.debugLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`) if (await this.successfulStatusCodes(statusCode, deviceStatus)) { - await this.debugSuccessLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`); - await this.updateHomeKitCharacteristics(); + await this.debugSuccessLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`) + await this.updateHomeKitCharacteristics() } else { - await this.statusCode(statusCode); - await this.statusCode(deviceStatus.statusCode); + await this.statusCode(statusCode) + await this.statusCode(deviceStatus.statusCode) } } catch (e: any) { - await this.apiError(e); - await this.errorLog(`failed openAPIpushChanges with ${this.device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`); + await this.apiError(e) + await this.errorLog(`failed openAPIpushChanges with ${this.device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`) } } else { - await this.debugLog(`No changes (openAPIpushChanges), CurrentPosition: ${this.WindowCovering.CurrentPosition},` - + ` TargetPosition: ${this.WindowCovering.TargetPosition}`); + await this.debugLog(`No changes (openAPIpushChanges), CurrentPosition: ${this.WindowCovering.CurrentPosition}, TargetPosition: ${this.WindowCovering.TargetPosition}`) } } @@ -625,206 +695,229 @@ export class Curtain extends deviceBase { */ async TargetPositionSet(value: CharacteristicValue): Promise { if (this.WindowCovering.TargetPosition !== this.accessory.context.TargetPosition) { - await this.infoLog(`Set TargetPosition: ${value}`); + await this.infoLog(`Set TargetPosition: ${value}`) } else { - await this.debugLog(`No Changes, TargetPosition: ${value}`); + await this.debugLog(`No Changes, TargetPosition: ${value}`) } // Set HoldPosition to false when TargetPosition is changed - this.WindowCovering.HoldPosition = false; - this.WindowCovering.Service.updateCharacteristic(this.hap.Characteristic.HoldPosition, this.WindowCovering.HoldPosition); + this.WindowCovering.HoldPosition = false + this.WindowCovering.Service.updateCharacteristic(this.hap.Characteristic.HoldPosition, this.WindowCovering.HoldPosition) - this.WindowCovering.TargetPosition = value; - await this.mqtt('TargetPosition', this.WindowCovering.TargetPosition); - await this.mqtt('HoldPosition', this.WindowCovering.HoldPosition); - await this.startUpdatingCurtainIfNeeded(); + this.WindowCovering.TargetPosition = value + await this.mqtt('TargetPosition', this.WindowCovering.TargetPosition) + await this.mqtt('HoldPosition', this.WindowCovering.HoldPosition) + await this.startUpdatingCurtainIfNeeded() } async startUpdatingCurtainIfNeeded() { - await this.setMinMax(); + await this.setMinMax() if (this.WindowCovering.TargetPosition > this.WindowCovering.CurrentPosition) { - this.WindowCovering.PositionState = this.hap.Characteristic.PositionState.INCREASING; - this.setNewTarget = true; - await this.debugLog(`value: ${this.WindowCovering.TargetPosition}, CurrentPosition: ${this.WindowCovering.CurrentPosition}`); + this.WindowCovering.PositionState = this.hap.Characteristic.PositionState.INCREASING + this.setNewTarget = true + await this.debugLog(`value: ${this.WindowCovering.TargetPosition}, CurrentPosition: ${this.WindowCovering.CurrentPosition}`) } else if (this.WindowCovering.TargetPosition < this.WindowCovering.CurrentPosition) { - this.WindowCovering.PositionState = this.hap.Characteristic.PositionState.DECREASING; - this.setNewTarget = true; - await this.debugLog(`value: ${this.WindowCovering.TargetPosition}, CurrentPosition: ${this.WindowCovering.CurrentPosition}`); + this.WindowCovering.PositionState = this.hap.Characteristic.PositionState.DECREASING + this.setNewTarget = true + await this.debugLog(`value: ${this.WindowCovering.TargetPosition}, CurrentPosition: ${this.WindowCovering.CurrentPosition}`) } else { - this.WindowCovering.PositionState = this.hap.Characteristic.PositionState.STOPPED; - this.setNewTarget = false; - await this.debugLog(`value: ${this.WindowCovering.TargetPosition}, CurrentPosition: ${this.WindowCovering.CurrentPosition}`); + this.WindowCovering.PositionState = this.hap.Characteristic.PositionState.STOPPED + this.setNewTarget = false + await this.debugLog(`value: ${this.WindowCovering.TargetPosition}, CurrentPosition: ${this.WindowCovering.CurrentPosition}`) } - this.WindowCovering.Service.setCharacteristic(this.hap.Characteristic.PositionState, this.WindowCovering.PositionState); - this.WindowCovering.Service.getCharacteristic(this.hap.Characteristic.PositionState).updateValue(this.WindowCovering.PositionState); + this.WindowCovering.Service.setCharacteristic(this.hap.Characteristic.PositionState, this.WindowCovering.PositionState) + this.WindowCovering.Service.getCharacteristic(this.hap.Characteristic.PositionState).updateValue(this.WindowCovering.PositionState) /** * If Curtain movement time is short, the moving flag from backend is always false. * The minimum time depends on the network control latency. */ - clearTimeout(this.setNewTargetTimer); - await this.debugLog(`deviceUpdateRate: ${this.deviceUpdateRate}`); + clearTimeout(this.setNewTargetTimer) + await this.debugLog(`deviceUpdateRate: ${this.deviceUpdateRate}`) if (this.setNewTarget) { this.setNewTargetTimer = setTimeout(async () => { - await this.debugLog(`setNewTarget ${this.setNewTarget} timeout`); - this.setNewTarget = false; - }, this.deviceUpdateRate * 1000); + await this.debugLog(`setNewTarget ${this.setNewTarget} timeout`) + this.setNewTarget = false + }, this.deviceUpdateRate * 1000) } - this.doCurtainUpdate.next(); + this.doCurtainUpdate.next() } /** * Handle requests to set the value of the "Target Position" characteristic */ async HoldPositionSet(value: CharacteristicValue): Promise { - this.debugLog(`HoldPosition: ${value}`); - this.WindowCovering.HoldPosition = value; - this.doCurtainUpdate.next(); + this.debugLog(`HoldPosition: ${value}`) + this.WindowCovering.HoldPosition = value + this.doCurtainUpdate.next() + } + + /** + * Handle requests to set the value of the "Target Position" characteristic + */ + async OpenModeSwitchSet(value: CharacteristicValue): Promise { + if (this.OpenModeSwitch && this.device.curtain?.silentModeSwitch) { + this.debugLog(`Silent Open Mode: ${value}`) + this.OpenModeSwitch.On = value + this.accessory.context.OpenModeSwitch.On = value + if (value === true) { + this.infoLog('Silent Open Mode is enabled') + } + } + } + + /** + * Handle requests to set the value of the "Target Position" characteristic + */ + async CloseModeSwitchSet(value: CharacteristicValue): Promise { + if (this.CloseModeSwitch && this.device.curtain?.silentModeSwitch) { + this.debugLog(`Silent Close Mode: ${value}`) + this.CloseModeSwitch.On = value + this.accessory.context.CloseModeSwitch.On = value + if (value === true) { + this.infoLog('Silent Close Mode is enabled') + } + } } async updateHomeKitCharacteristics(): Promise { - await this.setMinMax(); + await this.setMinMax() // CurrentPosition - await this.updateCharacteristic(this.WindowCovering.Service, this.hap.Characteristic.CurrentPosition, - this.WindowCovering.CurrentPosition, 'CurrentPosition'); + await this.updateCharacteristic(this.WindowCovering.Service, this.hap.Characteristic.CurrentPosition, this.WindowCovering.CurrentPosition, 'CurrentPosition') // PositionState - await this.updateCharacteristic(this.WindowCovering.Service, this.hap.Characteristic.PositionState, - this.WindowCovering.PositionState, 'PositionState'); + await this.updateCharacteristic(this.WindowCovering.Service, this.hap.Characteristic.PositionState, this.WindowCovering.PositionState, 'PositionState') // TargetPosition - await this.updateCharacteristic(this.WindowCovering.Service, this.hap.Characteristic.TargetPosition, - this.WindowCovering.TargetPosition, 'TargetPosition'); + await this.updateCharacteristic(this.WindowCovering.Service, this.hap.Characteristic.TargetPosition, this.WindowCovering.TargetPosition, 'TargetPosition') // HoldPosition - await this.updateCharacteristic(this.WindowCovering.Service, this.hap.Characteristic.HoldPosition, - this.WindowCovering.HoldPosition, 'HoldPosition'); + await this.updateCharacteristic(this.WindowCovering.Service, this.hap.Characteristic.HoldPosition, this.WindowCovering.HoldPosition, 'HoldPosition') // CurrentAmbientLightLevel if (!this.device.curtain?.hide_lightsensor && this.LightSensor?.Service) { - const history = { time: Math.round(new Date().valueOf() / 1000), lux: this.LightSensor.CurrentAmbientLightLevel }; - await this.updateCharacteristic(this.LightSensor?.Service, this.hap.Characteristic.CurrentAmbientLightLevel, - this.LightSensor?.CurrentAmbientLightLevel, 'CurrentAmbientLightLevel', history); + const history = { time: Math.round(new Date().valueOf() / 1000), lux: this.LightSensor.CurrentAmbientLightLevel } + await this.updateCharacteristic(this.LightSensor?.Service, this.hap.Characteristic.CurrentAmbientLightLevel, this.LightSensor?.CurrentAmbientLightLevel, 'CurrentAmbientLightLevel', history) } // BatteryLevel - await this.updateCharacteristic(this.Battery.Service, this.hap.Characteristic.BatteryLevel, - this.Battery.BatteryLevel, 'BatteryLevel'); + await this.updateCharacteristic(this.Battery.Service, this.hap.Characteristic.BatteryLevel, this.Battery.BatteryLevel, 'BatteryLevel') // StatusLowBattery - await this.updateCharacteristic(this.Battery.Service, this.hap.Characteristic.StatusLowBattery, - this.Battery.StatusLowBattery, 'StatusLowBattery'); + await this.updateCharacteristic(this.Battery.Service, this.hap.Characteristic.StatusLowBattery, this.Battery.StatusLowBattery, 'StatusLowBattery') // ChargingState - await this.updateCharacteristic(this.Battery.Service, this.hap.Characteristic.ChargingState, - this.Battery.ChargingState, 'ChargingState'); + await this.updateCharacteristic(this.Battery.Service, this.hap.Characteristic.ChargingState, this.Battery.ChargingState, 'ChargingState') } async BLEPushConnection() { if (this.platform.config.credentials?.token && this.device.connectionType === 'BLE/OpenAPI') { - await this.warnLog('Using OpenAPI Connection to Push Changes'); - await this.openAPIpushChanges(); + await this.warnLog('Using OpenAPI Connection to Push Changes') + await this.openAPIpushChanges() } } async BLERefreshConnection(switchbot: any): Promise { - await this.errorLog(`wasn't able to establish BLE Connection, node-switchbot: ${switchbot}`); + await this.errorLog(`wasn't able to establish BLE Connection, node-switchbot: ${switchbot}`) if (this.platform.config.credentials?.token && this.device.connectionType === 'BLE/OpenAPI') { - await this.warnLog('Using OpenAPI Connection to Refresh Status'); - await this.openAPIRefreshStatus(); + await this.warnLog('Using OpenAPI Connection to Refresh Status') + await this.openAPIRefreshStatus() } } async setPerformance() { - let setPositionMode: number; - let Mode: string; + let setPositionMode: number + let Mode: string if (Number(this.WindowCovering.TargetPosition) > 50) { - if (this.device.curtain?.setOpenMode === '1') { - setPositionMode = 1; - Mode = 'Silent Mode'; - } else if (this.device.curtain?.setOpenMode === '0') { - setPositionMode = 0; - Mode = 'Performance Mode'; + if (this.device.curtain?.setOpenMode === '1' || this.OpenModeSwitch?.On) { + setPositionMode = 1 + Mode = 'Silent Mode' + } else if (this.device.curtain?.setOpenMode === '0' || !this.OpenModeSwitch?.On) { + setPositionMode = 0 + Mode = 'Performance Mode' } else { - setPositionMode = 0; - Mode = 'Default Mode'; + setPositionMode = 0 + Mode = 'Default Mode' } } else { - if (this.device.curtain?.setCloseMode === '1') { - setPositionMode = 1; - Mode = 'Silent Mode'; - } else if (this.device.curtain?.setOpenMode === '0') { - setPositionMode = 0; - Mode = 'Performance Mode'; + if (this.device.curtain?.setCloseMode === '1' || this.CloseModeSwitch?.On) { + setPositionMode = 1 + Mode = 'Silent Mode' + } else if (this.device.curtain?.setCloseMode === '0' || !this.CloseModeSwitch?.On) { + setPositionMode = 0 + Mode = 'Performance Mode' } else { - setPositionMode = 0; - Mode = 'Default Mode'; + setPositionMode = 0 + Mode = 'Default Mode' } } - return { setPositionMode, Mode }; + this.infoLog(`Position Mode: ${setPositionMode}, Mode: ${Mode}`) + return { setPositionMode, Mode } } async getCurrentPostion() { - await this.setMinMax(); - await this.debugLog(`CurrentPosition ${this.WindowCovering.CurrentPosition}`); + await this.setMinMax() + await this.debugLog(`CurrentPosition ${this.WindowCovering.CurrentPosition}`) if (this.setNewTarget) { - this.infoLog('Checking Status ...'); - await this.setMinMax(); + this.infoLog('Checking Status ...') + this.curtainMoving = true + await this.setMinMax() if (this.WindowCovering.TargetPosition > this.WindowCovering.CurrentPosition) { - await this.debugLog(`Closing, CurrentPosition: ${this.WindowCovering.CurrentPosition}`); - this.WindowCovering.PositionState = this.hap.Characteristic.PositionState.INCREASING; - this.WindowCovering.Service.getCharacteristic(this.hap.Characteristic.PositionState).updateValue(this.WindowCovering.PositionState); - await this.debugLog(`Increasing, PositionState: ${this.WindowCovering.PositionState}`); + await this.debugLog(`Closing, CurrentPosition: ${this.WindowCovering.CurrentPosition}`) + this.WindowCovering.PositionState = this.hap.Characteristic.PositionState.INCREASING + this.WindowCovering.Service.getCharacteristic(this.hap.Characteristic.PositionState).updateValue(this.WindowCovering.PositionState) + await this.debugLog(`Increasing, PositionState: ${this.WindowCovering.PositionState}`) } else if (this.WindowCovering.TargetPosition < this.WindowCovering.CurrentPosition) { - await this.debugLog(`Opening, CurrentPosition: ${this.WindowCovering.CurrentPosition}`); - this.WindowCovering.PositionState = this.hap.Characteristic.PositionState.DECREASING; - this.WindowCovering.Service.getCharacteristic(this.hap.Characteristic.PositionState).updateValue(this.WindowCovering.PositionState); - await this.debugLog(`Decreasing, PositionState: ${this.WindowCovering.PositionState}`); + await this.debugLog(`Opening, CurrentPosition: ${this.WindowCovering.CurrentPosition}`) + this.WindowCovering.PositionState = this.hap.Characteristic.PositionState.DECREASING + this.WindowCovering.Service.getCharacteristic(this.hap.Characteristic.PositionState).updateValue(this.WindowCovering.PositionState) + await this.debugLog(`Decreasing, PositionState: ${this.WindowCovering.PositionState}`) } else { - await this.debugLog(`Standby, CurrentPosition: ${this.WindowCovering.CurrentPosition}`); - this.WindowCovering.PositionState = this.hap.Characteristic.PositionState.STOPPED; - this.WindowCovering.Service.getCharacteristic(this.hap.Characteristic.PositionState).updateValue(this.WindowCovering.PositionState); - await this.debugLog(`Stopped, PositionState: ${this.WindowCovering.PositionState}`); + await this.debugLog(`Standby, CurrentPosition: ${this.WindowCovering.CurrentPosition}`) + this.WindowCovering.PositionState = this.hap.Characteristic.PositionState.STOPPED + this.WindowCovering.Service.getCharacteristic(this.hap.Characteristic.PositionState).updateValue(this.WindowCovering.PositionState) + await this.debugLog(`Stopped, PositionState: ${this.WindowCovering.PositionState}`) } } else { - await this.debugLog(`Standby, CurrentPosition: ${this.WindowCovering.CurrentPosition}`); - this.WindowCovering.TargetPosition = this.WindowCovering.CurrentPosition; - this.WindowCovering.PositionState = this.hap.Characteristic.PositionState.STOPPED; - await this.debugLog(`Stopped, PositionState: ${this.WindowCovering.PositionState}`); + await this.infoLog('Standby ...') + this.curtainMoving = false + await this.debugLog(`Standby, CurrentPosition: ${this.WindowCovering.CurrentPosition}`) + this.WindowCovering.TargetPosition = this.WindowCovering.CurrentPosition + this.WindowCovering.PositionState = this.hap.Characteristic.PositionState.STOPPED + await this.debugLog(`Stopped, PositionState: ${this.WindowCovering.PositionState}`) } - await this.debugLog(`CurrentPosition: ${this.WindowCovering.CurrentPosition}, TargetPosition: ${this.WindowCovering.TargetPosition},` - + ` PositionState: ${this.WindowCovering.PositionState},`); + await this.debugLog(`CurrentPosition: ${this.WindowCovering.CurrentPosition}, TargetPosition: ${this.WindowCovering.TargetPosition}, PositionState: ${this.WindowCovering.PositionState},`) } async setMinMax(): Promise { if (this.device.curtain?.set_min) { if (Number(this.WindowCovering.CurrentPosition) <= this.device.curtain?.set_min) { - this.WindowCovering.CurrentPosition = 0; + this.WindowCovering.CurrentPosition = 0 } } if (this.device.curtain?.set_max) { if (Number(this.WindowCovering.CurrentPosition) >= this.device.curtain?.set_max) { - this.WindowCovering.CurrentPosition = 100; + this.WindowCovering.CurrentPosition = 100 } } if (this.device.history) { - const motion = this.accessory.getService(this.hap.Service.MotionSensor); - const state = Number(this.WindowCovering.CurrentPosition) > 0 ? 1 : 0; - motion?.updateCharacteristic(this.hap.Characteristic.MotionDetected, state); + const motion = this.accessory.getService(this.hap.Service.MotionSensor) + const state = Number(this.WindowCovering.CurrentPosition) > 0 ? 1 : 0 + motion?.updateCharacteristic(this.hap.Characteristic.MotionDetected, state) } } async offlineOff(): Promise { if (this.device.offline) { - this.WindowCovering.Service.updateCharacteristic(this.hap.Characteristic.CurrentPosition, 100); - this.WindowCovering.Service.updateCharacteristic(this.hap.Characteristic.TargetPosition, 100); - this.WindowCovering.Service.updateCharacteristic(this.hap.Characteristic.PositionState, this.hap.Characteristic.PositionState.STOPPED); + this.WindowCovering.Service.updateCharacteristic(this.hap.Characteristic.CurrentPosition, 100) + this.WindowCovering.Service.updateCharacteristic(this.hap.Characteristic.TargetPosition, 100) + this.WindowCovering.Service.updateCharacteristic(this.hap.Characteristic.PositionState, this.hap.Characteristic.PositionState.STOPPED) } } async apiError(e: any): Promise { - this.WindowCovering.Service.updateCharacteristic(this.hap.Characteristic.CurrentPosition, e); - this.WindowCovering.Service.updateCharacteristic(this.hap.Characteristic.PositionState, e); - this.WindowCovering.Service.updateCharacteristic(this.hap.Characteristic.TargetPosition, e); - this.Battery.Service.updateCharacteristic(this.hap.Characteristic.BatteryLevel, e); - this.Battery.Service.updateCharacteristic(this.hap.Characteristic.StatusLowBattery, e); - this.Battery.Service.updateCharacteristic(this.hap.Characteristic.ChargingState, e); + this.WindowCovering.Service.updateCharacteristic(this.hap.Characteristic.CurrentPosition, e) + this.WindowCovering.Service.updateCharacteristic(this.hap.Characteristic.PositionState, e) + this.WindowCovering.Service.updateCharacteristic(this.hap.Characteristic.TargetPosition, e) + this.Battery.Service.updateCharacteristic(this.hap.Characteristic.BatteryLevel, e) + this.Battery.Service.updateCharacteristic(this.hap.Characteristic.StatusLowBattery, e) + this.Battery.Service.updateCharacteristic(this.hap.Characteristic.ChargingState, e) if (!this.device.curtain?.hide_lightsensor && this.LightSensor?.Service) { - this.LightSensor.Service.updateCharacteristic(this.hap.Characteristic.CurrentAmbientLightLevel, e); - this.LightSensor.Service.updateCharacteristic(this.hap.Characteristic.StatusActive, e); + this.LightSensor.Service.updateCharacteristic(this.hap.Characteristic.CurrentAmbientLightLevel, e) + this.LightSensor.Service.updateCharacteristic(this.hap.Characteristic.StatusActive, e) } } } diff --git a/src/device/device.ts b/src/device/device.ts index 53d63229..92c269ff 100644 --- a/src/device/device.ts +++ b/src/device/device.ts @@ -3,76 +3,79 @@ * device.ts: @switchbot/homebridge-switchbot. */ -import { hostname } from 'os'; -import { request } from 'undici'; -import { Devices } from '../settings.js'; -import { BlindTiltMappingMode, sleep } from '../utils.js'; -import { SwitchBotModel, SwitchBotBLEModel, SwitchBotBLEModelName, SwitchBotBLEModelFriendlyName } from 'node-switchbot'; - -import type { MqttClient } from 'mqtt'; -import type { device } from '../types/devicelist.js'; -import type { ad } from '../types/bledevicestatus.js'; -import type { SwitchBotPlatform } from '../platform.js'; -import type { SwitchBotPlatformConfig, devicesConfig } from '../settings.js'; -import type { API, CharacteristicValue, HAP, Logging, PlatformAccessory, Service } from 'homebridge'; +import type { API, CharacteristicValue, HAP, Logging, PlatformAccessory, Service } from 'homebridge' +import type { MqttClient } from 'mqtt' + +import type { SwitchBotPlatform } from '../platform.js' +import type { devicesConfig, SwitchBotPlatformConfig } from '../settings.js' +import type { ad } from '../types/bledevicestatus.js' +import type { device } from '../types/devicelist.js' + +import { hostname } from 'node:os' + +import { SwitchBotBLEModel, SwitchBotBLEModelFriendlyName, SwitchBotBLEModelName, SwitchBotModel } from 'node-switchbot' +import { request } from 'undici' + +import { Devices } from '../settings.js' +import { BlindTiltMappingMode, sleep } from '../utils.js' export abstract class deviceBase { - public readonly api: API; - public readonly log: Logging; - public readonly config!: SwitchBotPlatformConfig; - protected readonly hap: HAP; + public readonly api: API + public readonly log: Logging + public readonly config!: SwitchBotPlatformConfig + protected readonly hap: HAP // Config - protected deviceLogging!: string; - protected deviceRefreshRate!: number; - protected deviceUpdateRate!: number; - protected devicePushRate!: number; - protected deviceMaxRetries!: number; - protected deviceDelayBetweenRetries!: number; + protected deviceLogging!: string + protected deviceRefreshRate!: number + protected deviceUpdateRate!: number + protected devicePushRate!: number + protected deviceMaxRetries!: number + protected deviceDelayBetweenRetries!: number // Connection - protected readonly BLE: boolean; - protected readonly OpenAPI: boolean; + protected readonly BLE: boolean + protected readonly OpenAPI: boolean // Accsrroy Information - protected deviceModel!: SwitchBotModel; - protected deviceBLEModel!: SwitchBotBLEModel; + protected deviceModel!: SwitchBotModel + protected deviceBLEModel!: SwitchBotBLEModel // MQTT - protected deviceMqttURL!: string; - protected deviceMqttOptions!: any; - protected deviceMqttPubOptions!: any; + protected deviceMqttURL!: string + protected deviceMqttOptions!: any + protected deviceMqttPubOptions!: any // BLE - protected scanDuration!: number; + protected scanDuration!: number // EVE history service handler - protected historyService?: any = null; + protected historyService?: any = null - //MQTT stuff - protected mqttClient: MqttClient | null = null; + // MQTT stuff + protected mqttClient: MqttClient | null = null constructor( protected readonly platform: SwitchBotPlatform, protected accessory: PlatformAccessory, protected device: device & devicesConfig, ) { - this.api = this.platform.api; - this.log = this.platform.log; - this.config = this.platform.config; - this.hap = this.api.hap; + this.api = this.platform.api + this.log = this.platform.log + this.config = this.platform.config + this.hap = this.api.hap // Connection - this.BLE = this.device.connectionType === 'BLE' || this.device.connectionType === 'BLE/OpenAPI'; - this.OpenAPI = this.device.connectionType === 'OpenAPI' || this.device.connectionType === 'BLE/OpenAPI'; + this.BLE = this.device.connectionType === 'BLE' || this.device.connectionType === 'BLE/OpenAPI' + this.OpenAPI = this.device.connectionType === 'OpenAPI' || this.device.connectionType === 'BLE/OpenAPI' - this.getDeviceLogSettings(accessory, device); - this.getDeviceRateSettings(accessory, device); - this.getDeviceRetry(device); - this.getDeviceConfigSettings(device); - this.getDeviceContext(accessory, device); - this.getDeviceScanDuration(accessory, device); - this.getMqttSettings(device); + this.getDeviceLogSettings(accessory, device) + this.getDeviceRateSettings(accessory, device) + this.getDeviceRetry(device) + this.getDeviceConfigSettings(device) + this.getDeviceContext(accessory, device) + this.getDeviceScanDuration(accessory, device) + this.getMqttSettings(device) // Set accessory information accessory @@ -83,216 +86,146 @@ export abstract class deviceBase { .setCharacteristic(this.hap.Characteristic.ConfiguredName, accessory.displayName) .setCharacteristic(this.hap.Characteristic.Model, device.model) .setCharacteristic(this.hap.Characteristic.ProductData, device.deviceId) - .setCharacteristic(this.hap.Characteristic.SerialNumber, device.deviceId); + .setCharacteristic(this.hap.Characteristic.SerialNumber, device.deviceId) } async getDeviceLogSettings(accessory: PlatformAccessory, device: device & devicesConfig): Promise { - this.deviceLogging = this.platform.debugMode ? 'debugMode' : device.logging ?? this.config.logging ?? 'standard'; - const logging = this.platform.debugMode ? 'Debug Mode' : device.logging ? 'Device Config' : this.config.logging ? 'Platform Config' : 'Default'; - accessory.context.deviceLogging = this.deviceLogging; - await this.debugLog(`Using ${logging} Logging: ${this.deviceLogging}`); + this.deviceLogging = this.platform.debugMode ? 'debugMode' : device.logging ?? this.config.logging ?? 'standard' + const logging = this.platform.debugMode ? 'Debug Mode' : device.logging ? 'Device Config' : this.config.logging ? 'Platform Config' : 'Default' + accessory.context.deviceLogging = this.deviceLogging + await this.debugLog(`Using ${logging} Logging: ${this.deviceLogging}`) } async getDeviceRateSettings(accessory: PlatformAccessory, device: device & devicesConfig): Promise { // refreshRate - this.deviceRefreshRate = device.refreshRate ?? this.config.options?.refreshRate ?? 5; - accessory.context.deviceRefreshRate = this.deviceRefreshRate; - const refreshRate = device.refreshRate ? 'Device Config' : this.config.options?.refreshRate ? 'Platform Config' : 'Default'; + this.deviceRefreshRate = device.refreshRate ?? this.config.options?.refreshRate ?? 5 + accessory.context.deviceRefreshRate = this.deviceRefreshRate + const refreshRate = device.refreshRate ? 'Device Config' : this.config.options?.refreshRate ? 'Platform Config' : 'Default' // updateRate - this.deviceUpdateRate = device.updateRate ?? this.config.options?.updateRate ?? 5; - accessory.context.deviceUpdateRate = this.deviceUpdateRate; - const updateRate = device.updateRate ? 'Device Config' : this.config.options?.updateRate ? 'Platform Config' : 'Default'; + this.deviceUpdateRate = device.updateRate ?? this.config.options?.updateRate ?? 5 + accessory.context.deviceUpdateRate = this.deviceUpdateRate + const updateRate = device.updateRate ? 'Device Config' : this.config.options?.updateRate ? 'Platform Config' : 'Default' // pushRate - this.devicePushRate = device.pushRate ?? this.config.options?.pushRate ?? 1; - accessory.context.devicePushRate = this.devicePushRate; - const pushRate = device.pushRate ? 'Device Config' : this.config.options?.pushRate ? 'Platform Config' : 'Default'; - await this.debugLog(`Using ${refreshRate} refreshRate: ${this.deviceRefreshRate}, ${updateRate} updateRate: ${this.deviceUpdateRate},` - + ` ${pushRate} pushRate: ${this.devicePushRate}`); + this.devicePushRate = device.pushRate ?? this.config.options?.pushRate ?? 1 + accessory.context.devicePushRate = this.devicePushRate + const pushRate = device.pushRate ? 'Device Config' : this.config.options?.pushRate ? 'Platform Config' : 'Default' + await this.debugLog(`Using ${refreshRate} refreshRate: ${this.deviceRefreshRate}, ${updateRate} updateRate: ${this.deviceUpdateRate}, ${pushRate} pushRate: ${this.devicePushRate}`) } async getDeviceRetry(device: device & devicesConfig): Promise { - this.deviceMaxRetries = device.maxRetries ?? 5; - const maxRetries = device.maxRetries ? 'Device' : 'Default'; - this.deviceDelayBetweenRetries = device.delayBetweenRetries ? (device.delayBetweenRetries * 1000) : 3000; - const delayBetweenRetries = device.delayBetweenRetries ? 'Device' : 'Default'; - await this.debugLog(`Using ${maxRetries} Max Retries: ${this.deviceMaxRetries},` - + ` ${delayBetweenRetries} Delay Between Retries: ${this.deviceDelayBetweenRetries}`); + this.deviceMaxRetries = device.maxRetries ?? 5 + const maxRetries = device.maxRetries ? 'Device' : 'Default' + this.deviceDelayBetweenRetries = device.delayBetweenRetries ? (device.delayBetweenRetries * 1000) : 3000 + const delayBetweenRetries = device.delayBetweenRetries ? 'Device' : 'Default' + await this.debugLog(`Using ${maxRetries} Max Retries: ${this.deviceMaxRetries}, ${delayBetweenRetries} Delay Between Retries: ${this.deviceDelayBetweenRetries}`) } - async retryBLE({ max, fn }: { max: number; fn: { (): any; (): Promise } }): Promise { + async retryBLE({ max, fn }: { max: number, fn: { (): any, (): Promise } }): Promise { return fn().catch(async (e: any) => { if (max === 0) { - throw e; + throw e } - await this.warnLog(e); - await this.infoLog('Retrying'); - await sleep(1000); - return this.retryBLE({ max: max - 1, fn }); - }); + await this.warnLog(e) + await this.infoLog('Retrying') + await sleep(1000) + return this.retryBLE({ max: max - 1, fn }) + }) } async maxRetryBLE(): Promise { - return this.device.maxRetry ? this.device.maxRetry : 5; + return this.device.maxRetry ? this.device.maxRetry : 5 } async getDeviceScanDuration(accessory: PlatformAccessory, device: device & devicesConfig): Promise { - this.scanDuration = device.scanDuration ? (this.deviceUpdateRate > device.scanDuration) ? this.deviceUpdateRate : device.scanDuration - ? (this.deviceUpdateRate > 1) ? this.deviceUpdateRate : 1 : this.deviceUpdateRate : 1; + this.scanDuration = device.scanDuration + ? (this.deviceUpdateRate > device.scanDuration) ? this.deviceUpdateRate : device.scanDuration ? (this.deviceUpdateRate > 1) ? this.deviceUpdateRate : 1 : this.deviceUpdateRate + : 1 if (device.scanDuration) { if (this.deviceUpdateRate > device.scanDuration) { - this.scanDuration = this.deviceUpdateRate; + this.scanDuration = this.deviceUpdateRate if (this.BLE) { - this.warnLog('scanDuration is less than updateRate, overriding scanDuration with updateRate'); + this.warnLog('scanDuration is less than updateRate, overriding scanDuration with updateRate') } } else { - this.scanDuration = accessory.context.scanDuration = device.scanDuration; + this.scanDuration = accessory.context.scanDuration = device.scanDuration } if (this.BLE) { - this.debugLog(`Using Device Config scanDuration: ${this.scanDuration}`); + this.debugLog(`Using Device Config scanDuration: ${this.scanDuration}`) } } else { if (this.deviceUpdateRate > 1) { - this.scanDuration = this.deviceUpdateRate; + this.scanDuration = this.deviceUpdateRate } else { - this.scanDuration = accessory.context.scanDuration = 1; + this.scanDuration = accessory.context.scanDuration = 1 } if (this.BLE) { - this.debugLog(`Using Default scanDuration: ${this.scanDuration}`); + this.debugLog(`Using Default scanDuration: ${this.scanDuration}`) } } } async getDeviceConfigSettings(device: device & devicesConfig): Promise { - const deviceConfig = {}; - if (device.logging !== 'standard') { - deviceConfig['logging'] = device.logging; - } - if (device.refreshRate !== 0) { - deviceConfig['refreshRate'] = device.refreshRate; - } - if (device.updateRate !== 0) { - deviceConfig['updateRate'] = device.updateRate; - } - if (device.scanDuration !== 0) { - deviceConfig['scanDuration'] = device.scanDuration; - } - if (device.offline === true) { - deviceConfig['offline'] = device.offline; - } - if (device.maxRetry !== 0) { - deviceConfig['maxRetry'] = device.maxRetry; - } - if (device.webhook === true) { - deviceConfig['webhook'] = device.webhook; - } - if (device.connectionType !== '') { - deviceConfig['connectionType'] = device.connectionType; - } - if (device.external === true) { - deviceConfig['external'] = device.external; - } - if (device.mqttURL !== '') { - deviceConfig['mqttURL'] = device.mqttURL; - } - if (device.mqttOptions) { - deviceConfig['mqttOptions'] = device.mqttOptions; - } - if (device.mqttPubOptions) { - deviceConfig['mqttPubOptions'] = device.mqttPubOptions; - } - if (device.maxRetries !== 0) { - deviceConfig['maxRetries'] = device.maxRetries; - } - if (device.delayBetweenRetries !== 0) { - deviceConfig['delayBetweenRetries'] = device.delayBetweenRetries; - } - let botConfig = {}; - if (device.bot) { - botConfig = device.bot; - } - let lockConfig = {}; - if (device.lock) { - lockConfig = device.lock; - } - let ceilinglightConfig = {}; - if (device.ceilinglight) { - ceilinglightConfig = device.ceilinglight; - } - let colorbulbConfig = {}; - if (device.colorbulb) { - colorbulbConfig = device.colorbulb; - } - let contactConfig = {}; - if (device.contact) { - contactConfig = device.contact; - } - let motionConfig = {}; - if (device.motion) { - motionConfig = device.motion; - } - let curtainConfig = {}; - if (device.curtain) { - curtainConfig = device.curtain; - } - let hubConfig = {}; - if (device.hub) { - hubConfig = device.hub; - } - let waterdetectorConfig = {}; - if (device.waterdetector) { - waterdetectorConfig = device.waterdetector; - } - let humidifierConfig = {}; - if (device.humidifier) { - humidifierConfig = device.humidifier; - } - let meterConfig = {}; - if (device.meter) { - meterConfig = device.meter; - } - let iosensorConfig = {}; - if (device.iosensor) { - iosensorConfig = device.iosensor; - } - let striplightConfig = {}; - if (device.striplight) { - striplightConfig = device.striplight; - } - let plugConfig = {}; - if (device.plug) { - plugConfig = device.plug; - } - let blindTiltConfig = {}; - if (device.blindTilt) { - if (device.blindTilt?.mode === undefined) { - blindTiltConfig['mode'] = BlindTiltMappingMode.OnlyUp; - } - blindTiltConfig = device.blindTilt; - } - const config = Object.assign({}, deviceConfig, botConfig, curtainConfig, waterdetectorConfig, striplightConfig, plugConfig, iosensorConfig, - meterConfig, humidifierConfig, hubConfig, lockConfig, ceilinglightConfig, colorbulbConfig, contactConfig, motionConfig, blindTiltConfig); - if (Object.entries(config).length !== 0) { - this.debugSuccessLog(`Config: ${JSON.stringify(config)}`); + const deviceConfig = Object.assign( + {}, + device.logging !== 'standard' && { logging: device.logging }, + device.refreshRate !== 0 && { refreshRate: device.refreshRate }, + device.updateRate !== 0 && { updateRate: device.updateRate }, + device.scanDuration !== 0 && { scanDuration: device.scanDuration }, + device.offline === true && { offline: device.offline }, + device.maxRetry !== 0 && { maxRetry: device.maxRetry }, + device.webhook === true && { webhook: device.webhook }, + device.connectionType !== '' && { connectionType: device.connectionType }, + device.external === true && { external: device.external }, + device.mqttURL !== '' && { mqttURL: device.mqttURL }, + device.mqttOptions && { mqttOptions: device.mqttOptions }, + device.mqttPubOptions && { mqttPubOptions: device.mqttPubOptions }, + device.maxRetries !== 0 && { maxRetries: device.maxRetries }, + device.delayBetweenRetries !== 0 && { delayBetweenRetries: device.delayBetweenRetries }, + ) + const config = Object.assign( + {}, + deviceConfig, + device.bot, + device.lock, + device.ceilinglight, + device.colorbulb, + device.contact, + device.motion, + device.curtain, + device.hub, + device.waterdetector, + device.humidifier, + device.meter, + device.iosensor, + device.striplight, + device.plug, + device.blindTilt?.mode === undefined ? { mode: BlindTiltMappingMode.OnlyUp } : {}, + device.blindTilt, + ) + + if (Object.keys(config).length !== 0) { + this.debugSuccessLog(`Config: ${JSON.stringify(config)}`) } } /** * Get the current ambient light level based on the light level, set_minLux, set_maxLux, and spaceBetweenLevels. - * @param lightLevel: number - * @param set_minLux: number - * @param set_maxLux: number - * @param spaceBetweenLevels: number + * @param lightLevel number + * @param set_minLux number + * @param set_maxLux number + * @param spaceBetweenLevels number * @returns CurrentAmbientLightLevel */ async getLightLevel(lightLevel: number, set_minLux: number, set_maxLux: number, spaceBetweenLevels: number): Promise { - const numberOfLevels = spaceBetweenLevels + 1; - this.debugLog(`LightLevel: ${lightLevel}, set_minLux: ${set_minLux}, set_maxLux: ${set_maxLux}, spaceBetweenLevels: ${spaceBetweenLevels},` - + ` numberOfLevels: ${numberOfLevels}`); - const CurrentAmbientLightLevel = lightLevel === 1 ? set_minLux : lightLevel = numberOfLevels - ? set_maxLux : ((set_maxLux - set_minLux) / spaceBetweenLevels) * (Number(lightLevel) - 1); - await this.debugLog(`CurrentAmbientLightLevel: ${CurrentAmbientLightLevel}, LightLevel: ${lightLevel}, set_minLux: ${set_minLux},` - + ` set_maxLux: ${set_maxLux}`); - return CurrentAmbientLightLevel; + const numberOfLevels = spaceBetweenLevels + 1 + this.debugLog(`LightLevel: ${lightLevel}, set_minLux: ${set_minLux}, set_maxLux: ${set_maxLux}, spaceBetweenLevels: ${spaceBetweenLevels}, numberOfLevels: ${numberOfLevels}`) + const CurrentAmbientLightLevel = lightLevel === 1 + ? set_minLux + : lightLevel = numberOfLevels + ? set_maxLux + : ((set_maxLux - set_minLux) / spaceBetweenLevels) * (Number(lightLevel) - 1) + await this.debugLog(`CurrentAmbientLightLevel: ${CurrentAmbientLightLevel}, LightLevel: ${lightLevel}, set_minLux: ${set_minLux}, set_maxLux: ${set_maxLux}`) + return CurrentAmbientLightLevel } /* @@ -300,12 +233,12 @@ export abstract class deviceBase { * 'homebridge-switchbot/${this.device.deviceType}/xx:xx:xx:xx:xx:xx' */ async mqttPublish(message: string, topic?: string) { - const mac = this.device.deviceId?.toLowerCase().match(/[\s\S]{1,2}/g)?.join(':'); - const options = this.deviceMqttPubOptions ?? {}; - const mqttTopic = topic ? `/${topic}` : ''; - const mqttMessageTopic = topic ? `${topic}/` : ''; - this.mqttClient?.publish(`homebridge-switchbot/${this.device.deviceType}/${mac}${mqttTopic}`, `${message}`, options); - this.debugLog(`MQTT message: ${mqttMessageTopic}${message} options:${JSON.stringify(options)}`); + const mac = this.device.deviceId?.toLowerCase().match(/[\s\S]{1,2}/g)?.join(':') + const options = this.deviceMqttPubOptions ?? {} + const mqttTopic = topic ? `/${topic}` : '' + const mqttMessageTopic = topic ? `${topic}/` : '' + this.mqttClient?.publish(`homebridge-switchbot/${this.device.deviceType}/${mac}${mqttTopic}`, `${message}`, options) + this.debugLog(`MQTT message: ${mqttMessageTopic}${message} options:${JSON.stringify(options)}`) } /* @@ -313,16 +246,15 @@ export abstract class deviceBase { */ async getMqttSettings(device: device & devicesConfig): Promise { // mqttURL - this.deviceMqttURL = device.mqttURL ?? this.config.options?.mqttURL ?? ''; - const mqttURL = device.mqttURL ? 'Device Config' : this.config.options?.mqttURL ? 'Platform Config' : 'Default'; + this.deviceMqttURL = device.mqttURL ?? this.config.options?.mqttURL ?? '' + const mqttURL = device.mqttURL ? 'Device Config' : this.config.options?.mqttURL ? 'Platform Config' : 'Default' // mqttOptions - this.deviceMqttOptions = device.mqttOptions ?? this.config.options?.mqttOptions ?? {}; - const mqttOptions = device.mqttOptions ? 'Device Config' : this.config.options?.mqttOptions ? 'Platform Config' : 'Default'; + this.deviceMqttOptions = device.mqttOptions ?? this.config.options?.mqttOptions ?? {} + const mqttOptions = device.mqttOptions ? 'Device Config' : this.config.options?.mqttOptions ? 'Platform Config' : 'Default' // mqttPubOptions - this.deviceMqttPubOptions = device.mqttPubOptions ?? this.config.options?.mqttPubOptions ?? {}; - const mqttPubOptions = device.mqttPubOptions ? 'Device Config' : this.config.options?.mqttPubOptions ? 'Platform Config' : 'Default'; - await this.debugLog(`Using ${mqttURL} MQTT URL: ${this.deviceMqttURL}, ${mqttOptions} mqttOptions: ${JSON.stringify(this.deviceMqttOptions)},` - + ` ${mqttPubOptions} mqttPubOptions: ${JSON.stringify(this.deviceMqttPubOptions)}`); + this.deviceMqttPubOptions = device.mqttPubOptions ?? this.config.options?.mqttPubOptions ?? {} + const mqttPubOptions = device.mqttPubOptions ? 'Device Config' : this.config.options?.mqttPubOptions ? 'Platform Config' : 'Default' + await this.debugLog(`Using ${mqttURL} MQTT URL: ${this.deviceMqttURL}, ${mqttOptions} mqttOptions: ${JSON.stringify(this.deviceMqttOptions)}, ${mqttPubOptions} mqttPubOptions: ${JSON.stringify(this.deviceMqttPubOptions)}`) } /* @@ -332,53 +264,53 @@ export abstract class deviceBase { const mac = this.device .deviceId!.match(/.{1,2}/g)! .join(':') - .toLowerCase(); + .toLowerCase() this.historyService = device.history ? new this.platform.fakegatoAPI('room', accessory, { log: this.platform.log, storage: 'fs', filename: `${hostname().split('.')[0]}_${mac}_persist.json`, }) - : null; + : null } async switchbotBLE(): Promise { - const switchbot = await this.platform.connectBLE(this.accessory, this.device); + const switchbot = await this.platform.connectBLE(this.accessory, this.device) // Convert to BLE Address - await this.convertBLEAddress(); - await this.getCustomBLEAddress(switchbot); - return switchbot; + await this.convertBLEAddress() + await this.getCustomBLEAddress(switchbot) + return switchbot } async convertBLEAddress() { this.device.bleMac = this.device .deviceId!.match(/.{1,2}/g)! .join(':') - .toLowerCase(); - await this.debugLog(`BLE Address: ${this.device.bleMac}`); + .toLowerCase() + await this.debugLog(`BLE Address: ${this.device.bleMac}`) } async monitorAdvertisementPackets(switchbot: any) { - await this.debugLog(`Scanning for ${this.device.bleModelName} devices...`); - await switchbot.startScan({ model: this.device.bleModel, id: this.device.bleMac }); + await this.debugLog(`Scanning for ${this.device.bleModelName} devices...`) + await switchbot.startScan({ model: this.device.bleModel, id: this.device.bleMac }) // Set an event handler - let serviceData = { model: this.device.bleModel, modelName: this.device.bleModelName } as ad['serviceData']; + let serviceData = { model: this.device.bleModel, modelName: this.device.bleModelName } as ad['serviceData'] switchbot.onadvertisement = async (ad: ad) => { if (this.device.bleMac === ad.address && ad.serviceData.model === this.device.bleModel) { - this.debugLog(`${JSON.stringify(ad, null, ' ')}`); - this.debugLog(`address: ${ad.address}, model: ${ad.serviceData.model}`); - this.debugLog(`serviceData: ${JSON.stringify(ad.serviceData)}`); - serviceData = ad.serviceData; + this.debugLog(`${JSON.stringify(ad, null, ' ')}`) + this.debugLog(`address: ${ad.address}, model: ${ad.serviceData.model}`) + this.debugLog(`serviceData: ${JSON.stringify(ad.serviceData)}`) + serviceData = ad.serviceData } else { - serviceData = { model: '', modelName: '' } as ad['serviceData']; - this.debugLog(`serviceData: ${JSON.stringify(ad.serviceData)}`); + serviceData = { model: '', modelName: '' } as ad['serviceData'] + this.debugLog(`serviceData: ${JSON.stringify(ad.serviceData)}`) } - }; + } // Wait - await switchbot.wait(this.scanDuration * 1000); + await switchbot.wait(this.scanDuration * 1000) // Stop to monitor - await switchbot.stopScan(); - return serviceData; + await switchbot.stopScan() + return serviceData } async getCustomBLEAddress(switchbot: any): Promise { @@ -386,300 +318,298 @@ export abstract class deviceBase { this.debugLog(`customBLEaddress: ${this.device.customBLEaddress}`); (async () => { // Start to monitor advertisement packets - await switchbot.startScan({ model: this.device.bleModel }); + await switchbot.startScan({ model: this.device.bleModel }) // Set an event handler switchbot.onadvertisement = async (ad: ad) => { - this.warnLog(`ad: ${JSON.stringify(ad, null, ' ')}`); - }; - await sleep(10000); + this.warnLog(`ad: ${JSON.stringify(ad, null, ' ')}`) + } + await sleep(10000) // Stop to monitor - switchbot.stopScan(); - })(); + switchbot.stopScan() + })() } } - async pushChangeRequest(bodyChange: string): Promise<{ body: any; statusCode: any; }> { + async pushChangeRequest(bodyChange: string): Promise<{ body: any, statusCode: any }> { return await request(`${Devices}/${this.device.deviceId}/commands`, { body: bodyChange, method: 'POST', headers: this.platform.generateHeaders(), - }); + }) } - async deviceRefreshStatus(): Promise<{ body: any; statusCode: any; }> { - return await this.platform.retryRequest(this.deviceMaxRetries, this.deviceDelayBetweenRetries, - `${Devices}/${this.device.deviceId}/status`, { headers: this.platform.generateHeaders() }); + async deviceRefreshStatus(): Promise<{ body: any, statusCode: any }> { + return await this.platform.retryRequest(this.deviceMaxRetries, this.deviceDelayBetweenRetries, `${Devices}/${this.device.deviceId}/status`, { headers: this.platform.generateHeaders() }) } async successfulStatusCodes(statusCode: any, deviceStatus: any) { - return (statusCode === 200 || statusCode === 100) && (deviceStatus.statusCode === 200 || deviceStatus.statusCode === 100); + return (statusCode === 200 || statusCode === 100) && (deviceStatus.statusCode === 200 || deviceStatus.statusCode === 100) } /** - * Update the characteristic value and log the change. - * - * @param Service: Service - * @param Characteristic: Characteristic - * @param CharacteristicValue: CharacteristicValue | undefined - * @param CharacteristicName: string - * @param history: object - * @return: void - * - */ - async updateCharacteristic(Service: Service, Characteristic: any, - CharacteristicValue: CharacteristicValue | undefined, CharacteristicName: string, history?: object): Promise { + * Update the characteristic value and log the change. + * + * @param Service Service + * @param Characteristic Characteristic + * @param CharacteristicValue CharacteristicValue | undefined + * @param CharacteristicName string + * @param history object + * @return: void + * + */ + async updateCharacteristic(Service: Service, Characteristic: any, CharacteristicValue: CharacteristicValue | undefined, CharacteristicName: string, history?: object): Promise { if (CharacteristicValue === undefined) { - this.debugLog(`${CharacteristicName}: ${CharacteristicValue}`); + this.debugLog(`${CharacteristicName}: ${CharacteristicValue}`) } else { - await this.mqtt(CharacteristicName, CharacteristicValue); + await this.mqtt(CharacteristicName, CharacteristicValue) if (this.device.history) { - this.historyService?.addEntry(history); + this.historyService?.addEntry(history) } - Service.updateCharacteristic(Characteristic, CharacteristicValue); - this.debugLog(`updateCharacteristic ${CharacteristicName}: ${CharacteristicValue}`); - this.debugWarnLog(`${CharacteristicName} context before: ${this.accessory.context[CharacteristicName]}`); - this.accessory.context[CharacteristicName] = CharacteristicValue; - this.debugWarnLog(`${CharacteristicName} context after: ${this.accessory.context[CharacteristicName]}`); + Service.updateCharacteristic(Characteristic, CharacteristicValue) + this.debugLog(`updateCharacteristic ${CharacteristicName}: ${CharacteristicValue}`) + this.debugWarnLog(`${CharacteristicName} context before: ${this.accessory.context[CharacteristicName]}`) + this.accessory.context[CharacteristicName] = CharacteristicValue + this.debugWarnLog(`${CharacteristicName} context after: ${this.accessory.context[CharacteristicName]}`) } } async mqtt(CharacteristicName: string, CharacteristicValue: CharacteristicValue) { if (this.device.mqttURL) { - this.mqttPublish(CharacteristicName, CharacteristicValue.toString()); + this.mqttPublish(CharacteristicName, CharacteristicValue.toString()) } } async getDeviceContext(accessory: PlatformAccessory, device: device & devicesConfig): Promise { - // Set the accessory context - switch (device.deviceType) { - case 'Humidifier': - device.model = SwitchBotModel.Humidifier; - device.bleModel = SwitchBotBLEModel.Humidifier; - device.bleModelName = SwitchBotBLEModelName.Humidifier; - device.bleModelFriednlyName = SwitchBotBLEModelFriendlyName.Humidifier; - break; - case 'Hub Mini': - device.model = SwitchBotModel.HubMini; - device.bleModel = SwitchBotBLEModel.Unknown; - device.bleModelName = SwitchBotBLEModelName.Unknown; - device.bleModelFriednlyName = SwitchBotBLEModelFriendlyName.Unknown; - break; - case 'Hub Plus': - device.model = SwitchBotModel.HubPlus; - device.bleModel = SwitchBotBLEModel.Unknown; - device.bleModelName = SwitchBotBLEModelName.Unknown; - device.bleModelFriednlyName = SwitchBotBLEModelFriendlyName.Unknown; - break; - case 'Hub 2': - device.model = SwitchBotModel.Hub2; - device.bleModel = SwitchBotBLEModel.Hub2; - device.bleModelName = SwitchBotBLEModelName.Hub2; - device.bleModelFriednlyName = SwitchBotBLEModelFriendlyName.Hub2; - break; - case 'Bot': - device.model = SwitchBotModel.Bot; - device.bleModel = SwitchBotBLEModel.Bot; - device.bleModelName = SwitchBotBLEModelName.Bot; - device.bleModelFriednlyName = SwitchBotBLEModelFriendlyName.Bot; - break; - case 'Meter': - device.model = SwitchBotModel.Meter; - device.bleModel = SwitchBotBLEModel.Meter; - device.bleModelName = SwitchBotBLEModelName.Meter; - device.bleModelFriednlyName = SwitchBotBLEModelFriendlyName.Meter; - break; - case 'MeterPlus': - device.model = SwitchBotModel.MeterPlusUS; - device.bleModel = SwitchBotBLEModel.MeterPlus; - device.bleModelName = SwitchBotBLEModelName.MeterPlus; - device.bleModelFriednlyName = SwitchBotBLEModelFriendlyName.MeterPlus; - break; - case 'Meter Plus (JP)': - device.model = SwitchBotModel.MeterPlusJP; - device.bleModel = SwitchBotBLEModel.MeterPlus; - device.bleModelName = SwitchBotBLEModelName.MeterPlus; - device.bleModelFriednlyName = SwitchBotBLEModelFriendlyName.MeterPlus; - break; - case 'WoIOSensor': - device.model = SwitchBotModel.OutdoorMeter; - device.bleModel = SwitchBotBLEModel.OutdoorMeter; - device.bleModelName = SwitchBotBLEModelName.OutdoorMeter; - device.bleModelFriednlyName = SwitchBotBLEModelFriendlyName.OutdoorMeter; - break; - case 'Water Detector': - device.model = SwitchBotModel.WaterDetector; - device.bleModel = SwitchBotBLEModel.Unknown; - device.bleModelName = SwitchBotBLEModelName.Unknown; - device.bleModelFriednlyName = SwitchBotBLEModelFriendlyName.Unknown; - break; - case 'Motion Sensor': - device.model = SwitchBotModel.MotionSensor; - device.bleModel = SwitchBotBLEModel.MotionSensor; - device.bleModelName = SwitchBotBLEModelName.MotionSensor; - device.bleModelFriednlyName = SwitchBotBLEModelFriendlyName.MotionSensor; - break; - case 'Contact Sensor': - device.model = SwitchBotModel.ContactSensor; - device.bleModel = SwitchBotBLEModel.ContactSensor; - device.bleModelName = SwitchBotBLEModelName.ContactSensor; - device.bleModelFriednlyName = SwitchBotBLEModelFriendlyName.ContactSensor; - break; - case 'Curtain': - device.model = SwitchBotModel.Curtain; - device.bleModel = SwitchBotBLEModel.Curtain; - device.bleModelName = SwitchBotBLEModelName.Curtain; - device.bleModelFriednlyName = SwitchBotBLEModelFriendlyName.Curtain; - break; - case 'Curtain3': - device.model = SwitchBotModel.Curtain3; - device.bleModel = SwitchBotBLEModel.Curtain3; - device.bleModelName = SwitchBotBLEModelName.Curtain3; - device.bleModelFriednlyName = SwitchBotBLEModelFriendlyName.Curtain3; - break; - case 'Blind Tilt': - device.model = SwitchBotModel.BlindTilt; - device.bleModel = SwitchBotBLEModel.BlindTilt; - device.bleModelName = SwitchBotBLEModelName.BlindTilt; - device.bleModelFriednlyName = SwitchBotBLEModelFriendlyName.BlindTilt; - break; - case 'Plug': - device.model = SwitchBotModel.Plug; - device.bleModel = SwitchBotBLEModel.PlugMiniUS; - device.bleModelName = SwitchBotBLEModelName.PlugMini; - device.bleModelFriednlyName = SwitchBotBLEModelFriendlyName.PlugMini; - break; - case 'Plug Mini (US)': - device.model = SwitchBotModel.PlugMiniUS; - device.bleModel = SwitchBotBLEModel.PlugMiniUS; - device.bleModelName = SwitchBotBLEModelName.PlugMini; - device.bleModelFriednlyName = SwitchBotBLEModelFriendlyName.PlugMini; - break; - case 'Plug Mini (JP)': - device.model = SwitchBotModel.PlugMiniJP; - device.bleModel = SwitchBotBLEModel.PlugMiniJP; - device.bleModelName = SwitchBotBLEModelName.PlugMini; - device.bleModelFriednlyName = SwitchBotBLEModelFriendlyName.PlugMini; - break; - case 'Smart Lock': - device.model = SwitchBotModel.Lock; - device.bleModel = SwitchBotBLEModel.Lock; - device.bleModelName = SwitchBotBLEModelName.Lock; - device.bleModelFriednlyName = SwitchBotBLEModelFriendlyName.Lock; - break; - case 'Smart Lock Pro': - device.model = SwitchBotModel.LockPro; - device.bleModel = SwitchBotBLEModel.LockPro; - device.bleModelName = SwitchBotBLEModelName.LockPro; - device.bleModelFriednlyName = SwitchBotBLEModelFriendlyName.LockPro; - break; - case 'Color Bulb': - device.model = SwitchBotModel.ColorBulb; - device.bleModel = SwitchBotBLEModel.ColorBulb; - device.bleModelName = SwitchBotBLEModelName.ColorBulb; - device.bleModelFriednlyName = SwitchBotBLEModelFriendlyName.ColorBulb; - break; - case 'K10+': - device.model = SwitchBotModel.K10; - device.bleModel = SwitchBotBLEModel.Unknown; - device.bleModelName = SwitchBotBLEModelName.Unknown; - device.bleModelFriednlyName = SwitchBotBLEModelFriendlyName.Unknown; - break; - case 'WoSweeper': - device.model = SwitchBotModel.WoSweeper; - device.bleModel = SwitchBotBLEModel.Unknown; - device.bleModelName = SwitchBotBLEModelName.Unknown; - device.bleModelFriednlyName = SwitchBotBLEModelFriendlyName.Unknown; - break; - case 'WoSweeperMini': - device.model = SwitchBotModel.WoSweeperMini; - device.bleModel = SwitchBotBLEModel.Unknown; - device.bleModelName = SwitchBotBLEModelName.Unknown; - device.bleModelFriednlyName = SwitchBotBLEModelFriendlyName.Unknown; - break; - case 'Robot Vacuum Cleaner S1': - device.model = SwitchBotModel.RobotVacuumCleanerS1; - device.bleModel = SwitchBotBLEModel.Unknown; - device.bleModelName = SwitchBotBLEModelName.Unknown; - device.bleModelFriednlyName = SwitchBotBLEModelFriendlyName.Unknown; - break; - case 'Robot Vacuum Cleaner S1 Plus': - device.model = SwitchBotModel.RobotVacuumCleanerS1Plus; - device.bleModel = SwitchBotBLEModel.Unknown; - device.bleModelName = SwitchBotBLEModelName.Unknown; - device.bleModelFriednlyName = SwitchBotBLEModelFriendlyName.Unknown; - break; - case 'Robot Vacuum Cleaner S10': - device.model = SwitchBotModel.RobotVacuumCleanerS10; - device.bleModel = SwitchBotBLEModel.Unknown; - device.bleModelName = SwitchBotBLEModelName.Unknown; - device.bleModelFriednlyName = SwitchBotBLEModelFriendlyName.Unknown; - break; - case 'Ceiling Light': - device.model = SwitchBotModel.CeilingLight; - device.bleModel = SwitchBotBLEModel.CeilingLight; - device.bleModelName = SwitchBotBLEModelName.CeilingLight; - device.bleModelFriednlyName = SwitchBotBLEModelFriendlyName.CeilingLight; - break; - case 'Ceiling Light Pro': - device.model = SwitchBotModel.CeilingLightPro; - device.bleModel = SwitchBotBLEModel.CeilingLightPro; - device.bleModelName = SwitchBotBLEModelName.CeilingLightPro; - device.bleModelFriednlyName = SwitchBotBLEModelFriendlyName.CeilingLightPro; - break; - case 'Strip Light': - device.model = SwitchBotModel.StripLight; - device.bleModel = SwitchBotBLEModel.StripLight; - device.bleModelName = SwitchBotBLEModelName.StripLight; - device.bleModelFriednlyName = SwitchBotBLEModelFriendlyName.StripLight; - break; - case 'Indoor Cam': - device.model = SwitchBotModel.IndoorCam; - device.bleModel = SwitchBotBLEModel.Unknown; - device.bleModelName = SwitchBotBLEModelName.Unknown; - device.bleModelFriednlyName = SwitchBotBLEModelFriendlyName.Unknown; - break; - case 'Remote': - device.model = SwitchBotModel.Remote; - device.bleModel = SwitchBotBLEModel.Unknown; - device.bleModelName = SwitchBotBLEModelName.Unknown; - device.bleModelFriednlyName = SwitchBotBLEModelFriendlyName.Unknown; - break; - case 'remote with screen+': - device.model = SwitchBotModel.UniversalRemote; - device.bleModel = SwitchBotBLEModel.Unknown; - device.bleModelName = SwitchBotBLEModelName.Unknown; - device.bleModelFriednlyName = SwitchBotBLEModelFriendlyName.Unknown; - break; - case 'Battery Circulator Fan': - device.model = SwitchBotModel.BatteryCirculatorFan; - device.bleModel = SwitchBotBLEModel.Unknown; - device.bleModelName = SwitchBotBLEModelName.Unknown; - device.bleModelFriednlyName = SwitchBotBLEModelFriendlyName.Unknown; - break; - default: - device.model = SwitchBotModel.Unknown; - device.bleModel = SwitchBotBLEModel.Unknown; - device.bleModelName = SwitchBotBLEModelName.Unknown; - device.bleModelFriednlyName = SwitchBotBLEModelFriendlyName.Unknown; - } - await this.debugLog(`Model: ${device.model}, BLE Model: ${device.bleModel}, BLE Model Name: ${device.bleModelName}, ` - + `BLE Model Friendly Name: ${device.bleModelFriednlyName}`); - accessory.context.model = device.model; - accessory.context.bleModel = device.bleModel; - accessory.context.bleModelName = device.bleModelName; - accessory.context.bleModelFriednlyName = device.bleModelFriednlyName; - - const deviceFirmwareVersion = device.firmware ?? device.version ?? accessory.context.version ?? this.platform.version ?? '0.0.0'; - const version = deviceFirmwareVersion.toString(); - this.debugLog(`Firmware Version: ${version.replace(/^V|-.*$/g, '')}`); - let deviceVersion: string; + const deviceMapping = { + 'Humidifier': { + model: SwitchBotModel.Humidifier, + bleModel: SwitchBotBLEModel.Humidifier, + bleModelName: SwitchBotBLEModelName.Humidifier, + bleModelFriendlyName: SwitchBotBLEModelFriendlyName.Humidifier, + }, + 'Hub Mini': { + model: SwitchBotModel.HubMini, + bleModel: SwitchBotBLEModel.Unknown, + bleModelName: SwitchBotBLEModelName.Unknown, + bleModelFriendlyName: SwitchBotBLEModelFriendlyName.Unknown, + }, + 'Hub Plus': { + model: SwitchBotModel.HubPlus, + bleModel: SwitchBotBLEModel.Unknown, + bleModelName: SwitchBotBLEModelName.Unknown, + bleModelFriendlyName: SwitchBotBLEModelFriendlyName.Unknown, + }, + 'Hub 2': { + model: SwitchBotModel.Hub2, + bleModel: SwitchBotBLEModel.Hub2, + bleModelName: SwitchBotBLEModelName.Hub2, + bleModelFriendlyName: SwitchBotBLEModelFriendlyName.Hub2, + }, + 'Bot': { + model: SwitchBotModel.Bot, + bleModel: SwitchBotBLEModel.Bot, + bleModelName: SwitchBotBLEModelName.Bot, + bleModelFriendlyName: SwitchBotBLEModelFriendlyName.Bot, + }, + 'Meter': { + model: SwitchBotModel.Meter, + bleModel: SwitchBotBLEModel.Meter, + bleModelName: SwitchBotBLEModelName.Meter, + bleModelFriendlyName: SwitchBotBLEModelFriendlyName.Meter, + }, + 'MeterPlus': { + model: SwitchBotModel.MeterPlusUS, + bleModel: SwitchBotBLEModel.MeterPlus, + bleModelName: SwitchBotBLEModelName.MeterPlus, + bleModelFriendlyName: SwitchBotBLEModelFriendlyName.MeterPlus, + }, + 'Meter Plus (JP)': { + model: SwitchBotModel.MeterPlusJP, + bleModel: SwitchBotBLEModel.MeterPlus, + bleModelName: SwitchBotBLEModelName.MeterPlus, + bleModelFriendlyName: SwitchBotBLEModelFriendlyName.MeterPlus, + }, + 'WoIOSensor': { + model: SwitchBotModel.OutdoorMeter, + bleModel: SwitchBotBLEModel.OutdoorMeter, + bleModelName: SwitchBotBLEModelName.OutdoorMeter, + bleModelFriendlyName: SwitchBotBLEModelFriendlyName.OutdoorMeter, + }, + 'Water Detector': { + model: SwitchBotModel.WaterDetector, + bleModel: SwitchBotBLEModel.Unknown, + bleModelName: SwitchBotBLEModelName.Unknown, + bleModelFriendlyName: SwitchBotBLEModelFriendlyName.Unknown, + }, + 'Motion Sensor': { + model: SwitchBotModel.MotionSensor, + bleModel: SwitchBotBLEModel.MotionSensor, + bleModelName: SwitchBotBLEModelName.MotionSensor, + bleModelFriendlyName: SwitchBotBLEModelFriendlyName.MotionSensor, + }, + 'Contact Sensor': { + model: SwitchBotModel.ContactSensor, + bleModel: SwitchBotBLEModel.ContactSensor, + bleModelName: SwitchBotBLEModelName.ContactSensor, + bleModelFriendlyName: SwitchBotBLEModelFriendlyName.ContactSensor, + }, + 'Curtain': { + model: SwitchBotModel.Curtain, + bleModel: SwitchBotBLEModel.Curtain, + bleModelName: SwitchBotBLEModelName.Curtain, + bleModelFriendlyName: SwitchBotBLEModelFriendlyName.Curtain, + }, + 'Curtain3': { + model: SwitchBotModel.Curtain3, + bleModel: SwitchBotBLEModel.Curtain3, + bleModelName: SwitchBotBLEModelName.Curtain3, + bleModelFriendlyName: SwitchBotBLEModelFriendlyName.Curtain3, + }, + 'Blind Tilt': { + model: SwitchBotModel.BlindTilt, + bleModel: SwitchBotBLEModel.BlindTilt, + bleModelName: SwitchBotBLEModelName.BlindTilt, + bleModelFriendlyName: SwitchBotBLEModelFriendlyName.BlindTilt, + }, + 'Plug': { + model: SwitchBotModel.Plug, + bleModel: SwitchBotBLEModel.PlugMiniUS, + bleModelName: SwitchBotBLEModelName.PlugMini, + bleModelFriendlyName: SwitchBotBLEModelFriendlyName.PlugMini, + }, + 'Plug Mini (US)': { + model: SwitchBotModel.PlugMiniUS, + bleModel: SwitchBotBLEModel.PlugMiniUS, + bleModelName: SwitchBotBLEModelName.PlugMini, + bleModelFriendlyName: SwitchBotBLEModelFriendlyName.PlugMini, + }, + 'Plug Mini (JP)': { + model: SwitchBotModel.PlugMiniJP, + bleModel: SwitchBotBLEModel.PlugMiniJP, + bleModelName: SwitchBotBLEModelName.PlugMini, + bleModelFriendlyName: SwitchBotBLEModelFriendlyName.PlugMini, + }, + 'Smart Lock': { + model: SwitchBotModel.Lock, + bleModel: SwitchBotBLEModel.Lock, + bleModelName: SwitchBotBLEModelName.Lock, + bleModelFriendlyName: SwitchBotBLEModelFriendlyName.Lock, + }, + 'Smart Lock Pro': { + model: SwitchBotModel.LockPro, + bleModel: SwitchBotBLEModel.LockPro, + bleModelName: SwitchBotBLEModelName.LockPro, + bleModelFriendlyName: SwitchBotBLEModelFriendlyName.LockPro, + }, + 'Color Bulb': { + model: SwitchBotModel.ColorBulb, + bleModel: SwitchBotBLEModel.ColorBulb, + bleModelName: SwitchBotBLEModelName.ColorBulb, + bleModelFriendlyName: SwitchBotBLEModelFriendlyName.ColorBulb, + }, + 'K10+': { + model: SwitchBotModel.K10, + bleModel: SwitchBotBLEModel.Unknown, + bleModelName: SwitchBotBLEModelName.Unknown, + bleModelFriendlyName: SwitchBotBLEModelFriendlyName.Unknown, + }, + 'WoSweeper': { + model: SwitchBotModel.WoSweeper, + bleModel: SwitchBotBLEModel.Unknown, + bleModelName: SwitchBotBLEModelName.Unknown, + bleModelFriendlyName: SwitchBotBLEModelFriendlyName.Unknown, + }, + 'WoSweeperMini': { + model: SwitchBotModel.WoSweeperMini, + bleModel: SwitchBotBLEModel.Unknown, + bleModelName: SwitchBotBLEModelName.Unknown, + bleModelFriendlyName: SwitchBotBLEModelFriendlyName.Unknown, + }, + 'Robot Vacuum Cleaner S1': { + model: SwitchBotModel.RobotVacuumCleanerS1, + bleModel: SwitchBotBLEModel.Unknown, + bleModelName: SwitchBotBLEModelName.Unknown, + bleModelFriendlyName: SwitchBotBLEModelFriendlyName.Unknown, + }, + 'Robot Vacuum Cleaner S1 Plus': { + model: SwitchBotModel.RobotVacuumCleanerS1Plus, + bleModel: SwitchBotBLEModel.Unknown, + bleModelName: SwitchBotBLEModelName.Unknown, + bleModelFriendlyName: SwitchBotBLEModelFriendlyName.Unknown, + }, + 'Robot Vacuum Cleaner S10': { + model: SwitchBotModel.RobotVacuumCleanerS10, + bleModel: SwitchBotBLEModel.Unknown, + bleModelName: SwitchBotBLEModelName.Unknown, + bleModelFriendlyName: SwitchBotBLEModelFriendlyName.Unknown, + }, + 'Ceiling Light': { + model: SwitchBotModel.CeilingLight, + bleModel: SwitchBotBLEModel.CeilingLight, + bleModelName: SwitchBotBLEModelName.CeilingLight, + bleModelFriendlyName: SwitchBotBLEModelFriendlyName.CeilingLight, + }, + 'Ceiling Light Pro': { + model: SwitchBotModel.CeilingLightPro, + bleModel: SwitchBotBLEModel.CeilingLightPro, + bleModelName: SwitchBotBLEModelName.CeilingLightPro, + bleModelFriendlyName: SwitchBotBLEModelFriendlyName.CeilingLightPro, + }, + 'Strip Light': { + model: SwitchBotModel.StripLight, + bleModel: SwitchBotBLEModel.StripLight, + bleModelName: SwitchBotBLEModelName.StripLight, + bleModelFriendlyName: SwitchBotBLEModelFriendlyName.StripLight, + }, + 'Indoor Cam': { + model: SwitchBotModel.IndoorCam, + bleModel: SwitchBotBLEModel.Unknown, + bleModelName: SwitchBotBLEModelName.Unknown, + bleModelFriendlyName: SwitchBotBLEModelFriendlyName.Unknown, + }, + 'Remote': { + model: SwitchBotModel.Remote, + bleModel: SwitchBotBLEModel.Unknown, + bleModelName: SwitchBotBLEModelName.Unknown, + bleModelFriendlyName: SwitchBotBLEModelFriendlyName.Unknown, + }, + 'remote with screen+': { + model: SwitchBotModel.UniversalRemote, + bleModel: SwitchBotBLEModel.Unknown, + bleModelName: SwitchBotBLEModelName.Unknown, + bleModelFriendlyName: SwitchBotBLEModelFriendlyName.Unknown, + }, + 'Battery Circulator Fan': { + model: SwitchBotModel.BatteryCirculatorFan, + bleModel: SwitchBotBLEModel.Unknown, + bleModelName: SwitchBotBLEModelName.Unknown, + bleModelFriendlyName: SwitchBotBLEModelFriendlyName.Unknown, + }, + } + const defaultDevice = { + model: SwitchBotModel.Unknown, + bleModel: SwitchBotBLEModel.Unknown, + bleModelName: SwitchBotBLEModelName.Unknown, + bleModelFriendlyName: SwitchBotBLEModelFriendlyName.Unknown, + } + const deviceConfig = deviceMapping[device.deviceType] || defaultDevice + device.model = deviceConfig.model + device.bleModel = deviceConfig.bleModel + device.bleModelName = deviceConfig.bleModelName + device.bleModelFriednlyName = deviceConfig.bleModelFriednlyName + await this.debugLog(`Model: ${device.model}, BLE Model: ${device.bleModel}, BLE Model Name: ${device.bleModelName}, BLE Model Friendly Name: ${device.bleModelFriednlyName}`) + + const deviceFirmwareVersion = device.firmware ?? device.version ?? accessory.context.version ?? this.platform.version ?? '0.0.0' + const version = deviceFirmwareVersion.toString() + this.debugLog(`Firmware Version: ${version.replace(/^V|-.*$/g, '')}`) + let deviceVersion: string if (version?.includes('.') === false) { - const replace = version?.replace(/^V|-.*$/g, ''); - const match = replace?.match(/.{1,1}/g); - const validVersion = match?.join('.'); - deviceVersion = validVersion ?? '0.0.0'; + const replace = version?.replace(/^V|-.*$/g, '') + const match = replace?.match(/./g) + const validVersion = match?.join('.') + deviceVersion = validVersion ?? '0.0.0' } else { - deviceVersion = version.replace(/^V|-.*$/g, '') ?? '0.0.0'; + deviceVersion = version.replace(/^V|-.*$/g, '') ?? '0.0.0' } accessory .getService(this.hap.Service.AccessoryInformation)! @@ -687,82 +617,38 @@ export abstract class deviceBase { .setCharacteristic(this.hap.Characteristic.SoftwareRevision, deviceVersion) .setCharacteristic(this.hap.Characteristic.FirmwareRevision, deviceVersion) .getCharacteristic(this.hap.Characteristic.FirmwareRevision) - .updateValue(deviceVersion); - accessory.context.version = deviceVersion; - this.debugSuccessLog(`version: ${accessory.context.version}`); + .updateValue(deviceVersion) + accessory.context.version = deviceVersion + this.debugSuccessLog(`version: ${accessory.context.version}`) } async statusCode(statusCode: number): Promise { - if (statusCode === 171) { - const previousStatusCode = statusCode; - if (this.device.hubDeviceId === this.device.deviceId) { - statusCode = 161; - } - if (this.device.hubDeviceId === '000000000000') { - statusCode = 161; - } - this.debugErrorLog(`statusCode: ${previousStatusCode} is now statusCode: ${statusCode}, because the hubDeviceId: ${this.device.hubDeviceId}` - + ` is set to the same as the deviceId: ${this.device.deviceId}, meaning the device is it's own hub.`); - } - switch (statusCode) { - case 151: - this.errorLog(`Command not supported by this deviceType, statusCode: ${statusCode}`); - break; - case 152: - this.errorLog(`Device not found, statusCode: ${statusCode}`); - break; - case 160: - this.errorLog(`Command is not supported, statusCode: ${statusCode}`); - break; - case 161: - this.errorLog(`Device is offline, statusCode: ${statusCode}`); - break; - case 171: - this.errorLog(`Hub Device is offline, statusCode: ${statusCode}. Hub: ${this.device.hubDeviceId}`); - break; - case 190: - this.errorLog('Device internal error due to device states not synchronized with server, or command format is invalid,' - + ` statusCode: ${statusCode}`); - break; - case 100: - this.debugLog(`Command successfully sent, statusCode: ${statusCode}`); - break; - case 200: - this.debugLog(`Request successful, statusCode: ${statusCode}`); - break; - case 400: - this.errorLog(`Bad Request, an invalid payload request, statusCode: ${statusCode}`); - break; - case 401: - this.errorLog(`Unauthorized, Authorization for the API is required, but the request has not been authenticated, statusCode: ${statusCode}`); - break; - case 403: - this.errorLog('Forbidden, The request has been authenticated but does not have appropriate permissions,' - + ` or a requested resource is not found, statusCode: ${statusCode}`); - break; - case 404: - this.errorLog(`Not Found, Specifies the requested path does not exist, statusCode: ${statusCode}`); - break; - case 406: - this.errorLog('Not Acceptable, a MIME type has been requested via the Accept header for a value not supported by the server,' - + ` statusCode: ${statusCode}`); - break; - case 415: - this.errorLog(`Unsupported Media Type, a contentType header has been defined that is not supported by the server, statusCode: ${statusCode}`); - break; - case 422: - this.errorLog('Unprocessable Entity, a valid request has been made, but the server cannot process it. ' - + `This is often used for APIs for which certain limits have been exceeded, statusCode: ${statusCode}`); - break; - case 429: - this.errorLog(`Too Many Requests, exceeded the number of requests allowed for a given time window, statusCode: ${statusCode}`); - break; - case 500: - this.errorLog(`Internal Server Error, An unexpected error occurred. These errors should be rare, statusCode: ${statusCode}`); - break; - default: - this.infoLog(`Unknown statusCode: ${statusCode}, Submit Bugs Here: https://tinyurl.com/SwitchBotBug`); - } + const statusMessages = { + 151: 'Command not supported by this deviceType', + 152: 'Device not found', + 160: 'Command is not supported', + 161: 'Device is offline', + 171: `Hub Device is offline. Hub: ${this.device.hubDeviceId}`, + 190: 'Device internal error due to device states not synchronized with server, or command format is invalid', + 100: 'Command successfully sent', + 200: 'Request successful', + 400: 'Bad Request, an invalid payload request', + 401: 'Unauthorized, Authorization for the API is required, but the request has not been authenticated', + 403: 'Forbidden, The request has been authenticated but does not have appropriate permissions, or a requested resource is not found', + 404: 'Not Found, Specifies the requested path does not exist', + 406: 'Not Acceptable, a MIME type has been requested via the Accept header for a value not supported by the server', + 415: 'Unsupported Media Type, a contentType header has been defined that is not supported by the server', + 422: 'Unprocessable Entity: The server cannot process the request, often due to exceeded API limits.', + 429: 'Too Many Requests, exceeded the number of requests allowed for a given time window', + 500: 'Internal Server Error, An unexpected error occurred. These errors should be rare', + } + if (statusCode === 171 && (this.device.hubDeviceId === this.device.deviceId || this.device.hubDeviceId === '000000000000')) { + this.debugErrorLog(`statusCode 171 changed to 161: hubDeviceId ${this.device.hubDeviceId} matches deviceId ${this.device.deviceId}, device is its own hub.`) + statusCode = 161 + } + const logMessage = statusMessages[statusCode] || `Unknown statusCode: ${statusCode}, Submit Bugs Here: https://tinyurl.com/SwitchBotBug` + const logMethod = [100, 200].includes(statusCode) ? 'debugLog' : statusMessages[statusCode] ? 'errorLog' : 'infoLog' + this[logMethod](`${logMessage}, statusCode: ${statusCode}`) } /** @@ -770,48 +656,48 @@ export abstract class deviceBase { */ async infoLog(...log: any[]): Promise { if (await this.enablingDeviceLogging()) { - this.log.info(`${this.device.deviceType}: ${this.accessory.displayName}`, String(...log)); + this.log.info(`${this.device.deviceType}: ${this.accessory.displayName}`, String(...log)) } } async successLog(...log: any[]): Promise { if (await this.enablingDeviceLogging()) { - this.log.success(`${this.device.deviceType}: ${this.accessory.displayName}`, String(...log)); + this.log.success(`${this.device.deviceType}: ${this.accessory.displayName}`, String(...log)) } } async debugSuccessLog(...log: any[]): Promise { if (await this.enablingDeviceLogging()) { if (await this.loggingIsDebug()) { - this.log.success(`[DEBUG] ${this.device.deviceType}: ${this.accessory.displayName}`, String(...log)); + this.log.success(`[DEBUG] ${this.device.deviceType}: ${this.accessory.displayName}`, String(...log)) } } } async warnLog(...log: any[]): Promise { if (await this.enablingDeviceLogging()) { - this.log.warn(`${this.device.deviceType}: ${this.accessory.displayName}`, String(...log)); + this.log.warn(`${this.device.deviceType}: ${this.accessory.displayName}`, String(...log)) } } async debugWarnLog(...log: any[]): Promise { if (await this.enablingDeviceLogging()) { if (await this.loggingIsDebug()) { - this.log.warn(`[DEBUG] ${this.device.deviceType}: ${this.accessory.displayName}`, String(...log)); + this.log.warn(`[DEBUG] ${this.device.deviceType}: ${this.accessory.displayName}`, String(...log)) } } } async errorLog(...log: any[]): Promise { if (await this.enablingDeviceLogging()) { - this.log.error(`${this.device.deviceType}: ${this.accessory.displayName}`, String(...log)); + this.log.error(`${this.device.deviceType}: ${this.accessory.displayName}`, String(...log)) } } async debugErrorLog(...log: any[]): Promise { if (await this.enablingDeviceLogging()) { if (await this.loggingIsDebug()) { - this.log.error(`[DEBUG] ${this.device.deviceType}: ${this.accessory.displayName}`, String(...log)); + this.log.error(`[DEBUG] ${this.device.deviceType}: ${this.accessory.displayName}`, String(...log)) } } } @@ -819,18 +705,18 @@ export abstract class deviceBase { async debugLog(...log: any[]): Promise { if (await this.enablingDeviceLogging()) { if (this.deviceLogging === 'debug') { - this.log.info(`[DEBUG] ${this.device.deviceType}: ${this.accessory.displayName}`, String(...log)); + this.log.info(`[DEBUG] ${this.device.deviceType}: ${this.accessory.displayName}`, String(...log)) } else if (this.deviceLogging === 'debugMode') { - this.log.debug(`${this.device.deviceType}: ${this.accessory.displayName}`, String(...log)); + this.log.debug(`${this.device.deviceType}: ${this.accessory.displayName}`, String(...log)) } } } async loggingIsDebug(): Promise { - return this.deviceLogging === 'debugMode' || this.deviceLogging === 'debug'; + return this.deviceLogging === 'debugMode' || this.deviceLogging === 'debug' } async enablingDeviceLogging(): Promise { - return this.deviceLogging === 'debugMode' || this.deviceLogging === 'debug' || this.deviceLogging === 'standard'; + return this.deviceLogging === 'debugMode' || this.deviceLogging === 'debug' || this.deviceLogging === 'standard' } } diff --git a/src/device/fan.ts b/src/device/fan.ts index 154e390b..362b5f34 100644 --- a/src/device/fan.ts +++ b/src/device/fan.ts @@ -2,306 +2,304 @@ * * plug.ts: @switchbot/homebridge-switchbot. */ -import { deviceBase } from './device.js'; -import { SwitchBotBLEModel, SwitchBotBLEModelName } from 'node-switchbot'; -import { Subject, debounceTime, interval, skipWhile, take, tap } from 'rxjs'; - -import type { devicesConfig } from '../settings.js'; -import type { device } from '../types/devicelist.js'; -import type { SwitchBotPlatform } from '../platform.js'; -import type { CharacteristicValue, PlatformAccessory, Service } from 'homebridge'; -import type { batteryCirculatorFanServiceData } from '../types/bledevicestatus.js'; -import type { batteryCirculatorFanStatus } from '../types/devicestatus.js'; -import type { batteryCirculatorFanWebhookContext } from '../types/devicewebhookstatus.js'; +import type { CharacteristicValue, PlatformAccessory, Service } from 'homebridge' + +import type { SwitchBotPlatform } from '../platform.js' +import type { devicesConfig } from '../settings.js' +import type { batteryCirculatorFanServiceData } from '../types/bledevicestatus.js' +import type { device } from '../types/devicelist.js' +import type { batteryCirculatorFanStatus } from '../types/devicestatus.js' +import type { batteryCirculatorFanWebhookContext } from '../types/devicewebhookstatus.js' + +/* +* For Testing Locally: +* import { SwitchBotBLEModel, SwitchBotBLEModelName } from '/Users/Shared/GitHub/OpenWonderLabs/node-switchbot/dist/index.js'; +*/ +import { SwitchBotBLEModel, SwitchBotBLEModelName } from 'node-switchbot' +import { debounceTime, interval, skipWhile, Subject, take, tap } from 'rxjs' + +import { deviceBase } from './device.js' export class Fan extends deviceBase { // Services private Fan: { - Name: CharacteristicValue; - Service: Service; - Active: CharacteristicValue; - SwingMode: CharacteristicValue; - RotationSpeed: CharacteristicValue; - }; + Name: CharacteristicValue + Service: Service + Active: CharacteristicValue + SwingMode: CharacteristicValue + RotationSpeed: CharacteristicValue + } private Battery: { - Name: CharacteristicValue; - Service: Service; - BatteryLevel: CharacteristicValue; - StatusLowBattery: CharacteristicValue; - ChargingState: CharacteristicValue; - }; + Name: CharacteristicValue + Service: Service + BatteryLevel: CharacteristicValue + StatusLowBattery: CharacteristicValue + ChargingState: CharacteristicValue + } private LightBulb: { - Name: CharacteristicValue; - Service: Service; - On: CharacteristicValue; - Brightness: CharacteristicValue; - }; + Name: CharacteristicValue + Service: Service + On: CharacteristicValue + Brightness: CharacteristicValue + } // OpenAPI - deviceStatus!: batteryCirculatorFanStatus; + deviceStatus!: batteryCirculatorFanStatus - //Webhook - webhookContext!: batteryCirculatorFanWebhookContext; + // Webhook + webhookContext!: batteryCirculatorFanWebhookContext // BLE - serviceData!: batteryCirculatorFanServiceData; + serviceData!: batteryCirculatorFanServiceData // Updates - fanUpdateInProgress!: boolean; - doFanUpdate!: Subject; + fanUpdateInProgress!: boolean + doFanUpdate!: Subject constructor( readonly platform: SwitchBotPlatform, accessory: PlatformAccessory, device: device & devicesConfig, ) { - super(platform, accessory, device); + super(platform, accessory, device) // Set category - accessory.category = this.hap.Categories.FAN; + accessory.category = this.hap.Categories.FAN // this is subject we use to track when we need to POST changes to the SwitchBot API - this.doFanUpdate = new Subject(); - this.fanUpdateInProgress = false; + this.doFanUpdate = new Subject() + this.fanUpdateInProgress = false // Initialize Fan Service - accessory.context.Fan = accessory.context.Fan ?? {}; + accessory.context.Fan = accessory.context.Fan ?? {} this.Fan = { Name: accessory.displayName, Service: accessory.getService(this.hap.Service.Fanv2) ?? accessory.addService(this.hap.Service.Fanv2) as Service, Active: accessory.context.Active ?? this.hap.Characteristic.Active.INACTIVE, SwingMode: accessory.context.SwingMode ?? this.hap.Characteristic.SwingMode.SWING_DISABLED, RotationSpeed: accessory.context.RotationSpeed ?? 0, - }; - accessory.context.Fan = this.Fan as object; + } + accessory.context.Fan = this.Fan as object // Initialize Fan Service - this.Fan.Service - .setCharacteristic(this.hap.Characteristic.Name, this.Fan.Name) - .getCharacteristic(this.hap.Characteristic.Active) - .onGet(() => { - return this.Fan.Active; - }) - .onSet(this.ActiveSet.bind(this)); + this.Fan.Service.setCharacteristic(this.hap.Characteristic.Name, this.Fan.Name).getCharacteristic(this.hap.Characteristic.Active).onGet(() => { + return this.Fan.Active + }).onSet(this.ActiveSet.bind(this)) // Initialize Fan RotationSpeed Characteristic - this.Fan.Service - .getCharacteristic(this.hap.Characteristic.RotationSpeed) - .onGet(() => { - return this.Fan.RotationSpeed; - }) - .onSet(this.RotationSpeedSet.bind(this)); + this.Fan.Service.getCharacteristic(this.hap.Characteristic.RotationSpeed).onGet(() => { + return this.Fan.RotationSpeed + }).onSet(this.RotationSpeedSet.bind(this)) // Initialize Fan SwingMode Characteristic - this.Fan.Service.getCharacteristic(this.hap.Characteristic.SwingMode) - .onGet(() => { - return this.Fan.SwingMode; - }) - .onSet(this.SwingModeSet.bind(this)); + this.Fan.Service.getCharacteristic(this.hap.Characteristic.SwingMode).onGet(() => { + return this.Fan.SwingMode + }).onSet(this.SwingModeSet.bind(this)) // Initialize Battery Service - accessory.context.Battery = accessory.context.Battery ?? {}; + accessory.context.Battery = accessory.context.Battery ?? {} this.Battery = { Name: `${accessory.displayName} Battery`, Service: accessory.getService(this.hap.Service.Battery) ?? accessory.addService(this.hap.Service.Battery) as Service, BatteryLevel: accessory.context.BatteryLevel ?? 100, StatusLowBattery: accessory.context.StatusLowBattery ?? this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL, ChargingState: accessory.context.ChargingState ?? this.hap.Characteristic.ChargingState.NOT_CHARGING, - }; - accessory.context.Battery = this.Battery as object; + } + accessory.context.Battery = this.Battery as object // Initialize Battery Service - this.Battery.Service - .setCharacteristic(this.hap.Characteristic.Name, this.Battery.Name) - .getCharacteristic(this.hap.Characteristic.BatteryLevel) - .onGet(() => { - return this.Battery.BatteryLevel; - }); + this.Battery.Service.setCharacteristic(this.hap.Characteristic.Name, this.Battery.Name).getCharacteristic(this.hap.Characteristic.BatteryLevel).onGet(() => { + return this.Battery.BatteryLevel + }) // Initialize Battery ChargingState Characteristic - this.Battery.Service - .getCharacteristic(this.hap.Characteristic.ChargingState) - .onGet(() => { - return this.Battery.ChargingState; - }); + this.Battery.Service.getCharacteristic(this.hap.Characteristic.ChargingState).onGet(() => { + return this.Battery.ChargingState + }) // Initialize Battery StatusLowBattery Characteristic - this.Battery.Service - .getCharacteristic(this.hap.Characteristic.StatusLowBattery) - .onGet(() => { - return this.Battery.StatusLowBattery; - }); + this.Battery.Service.getCharacteristic(this.hap.Characteristic.StatusLowBattery).onGet(() => { + return this.Battery.StatusLowBattery + }) // Initialize LightBulb Service - accessory.context.LightBulb = accessory.context.LightBulb ?? {}; + accessory.context.LightBulb = accessory.context.LightBulb ?? {} this.LightBulb = { - Name: `${accessory.displayName} Night Light`, + Name: `${accessory.displayName} Night Light`, Service: accessory.getService(this.hap.Service.Lightbulb) ?? accessory.addService(this.hap.Service.Lightbulb) as Service, On: accessory.context.On ?? false, Brightness: accessory.context.Brightness ?? 0, - }; - accessory.context.LightBulb = this.LightBulb as object; + } + accessory.context.LightBulb = this.LightBulb as object // Initialize LightBulb Characteristics - this.LightBulb.Service - .setCharacteristic(this.hap.Characteristic.Name, this.LightBulb.Name) - .getCharacteristic(this.hap.Characteristic.On) - .onGet(() => { - return this.LightBulb.On; - }) - .onSet(this.OnSet.bind(this)); + this.LightBulb.Service.setCharacteristic(this.hap.Characteristic.Name, this.LightBulb.Name).getCharacteristic(this.hap.Characteristic.On).onGet(() => { + return this.LightBulb.On + }).onSet(this.OnSet.bind(this)) // Initialize LightBulb Brightness Characteristic - this.LightBulb.Service - .getCharacteristic(this.hap.Characteristic.Brightness) - .onGet(() => { - return this.LightBulb.Brightness; - }) - .onSet(this.BrightnessSet.bind(this)); + this.LightBulb.Service.getCharacteristic(this.hap.Characteristic.Brightness).onGet(() => { + return this.LightBulb.Brightness + }).onSet(this.BrightnessSet.bind(this)) // Retrieve initial values and updateHomekit - this.debugLog('Retrieve initial values and update Homekit'); - this.refreshStatus(); + try { + this.debugLog('Retrieve initial values and update Homekit') + this.refreshStatus() + } catch (e: any) { + this.errorLog(`failed to retrieve initial values and update Homekit, Error: ${e}`) + } - //regisiter webhook event handler - this.debugLog('Registering Webhook Event Handler'); - this.registerWebhook(); + // regisiter webhook event handler if enabled + try { + this.debugLog('Registering Webhook Event Handler') + this.registerWebhook() + } catch (e: any) { + this.errorLog(`failed to registerWebhook, Error: ${e}`) + } + + // regisiter platform BLE event handler if enabled + try { + this.debugLog('Registering Platform BLE Event Handler') + this.registerPlatformBLE() + } catch (e: any) { + this.errorLog(`failed to registerPlatformBLE, Error: ${e}`) + } // Start an update interval interval(this.deviceRefreshRate * 1000) .pipe(skipWhile(() => this.fanUpdateInProgress)) .subscribe(async () => { - await this.refreshStatus(); - }); + await this.refreshStatus() + }) // Watch for Plug change events // We put in a debounce of 100ms so we don't make duplicate calls this.doFanUpdate .pipe( tap(() => { - this.fanUpdateInProgress = true; + this.fanUpdateInProgress = true }), debounceTime(this.devicePushRate * 1000), ) .subscribe(async () => { try { - await this.pushChanges(); + await this.pushChanges() } catch (e: any) { - await this.apiError(e); - await this.errorLog(`failed pushChanges with ${device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`); + await this.apiError(e) + await this.errorLog(`failed pushChanges with ${device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`) } - this.fanUpdateInProgress = false; - }); + this.fanUpdateInProgress = false + }) } async BLEparseStatus(): Promise { - await this.debugLog('BLEparseStatus'); - await this.debugLog(`(powerState, fanSpeed) = BLE:(${this.serviceData.state}, ${this.serviceData.fanSpeed}), current:(${this.Fan.Active},` - + ` ${this.Fan.RotationSpeed})`); + await this.debugLog('BLEparseStatus') + await this.debugLog(`(powerState, fanSpeed) = BLE:(${this.serviceData.state}, ${this.serviceData.fanSpeed}), current:(${this.Fan.Active}, ${this.Fan.RotationSpeed})`) // Active - this.Fan.Active = this.serviceData.state === 'on' ? this.hap.Characteristic.Active.ACTIVE : this.hap.Characteristic.Active.INACTIVE; - await this.debugLog(`Active: ${this.Fan.Active}`); + this.Fan.Active = this.serviceData.state === 'on' ? this.hap.Characteristic.Active.ACTIVE : this.hap.Characteristic.Active.INACTIVE + await this.debugLog(`Active: ${this.Fan.Active}`) // RotationSpeed - this.Fan.RotationSpeed = this.serviceData.fanSpeed; + this.Fan.RotationSpeed = this.serviceData.fanSpeed } async openAPIparseStatus() { - await this.debugLog('openAPIparseStatus'); - await this.debugLog(`(version, battery, powerState, oscillation, chargingStatus, fanSpeed) = OpenAPI:(${this.deviceStatus.version},` - + ` ${this.deviceStatus.battery}, ${this.deviceStatus.power}, ${this.deviceStatus.oscillation}, ${this.deviceStatus.chargingStatus},` - + ` ${this.deviceStatus.fanSpeed}), current:(${this.accessory.context.version}, ${this.Battery.BatteryLevel}, ${this.Fan.Active},` - + ` ${this.Fan.SwingMode}, ${this.Battery.ChargingState}, ${this.Fan.RotationSpeed})`); + await this.debugLog('openAPIparseStatus') + await this.debugLog(`(version, battery, powerState, oscillation, chargingStatus, fanSpeed) = OpenAPI:(${this.deviceStatus.version}, ${this.deviceStatus.battery}, ${this.deviceStatus.power}, ${this.deviceStatus.oscillation}, ${this.deviceStatus.chargingStatus}, ${this.deviceStatus.fanSpeed}), current:(${this.accessory.context.version}, ${this.Battery.BatteryLevel}, ${this.Fan.Active}, ${this.Fan.SwingMode}, ${this.Battery.ChargingState}, ${this.Fan.RotationSpeed})`) // Active - this.Fan.Active = this.deviceStatus.power === 'on' ? this.hap.Characteristic.Active.ACTIVE : this.hap.Characteristic.Active.INACTIVE; - this.debugLog(`Active: ${this.Fan.Active}`); + this.Fan.Active = this.deviceStatus.power === 'on' ? this.hap.Characteristic.Active.ACTIVE : this.hap.Characteristic.Active.INACTIVE + this.debugLog(`Active: ${this.Fan.Active}`) // SwingMode - this.Fan.SwingMode = this.deviceStatus.oscillation === 'on' ? - this.hap.Characteristic.SwingMode.SWING_ENABLED : this.hap.Characteristic.SwingMode.SWING_DISABLED; - await this.debugLog(`SwingMode: ${this.Fan.SwingMode}`); + this.Fan.SwingMode = this.deviceStatus.oscillation === 'on' + ? this.hap.Characteristic.SwingMode.SWING_ENABLED + : this.hap.Characteristic.SwingMode.SWING_DISABLED + await this.debugLog(`SwingMode: ${this.Fan.SwingMode}`) // RotationSpeed - this.Fan.RotationSpeed = this.deviceStatus.fanSpeed; - await this.debugLog(`RotationSpeed: ${this.Fan.RotationSpeed}`); + this.Fan.RotationSpeed = this.deviceStatus.fanSpeed + await this.debugLog(`RotationSpeed: ${this.Fan.RotationSpeed}`) // ChargingState - this.Battery.ChargingState = this.deviceStatus.chargingStatus === 'charging' ? - this.hap.Characteristic.ChargingState.CHARGING : this.hap.Characteristic.ChargingState.NOT_CHARGING; + this.Battery.ChargingState = this.deviceStatus.chargingStatus === 'charging' + ? this.hap.Characteristic.ChargingState.CHARGING + : this.hap.Characteristic.ChargingState.NOT_CHARGING // BatteryLevel - this.Battery.BatteryLevel = this.deviceStatus.battery; - await this.debugLog(`BatteryLevel: ${this.Battery.BatteryLevel}`); + this.Battery.BatteryLevel = this.deviceStatus.battery + await this.debugLog(`BatteryLevel: ${this.Battery.BatteryLevel}`) // StatusLowBattery this.Battery.StatusLowBattery = this.Battery.BatteryLevel < 10 - ? this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW : this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL; - await this.debugLog(`StatusLowBattery: ${this.Battery.StatusLowBattery}`); + ? this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW + : this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL + await this.debugLog(`StatusLowBattery: ${this.Battery.StatusLowBattery}`) // Firmware Version if (this.deviceStatus.version) { - const version = this.deviceStatus.version.toString(); - await this.debugLog(`Firmware Version: ${version.replace(/^V|-.*$/g, '')}`); - const deviceVersion = version.replace(/^V|-.*$/g, '') ?? '0.0.0'; + const version = this.deviceStatus.version.toString() + await this.debugLog(`Firmware Version: ${version.replace(/^V|-.*$/g, '')}`) + const deviceVersion = version.replace(/^V|-.*$/g, '') ?? '0.0.0' this.accessory .getService(this.hap.Service.AccessoryInformation)! .setCharacteristic(this.hap.Characteristic.HardwareRevision, deviceVersion) .setCharacteristic(this.hap.Characteristic.FirmwareRevision, deviceVersion) .getCharacteristic(this.hap.Characteristic.FirmwareRevision) - .updateValue(deviceVersion); - this.accessory.context.version = deviceVersion; - await this.debugSuccessLog(`version: ${this.accessory.context.version}`); + .updateValue(deviceVersion) + this.accessory.context.version = deviceVersion + await this.debugSuccessLog(`version: ${this.accessory.context.version}`) } } async parseStatusWebhook(): Promise { - await this.debugLog('parseStatusWebhook'); - await this.debugLog(`(version, battery, powerState, oscillation, chargingStatus, fanSpeed) = Webhook:(${this.webhookContext.version},` - + ` ${this.webhookContext.battery}, ${this.webhookContext.powerState}, ${this.webhookContext.oscillation},` - + ` ${this.webhookContext.chargingStatus}, ${this.webhookContext.fanSpeed}), current:(${this.accessory.context.version},` - + ` ${this.Battery.BatteryLevel}, ${this.Fan.Active}, ${this.Fan.SwingMode}, ${this.Battery.ChargingState}, ${this.Fan.RotationSpeed})`); + await this.debugLog('parseStatusWebhook') + await this.debugLog(`(version, battery, powerState, oscillation, chargingStatus, fanSpeed) = Webhook:(${this.webhookContext.version}, ${this.webhookContext.battery}, ${this.webhookContext.powerState}, ${this.webhookContext.oscillation}, ${this.webhookContext.chargingStatus}, ${this.webhookContext.fanSpeed}), current:(${this.accessory.context.version}, ${this.Battery.BatteryLevel}, ${this.Fan.Active}, ${this.Fan.SwingMode}, ${this.Battery.ChargingState}, ${this.Fan.RotationSpeed})`) // Active - this.Fan.Active = this.webhookContext.powerState === 'ON' ? this.hap.Characteristic.Active.ACTIVE : this.hap.Characteristic.Active.INACTIVE; - await this.debugLog(`Active: ${this.Fan.Active}`); + this.Fan.Active = this.webhookContext.powerState === 'ON' ? this.hap.Characteristic.Active.ACTIVE : this.hap.Characteristic.Active.INACTIVE + await this.debugLog(`Active: ${this.Fan.Active}`) // SwingMode - this.Fan.SwingMode = this.webhookContext.oscillation === 'on' ? - this.hap.Characteristic.SwingMode.SWING_ENABLED : this.hap.Characteristic.SwingMode.SWING_DISABLED; - await this.debugLog(`SwingMode: ${this.Fan.SwingMode}`); + this.Fan.SwingMode = this.webhookContext.oscillation === 'on' + ? this.hap.Characteristic.SwingMode.SWING_ENABLED + : this.hap.Characteristic.SwingMode.SWING_DISABLED + await this.debugLog(`SwingMode: ${this.Fan.SwingMode}`) // RotationSpeed - this.Fan.RotationSpeed = this.webhookContext.fanSpeed; - await this.debugLog(`RotationSpeed: ${this.Fan.RotationSpeed}`); + this.Fan.RotationSpeed = this.webhookContext.fanSpeed + await this.debugLog(`RotationSpeed: ${this.Fan.RotationSpeed}`) // BatteryLevel - this.Battery.BatteryLevel = this.webhookContext.battery; - await this.debugLog(`BatteryLevel: ${this.Battery.BatteryLevel}`); + this.Battery.BatteryLevel = this.webhookContext.battery + await this.debugLog(`BatteryLevel: ${this.Battery.BatteryLevel}`) // StatusLowBattery this.Battery.StatusLowBattery = this.Battery.BatteryLevel < 10 - ? this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW : this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL; - await this.debugLog(`StatusLowBattery: ${this.Battery.StatusLowBattery}`); + ? this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW + : this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL + await this.debugLog(`StatusLowBattery: ${this.Battery.StatusLowBattery}`) // ChargingState - this.Battery.ChargingState = this.webhookContext.chargingStatus === 'charging' ? - this.hap.Characteristic.ChargingState.CHARGING : this.hap.Characteristic.ChargingState.NOT_CHARGING; - await this.debugLog(`ChargingState: ${this.Battery.ChargingState}`); + this.Battery.ChargingState = this.webhookContext.chargingStatus === 'charging' + ? this.hap.Characteristic.ChargingState.CHARGING + : this.hap.Characteristic.ChargingState.NOT_CHARGING + await this.debugLog(`ChargingState: ${this.Battery.ChargingState}`) // FirmwareVersion if (this.webhookContext.version) { - const deviceVersion = this.webhookContext.version.replace(/^V|-.*$/g, '') ?? '0.0.0'; + const deviceVersion = this.webhookContext.version.replace(/^V|-.*$/g, '') ?? '0.0.0' this.accessory .getService(this.hap.Service.AccessoryInformation)! .setCharacteristic(this.hap.Characteristic.HardwareRevision, deviceVersion) .setCharacteristic(this.hap.Characteristic.FirmwareRevision, deviceVersion) .getCharacteristic(this.hap.Characteristic.FirmwareRevision) - .updateValue(deviceVersion); - this.accessory.context.version = deviceVersion; - await this.debugSuccessLog(`version: ${this.accessory.context.version}`); + .updateValue(deviceVersion) + this.accessory.context.version = deviceVersion + await this.debugSuccessLog(`version: ${this.accessory.context.version}`) } } @@ -310,101 +308,105 @@ export class Fan extends deviceBase { */ async refreshStatus(): Promise { if (!this.device.enableCloudService && this.OpenAPI) { - await this.errorLog(`refreshStatus enableCloudService: ${this.device.enableCloudService}`); - } else if (this.BLE || this.config.options?.BLE) { - await this.BLERefreshStatus(); + await this.errorLog(`refreshStatus enableCloudService: ${this.device.enableCloudService}`) + } else if (this.BLE) { + await this.BLERefreshStatus() } else if (this.OpenAPI && this.platform.config.credentials?.token) { - await this.openAPIRefreshStatus(); + await this.openAPIRefreshStatus() } else { - await this.offlineOff(); - await this.debugWarnLog(`Connection Type: ${this.device.connectionType}, refreshStatus will not happen.`); + await this.offlineOff() + await this.debugWarnLog(`Connection Type: ${this.device.connectionType}, refreshStatus will not happen.`) } } async BLERefreshStatus(): Promise { - await this.debugLog('BLERefreshStatus'); + await this.debugLog('BLERefreshStatus') + const switchbot = await this.switchbotBLE() + if (switchbot === undefined) { + await this.BLERefreshConnection(switchbot) + } else { + // Start to monitor advertisement packets + (async () => { + // Start to monitor advertisement packets + const serviceData = await this.monitorAdvertisementPackets(switchbot) as batteryCirculatorFanServiceData + // Update HomeKit + if (serviceData.model === SwitchBotBLEModel.Unknown && SwitchBotBLEModelName.Unknown) { + this.serviceData = serviceData + await this.BLEparseStatus() + await this.updateHomeKitCharacteristics() + } else { + await this.errorLog(`failed to get serviceData, serviceData: ${serviceData}`) + await this.BLERefreshConnection(switchbot) + } + })() + } + } + + async registerPlatformBLE(): Promise { + await this.debugLog('registerPlatformBLE') if (this.config.options?.BLE) { - await this.debugLog('is listening to Platform BLE.'); - this.device.bleMac = this.device.deviceId!.match(/.{1,2}/g)!.join(':').toLowerCase(); - await this.debugLog(`bleMac: ${this.device.bleMac}`); + await this.debugLog('is listening to Platform BLE.') + this.device.bleMac = this.device.deviceId!.match(/.{1,2}/g)!.join(':').toLowerCase() + await this.debugLog(`bleMac: ${this.device.bleMac}`) this.platform.bleEventHandler[this.device.bleMac] = async (context: batteryCirculatorFanServiceData) => { try { - await this.debugLog(`received BLE: ${JSON.stringify(context)}`); - this.serviceData = context; - await this.BLEparseStatus(); - await this.updateHomeKitCharacteristics(); + await this.debugLog(`received BLE: ${JSON.stringify(context)}`) + this.serviceData = context + await this.BLEparseStatus() + await this.updateHomeKitCharacteristics() } catch (e: any) { - await this.errorLog(`failed to handle BLE. Received: ${JSON.stringify(context)} Error: ${e}`); + await this.errorLog(`failed to handle BLE. Received: ${JSON.stringify(context)} Error: ${e}`) } - }; - } else { - await this.debugLog('is using Device BLE Scanning.'); - const switchbot = await this.switchbotBLE(); - if (switchbot === undefined) { - await this.BLERefreshConnection(switchbot); - } else { - // Start to monitor advertisement packets - (async () => { - // Start to monitor advertisement packets - const serviceData = await this.monitorAdvertisementPackets(switchbot) as batteryCirculatorFanServiceData; - // Update HomeKit - if (serviceData.model === SwitchBotBLEModel.Unknown && SwitchBotBLEModelName.Unknown) { - this.serviceData = serviceData; - await this.BLEparseStatus(); - await this.updateHomeKitCharacteristics(); - } else { - await this.errorLog(`failed to get serviceData, serviceData: ${serviceData}`); - await this.BLERefreshConnection(switchbot); - } - })(); } + } else { + await this.debugLog('is not listening to Platform BLE') } } async openAPIRefreshStatus(): Promise { - await this.debugLog('openAPIRefreshStatus'); + await this.debugLog('openAPIRefreshStatus') try { - const { body, statusCode } = await this.deviceRefreshStatus(); - const deviceStatus: any = await body.json(); - await this.debugLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`);; + const { body, statusCode } = await this.deviceRefreshStatus() + const deviceStatus: any = await body.json() + await this.debugLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`) if (await this.successfulStatusCodes(statusCode, deviceStatus)) { - await this.debugSuccessLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`); - this.deviceStatus = deviceStatus.body; - await this.openAPIparseStatus(); - await this.updateHomeKitCharacteristics(); + await this.debugSuccessLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`) + this.deviceStatus = deviceStatus.body + await this.openAPIparseStatus() + await this.updateHomeKitCharacteristics() } else { - await this.debugWarnLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`); - await this.debugWarnLog(statusCode, deviceStatus); + await this.debugWarnLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`) + await this.debugWarnLog(statusCode, deviceStatus) } } catch (e: any) { - await this.apiError(e); - await this.errorLog(`failed openAPIRefreshStatus with ${this.device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`); + await this.apiError(e) + await this.errorLog(`failed openAPIRefreshStatus with ${this.device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`) } } async registerWebhook() { if (this.device.webhook) { - await this.debugLog('is listening webhook.'); + await this.debugLog('is listening webhook.') this.platform.webhookEventHandler[this.device.deviceId] = async (context: batteryCirculatorFanWebhookContext) => { try { - await this.debugLog(`received Webhook: ${JSON.stringify(context)}`); - this.webhookContext = context; - await this.parseStatusWebhook(); - await this.updateHomeKitCharacteristics(); + await this.debugLog(`received Webhook: ${JSON.stringify(context)}`) + this.webhookContext = context + await this.parseStatusWebhook() + await this.updateHomeKitCharacteristics() } catch (e: any) { - await this.errorLog(`failed to handle webhook. Received: ${JSON.stringify(context)} Error: ${e}`); + await this.errorLog(`failed to handle webhook. Received: ${JSON.stringify(context)} Error: ${e}`) } - }; + } } else { - await this.debugLog('is not listening webhook.'); + await this.debugLog('is not listening webhook.') } } /** * Pushes the requested changes to the SwitchBot API - * commandType command parameter Description - * "command" "turnOff" "default" = set to OFF state - * "command" "turnOn" "default" = set to ON state + * commandType command parameter Description + * "command" "turnOff" "default" = set to OFF state + * "command" "turnOn" "default" = set to ON state * "command" "setNightLightMode" "off, 1, or 2" = off, turn off nightlight, (1, bright) (2, dim) * "command" "setWindMode" "direct, natural, sleep, or baby" = Set fan mode * "command" "setWindSpeed" "{1-100} e.g. 10" = Set fan speed 1~100 @@ -412,41 +414,41 @@ export class Fan extends deviceBase { async pushChanges(): Promise { if (!this.device.enableCloudService && this.OpenAPI) { - await this.errorLog(`pushChanges enableCloudService: ${this.device.enableCloudService}`); + await this.errorLog(`pushChanges enableCloudService: ${this.device.enableCloudService}`) } else if (this.BLE) { - await this.BLEpushChanges(); + await this.BLEpushChanges() } else if (this.OpenAPI && this.platform.config.credentials?.token) { - await this.openAPIpushChanges(); + await this.openAPIpushChanges() if (this.Fan.Active) { - await this.debugLog(`Active: ${this.Fan.Active}`); + await this.debugLog(`Active: ${this.Fan.Active}`) // Push RotationSpeed Update - await this.debugLog(`RotationSpeed: ${this.Fan.RotationSpeed}`); - await this.pushRotationSpeedChanges(); + await this.debugLog(`RotationSpeed: ${this.Fan.RotationSpeed}`) + await this.pushRotationSpeedChanges() // Push SwingMode Update - await this.debugLog(`SwingMode: ${this.Fan.SwingMode}`); - await this.pushSwingModeChanges(); + await this.debugLog(`SwingMode: ${this.Fan.SwingMode}`) + await this.pushSwingModeChanges() } else { - await this.debugLog('BLE (RotationSpeed) & (SwingMode) changes will not happen, as the device is Off.'); + await this.debugLog('BLE (RotationSpeed) & (SwingMode) changes will not happen, as the device is Off.') } } else { - await this.offlineOff(); - await this.debugWarnLog(`Connection Type: ${this.device.connectionType}, pushChanges will not happen.`); + await this.offlineOff() + await this.debugWarnLog(`Connection Type: ${this.device.connectionType}, pushChanges will not happen.`) } // Refresh the status from the API interval(15000) .pipe(skipWhile(() => this.fanUpdateInProgress)) .pipe(take(1)) .subscribe(async () => { - await this.refreshStatus(); - }); + await this.refreshStatus() + }) } async BLEpushChanges(): Promise { - await this.debugLog('BLEpushChanges'); + await this.debugLog('BLEpushChanges') if (this.Fan.Active !== this.accessory.context.Active) { - await this.debugLog(`BLEpushChanges On: ${this.Fan.Active} OnCached: ${this.accessory.context.Active}`); - const switchbot = await this.platform.connectBLE(this.accessory, this.device); - await this.convertBLEAddress(); + await this.debugLog(`BLEpushChanges On: ${this.Fan.Active} OnCached: ${this.accessory.context.Active}`) + const switchbot = await this.platform.connectBLE(this.accessory, this.device) + await this.convertBLEAddress() if (switchbot !== false) { switchbot .discover({ model: this.device.bleModel, id: this.device.bleMac }) @@ -455,120 +457,117 @@ export class Fan extends deviceBase { max: await this.maxRetryBLE(), fn: async () => { if (this.Fan.Active) { - return await device_list[0].turnOn({ id: this.device.bleMac }); + return await device_list[0].turnOn({ id: this.device.bleMac }) } else { - return await device_list[0].turnOff({ id: this.device.bleMac }); + return await device_list[0].turnOff({ id: this.device.bleMac }) } }, - }); + }) }) .then(async () => { - await this.successLog(`Active: ${this.Fan.Active} sent over SwitchBot BLE, sent successfully`); - await this.updateHomeKitCharacteristics(); + await this.successLog(`Active: ${this.Fan.Active} sent over SwitchBot BLE, sent successfully`) + await this.updateHomeKitCharacteristics() }) .catch(async (e: any) => { - await this.apiError(e); - await this.errorLog(`failed BLEpushChanges with ${this.device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`); - await this.BLEPushConnection(); - }); + await this.apiError(e) + await this.errorLog(`failed BLEpushChanges with ${this.device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`) + await this.BLEPushConnection() + }) } else { - await this.errorLog(`wasn't able to establish BLE Connection, node-switchbot: ${switchbot}`); - await this.BLEPushConnection(); + await this.errorLog(`wasn't able to establish BLE Connection, node-switchbot: ${switchbot}`) + await this.BLEPushConnection() } } else { - await this.debugLog(`No change (BLEpushChanges), Active: ${this.Fan.Active}, ActiveCached: ${this.accessory.context.Active}`); + await this.debugLog(`No change (BLEpushChanges), Active: ${this.Fan.Active}, ActiveCached: ${this.accessory.context.Active}`) } } async openAPIpushChanges() { - await this.debugLog('openAPIpushChanges'); + await this.debugLog('openAPIpushChanges') if (this.Fan.Active !== this.accessory.context.Active) { - const command = this.Fan.Active ? 'turnOn' : 'turnOff'; + const command = this.Fan.Active ? 'turnOn' : 'turnOff' const bodyChange = JSON.stringify({ command: `${command}`, parameter: 'default', commandType: 'command', - }); - await this.debugLog(`SwitchBot OpenAPI bodyChange: ${JSON.stringify(bodyChange)}`); + }) + await this.debugLog(`SwitchBot OpenAPI bodyChange: ${JSON.stringify(bodyChange)}`) try { - const { body, statusCode } = await this.pushChangeRequest(bodyChange); - const deviceStatus: any = await body.json(); - await this.debugLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`); + const { body, statusCode } = await this.pushChangeRequest(bodyChange) + const deviceStatus: any = await body.json() + await this.debugLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`) if (await this.successfulStatusCodes(statusCode, deviceStatus)) { - await this.debugSuccessLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`); - await this.updateHomeKitCharacteristics(); + await this.debugSuccessLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`) + await this.updateHomeKitCharacteristics() } else { - await this.statusCode(statusCode); - await this.statusCode(deviceStatus.statusCode); + await this.statusCode(statusCode) + await this.statusCode(deviceStatus.statusCode) } } catch (e: any) { - await this.apiError(e); - await this.errorLog(`failed openAPIpushChanges with ${this.device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`); + await this.apiError(e) + await this.errorLog(`failed openAPIpushChanges with ${this.device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`) } } else { - await this.debugLog(`No changes (openAPIpushChanges), Active: ${this.Fan.Active}, ActiveCached: ${this.accessory.context.Active}`); + await this.debugLog(`No changes (openAPIpushChanges), Active: ${this.Fan.Active}, ActiveCached: ${this.accessory.context.Active}`) } } async pushRotationSpeedChanges(): Promise { - await this.debugLog('pushRotationSpeedChanges'); + await this.debugLog('pushRotationSpeedChanges') if (this.Fan.SwingMode !== this.accessory.context.SwingMode) { const bodyChange = JSON.stringify({ command: 'setWindSpeed', parameter: `${this.Fan.RotationSpeed}`, commandType: 'command', - }); - await this.debugLog(`SwitchBot OpenAPI bodyChange: ${JSON.stringify(bodyChange)}`); + }) + await this.debugLog(`SwitchBot OpenAPI bodyChange: ${JSON.stringify(bodyChange)}`) try { - const { body, statusCode } = await this.pushChangeRequest(bodyChange); - const deviceStatus: any = await body.json(); - await this.debugLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`); + const { body, statusCode } = await this.pushChangeRequest(bodyChange) + const deviceStatus: any = await body.json() + await this.debugLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`) if (await this.successfulStatusCodes(statusCode, deviceStatus)) { - await this.debugSuccessLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`); - await this.updateHomeKitCharacteristics(); + await this.debugSuccessLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`) + await this.updateHomeKitCharacteristics() } else { - await this.statusCode(statusCode); - await this.statusCode(deviceStatus.statusCode); + await this.statusCode(statusCode) + await this.statusCode(deviceStatus.statusCode) } } catch (e: any) { - await this.apiError(e); - await this.errorLog(`failed pushRotationSpeedChanges with ${this.device.connectionType} Connection,` - + ` Error Message: ${JSON.stringify(e.message)}`); + await this.apiError(e) + await this.errorLog(`failed pushRotationSpeedChanges with ${this.device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`) } } else { - await this.debugLog(`No changes (pushRotationSpeedChanges), RotationSpeed: ${this.Fan.RotationSpeed},` - + ` RotationSpeedCached: ${this.accessory.context.RotationSpeed}`); + await this.debugLog(`No changes (pushRotationSpeedChanges), RotationSpeed: ${this.Fan.RotationSpeed}, RotationSpeedCached: ${this.accessory.context.RotationSpeed}`) } } async pushSwingModeChanges(): Promise { - await this.debugLog('pushSwingModeChanges'); + await this.debugLog('pushSwingModeChanges') if (this.Fan.SwingMode !== this.accessory.context.SwingMode) { - const parameter = this.Fan.SwingMode === this.hap.Characteristic.SwingMode.SWING_ENABLED ? 'on' : 'off'; + const parameter = this.Fan.SwingMode === this.hap.Characteristic.SwingMode.SWING_ENABLED ? 'on' : 'off' const bodyChange = JSON.stringify({ command: 'setOscillation', parameter: `${parameter}`, commandType: 'command', - }); - await this.debugLog(`SwitchBot OpenAPI bodyChange: ${JSON.stringify(bodyChange)}`); + }) + await this.debugLog(`SwitchBot OpenAPI bodyChange: ${JSON.stringify(bodyChange)}`) try { - const { body, statusCode } = await this.pushChangeRequest(bodyChange); - const deviceStatus: any = await body.json(); - await this.debugLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`); + const { body, statusCode } = await this.pushChangeRequest(bodyChange) + const deviceStatus: any = await body.json() + await this.debugLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`) if (await this.successfulStatusCodes(statusCode, deviceStatus)) { - await this.debugSuccessLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`); - await this.updateHomeKitCharacteristics(); + await this.debugSuccessLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`) + await this.updateHomeKitCharacteristics() } else { - await this.statusCode(statusCode); - await this.statusCode(deviceStatus.statusCode); + await this.statusCode(statusCode) + await this.statusCode(deviceStatus.statusCode) } } catch (e: any) { - await this.apiError(e); - await this.errorLog(`failed pushSwingModeChanges with ${this.device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`); + await this.apiError(e) + await this.errorLog(`failed pushSwingModeChanges with ${this.device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`) } } else { - await this.debugLog(`No changes (pushSwingModeChanges), SwingMode: ${this.Fan.SwingMode},` - + ` SwingModeCached: ${this.accessory.context.SwingMode}`); + await this.debugLog(`No changes (pushSwingModeChanges), SwingMode: ${this.Fan.SwingMode}, SwingModeCached: ${this.accessory.context.SwingMode}`) } } @@ -577,13 +576,13 @@ export class Fan extends deviceBase { */ async ActiveSet(value: CharacteristicValue): Promise { if (this.Fan.Active !== this.accessory.context.Active) { - await this.infoLog(`Set Active: ${value}`); + await this.infoLog(`Set Active: ${value}`) } else { - await this.debugLog(`No Changes, Active: ${value}`); + await this.debugLog(`No Changes, Active: ${value}`) } - this.Fan.Active = value; - this.doFanUpdate.next(); + this.Fan.Active = value + this.doFanUpdate.next() } /** @@ -591,13 +590,13 @@ export class Fan extends deviceBase { */ async RotationSpeedSet(value: CharacteristicValue): Promise { if (this.Fan.RotationSpeed !== this.accessory.context.RotationSpeed) { - await this.infoLog(`Set RotationSpeed ${value}`); + await this.infoLog(`Set RotationSpeed ${value}`) } else { - await this.debugLog(`No Changes, RotationSpeed: ${value}`); + await this.debugLog(`No Changes, RotationSpeed: ${value}`) } - this.Fan.RotationSpeed = value; - this.doFanUpdate.next(); + this.Fan.RotationSpeed = value + this.doFanUpdate.next() } /** @@ -605,13 +604,13 @@ export class Fan extends deviceBase { */ async SwingModeSet(value: CharacteristicValue): Promise { if (this.Fan.SwingMode !== this.accessory.context.SwingMode) { - await this.infoLog(`Set SwingMode ${value}`); + await this.infoLog(`Set SwingMode ${value}`) } else { - await this.debugLog(`No Changes, SwingMode: ${value}`); + await this.debugLog(`No Changes, SwingMode: ${value}`) } - this.Fan.SwingMode = value; - this.doFanUpdate.next(); + this.Fan.SwingMode = value + this.doFanUpdate.next() } /** @@ -619,13 +618,13 @@ export class Fan extends deviceBase { */ async OnSet(value: CharacteristicValue): Promise { if (this.LightBulb.On !== this.accessory.context.On) { - await this.infoLog(`Set On: ${value}`); + await this.infoLog(`Set On: ${value}`) } else { - await this.debugLog(`No Changes, On: ${value}`); + await this.debugLog(`No Changes, On: ${value}`) } - this.LightBulb.On = value; - this.doFanUpdate.next(); + this.LightBulb.On = value + this.doFanUpdate.next() } /** @@ -633,76 +632,68 @@ export class Fan extends deviceBase { */ async BrightnessSet(value: CharacteristicValue): Promise { if (this.LightBulb.On && (this.LightBulb.Brightness !== this.accessory.context.Brightness)) { - await this.infoLog(`Set Brightness: ${value}`); + await this.infoLog(`Set Brightness: ${value}`) } else { if (this.LightBulb.On) { - this.debugLog(`No Changes, Brightness: ${value}`); + this.debugLog(`No Changes, Brightness: ${value}`) } else { - this.debugLog(`Set Brightness: ${value}, On: ${this.LightBulb.On}`); + this.debugLog(`Set Brightness: ${value}, On: ${this.LightBulb.On}`) } } - this.LightBulb.Brightness = value; - this.doFanUpdate.next(); + this.LightBulb.Brightness = value + this.doFanUpdate.next() } async updateHomeKitCharacteristics(): Promise { // Active - await this.updateCharacteristic(this.Fan.Service, this.hap.Characteristic.Active, - this.Fan.Active, 'Active'); + await this.updateCharacteristic(this.Fan.Service, this.hap.Characteristic.Active, this.Fan.Active, 'Active') // RotationSpeed - await this.updateCharacteristic(this.Fan.Service, this.hap.Characteristic.RotationSpeed, - this.Fan.RotationSpeed, 'RotationSpeed'); + await this.updateCharacteristic(this.Fan.Service, this.hap.Characteristic.RotationSpeed, this.Fan.RotationSpeed, 'RotationSpeed') // SwingMode - await this.updateCharacteristic(this.Fan.Service, this.hap.Characteristic.SwingMode, - this.Fan.SwingMode, 'SwingMode'); + await this.updateCharacteristic(this.Fan.Service, this.hap.Characteristic.SwingMode, this.Fan.SwingMode, 'SwingMode') // BatteryLevel - await this.updateCharacteristic(this.Battery.Service, this.hap.Characteristic.BatteryLevel, - this.Battery.BatteryLevel, 'BatteryLevel'); + await this.updateCharacteristic(this.Battery.Service, this.hap.Characteristic.BatteryLevel, this.Battery.BatteryLevel, 'BatteryLevel') // ChargingState - await this.updateCharacteristic(this.Battery.Service, this.hap.Characteristic.ChargingState, - this.Battery.ChargingState, 'ChargingState'); + await this.updateCharacteristic(this.Battery.Service, this.hap.Characteristic.ChargingState, this.Battery.ChargingState, 'ChargingState') // StatusLowBattery - await this.updateCharacteristic(this.Battery.Service, this.hap.Characteristic.StatusLowBattery, - this.Battery.StatusLowBattery, 'StatusLowBattery'); + await this.updateCharacteristic(this.Battery.Service, this.hap.Characteristic.StatusLowBattery, this.Battery.StatusLowBattery, 'StatusLowBattery') // On - await this.updateCharacteristic(this.LightBulb.Service, this.hap.Characteristic.On, - this.LightBulb.On, 'On'); + await this.updateCharacteristic(this.LightBulb.Service, this.hap.Characteristic.On, this.LightBulb.On, 'On') // Brightness - await this.updateCharacteristic(this.LightBulb.Service, this.hap.Characteristic.Brightness, - this.LightBulb.Brightness, 'Brightness'); + await this.updateCharacteristic(this.LightBulb.Service, this.hap.Characteristic.Brightness, this.LightBulb.Brightness, 'Brightness') } async BLEPushConnection() { if (this.platform.config.credentials?.token && this.device.connectionType === 'BLE/OpenAPI') { - await this.warnLog('Using OpenAPI Connection to Push Changes'); - await this.openAPIpushChanges(); + await this.warnLog('Using OpenAPI Connection to Push Changes') + await this.openAPIpushChanges() } } async BLERefreshConnection(switchbot: any): Promise { - await this.errorLog(`wasn't able to establish BLE Connection, node-switchbot: ${switchbot}`); + await this.errorLog(`wasn't able to establish BLE Connection, node-switchbot: ${switchbot}`) if (this.platform.config.credentials?.token && this.device.connectionType === 'BLE/OpenAPI') { - await this.warnLog('Using OpenAPI Connection to Refresh Status'); - await this.openAPIRefreshStatus(); + await this.warnLog('Using OpenAPI Connection to Refresh Status') + await this.openAPIRefreshStatus() } } async offlineOff(): Promise { if (this.device.offline) { - this.Fan.Service.updateCharacteristic(this.hap.Characteristic.Active, this.hap.Characteristic.Active.INACTIVE); - this.Fan.Service.updateCharacteristic(this.hap.Characteristic.RotationSpeed, 0); - this.Fan.Service.updateCharacteristic(this.hap.Characteristic.SwingMode, this.hap.Characteristic.SwingMode.SWING_DISABLED); - this.LightBulb.Service.updateCharacteristic(this.hap.Characteristic.On, false); - this.LightBulb.Service.updateCharacteristic(this.hap.Characteristic.Brightness, 0); + this.Fan.Service.updateCharacteristic(this.hap.Characteristic.Active, this.hap.Characteristic.Active.INACTIVE) + this.Fan.Service.updateCharacteristic(this.hap.Characteristic.RotationSpeed, 0) + this.Fan.Service.updateCharacteristic(this.hap.Characteristic.SwingMode, this.hap.Characteristic.SwingMode.SWING_DISABLED) + this.LightBulb.Service.updateCharacteristic(this.hap.Characteristic.On, false) + this.LightBulb.Service.updateCharacteristic(this.hap.Characteristic.Brightness, 0) } } async apiError(e: any): Promise { - this.Fan.Service.updateCharacteristic(this.hap.Characteristic.Active, e); - this.Fan.Service.updateCharacteristic(this.hap.Characteristic.RotationSpeed, e); - this.Fan.Service.updateCharacteristic(this.hap.Characteristic.SwingMode, e); - this.LightBulb.Service.updateCharacteristic(this.hap.Characteristic.On, e); - this.LightBulb.Service.updateCharacteristic(this.hap.Characteristic.Brightness, e); + this.Fan.Service.updateCharacteristic(this.hap.Characteristic.Active, e) + this.Fan.Service.updateCharacteristic(this.hap.Characteristic.RotationSpeed, e) + this.Fan.Service.updateCharacteristic(this.hap.Characteristic.SwingMode, e) + this.LightBulb.Service.updateCharacteristic(this.hap.Characteristic.On, e) + this.LightBulb.Service.updateCharacteristic(this.hap.Characteristic.Brightness, e) } } diff --git a/src/device/hub.ts b/src/device/hub.ts index ece8c36d..91c80576 100644 --- a/src/device/hub.ts +++ b/src/device/hub.ts @@ -2,273 +2,273 @@ * * hub.ts: @switchbot/homebridge-switchbot. */ -import { Units } from 'homebridge'; -import { deviceBase } from './device.js'; -import { Subject, interval, skipWhile} from 'rxjs'; -import { validHumidity, convertUnits } from '../utils.js'; -import { SwitchBotBLEModel, SwitchBotBLEModelName } from 'node-switchbot'; - -import type { devicesConfig } from '../settings.js'; -import type { device } from '../types/devicelist.js'; -import type { SwitchBotPlatform } from '../platform.js'; -import type { hub2ServiceData } from '../types/bledevicestatus.js'; -import type { hub2Status } from '../types/devicestatus.js'; -import type { hub2WebhookContext } from '../types/devicewebhookstatus.js'; -import type { CharacteristicValue, PlatformAccessory, Service } from 'homebridge'; +import type { CharacteristicValue, PlatformAccessory, Service } from 'homebridge' + +import type { SwitchBotPlatform } from '../platform.js' +import type { devicesConfig } from '../settings.js' +import type { hub2ServiceData } from '../types/bledevicestatus.js' +import type { device } from '../types/devicelist.js' +import type { hub2Status } from '../types/devicestatus.js' +import type { hub2WebhookContext } from '../types/devicewebhookstatus.js' + +import { Units } from 'homebridge' +/* +* For Testing Locally: +* import { SwitchBotBLEModel, SwitchBotBLEModelName } from '/Users/Shared/GitHub/OpenWonderLabs/node-switchbot/dist/index.js'; +*/ +import { SwitchBotBLEModel, SwitchBotBLEModelName } from 'node-switchbot' +import { interval, skipWhile, Subject } from 'rxjs' + +import { convertUnits, validHumidity } from '../utils.js' +import { deviceBase } from './device.js' export class Hub extends deviceBase { // Services private LightSensor?: { - Name: CharacteristicValue; - Service: Service; - CurrentAmbientLightLevel: CharacteristicValue; - }; + Name: CharacteristicValue + Service: Service + CurrentAmbientLightLevel: CharacteristicValue + } private HumiditySensor?: { - Name: CharacteristicValue; - Service: Service; - CurrentRelativeHumidity: CharacteristicValue; - }; + Name: CharacteristicValue + Service: Service + CurrentRelativeHumidity: CharacteristicValue + } private TemperatureSensor?: { - Name: CharacteristicValue; - Service: Service; - CurrentTemperature: CharacteristicValue; - }; + Name: CharacteristicValue + Service: Service + CurrentTemperature: CharacteristicValue + } // OpenAPI - deviceStatus!: hub2Status; + deviceStatus!: hub2Status - //Webhook - webhookContext!: hub2WebhookContext; + // Webhook + webhookContext!: hub2WebhookContext // BLE - serviceData!: hub2ServiceData; + serviceData!: hub2ServiceData // Updates - hubUpdateInProgress!: boolean; - doHubUpdate!: Subject; + hubUpdateInProgress!: boolean + doHubUpdate!: Subject constructor( readonly platform: SwitchBotPlatform, accessory: PlatformAccessory, device: device & devicesConfig, ) { - super(platform, accessory, device); + super(platform, accessory, device) // Set category - accessory.category = this.hap.Categories.SENSOR; + accessory.category = this.hap.Categories.SENSOR // this is subject we use to track when we need to POST changes to the SwitchBot API - this.doHubUpdate = new Subject(); - this.hubUpdateInProgress = false; + this.doHubUpdate = new Subject() + this.hubUpdateInProgress = false // Initialize Temperature Sensor Service if (device.hub?.hide_temperature) { if (this.TemperatureSensor) { - this.debugLog('Removing Temperature Sensor Service'); - this.TemperatureSensor.Service = this.accessory.getService(this.hap.Service.TemperatureSensor) as Service; - accessory.removeService(this.TemperatureSensor.Service); + this.debugLog('Removing Temperature Sensor Service') + this.TemperatureSensor.Service = this.accessory.getService(this.hap.Service.TemperatureSensor) as Service + accessory.removeService(this.TemperatureSensor.Service) } } else { - accessory.context.TemperatureSensor = accessory.context.TemperatureSensor ?? {}; + accessory.context.TemperatureSensor = accessory.context.TemperatureSensor ?? {} this.TemperatureSensor = { Name: `${accessory.displayName} Temperature Sensor`, Service: accessory.getService(this.hap.Service.TemperatureSensor) ?? this.accessory.addService(this.hap.Service.TemperatureSensor) as Service, CurrentTemperature: accessory.context.CurrentTemperature ?? 0, - }; - accessory.context.TemperatureSensor = this.TemperatureSensor as object; + } + accessory.context.TemperatureSensor = this.TemperatureSensor as object // Initialize Temperature Sensor Characteristic - this.TemperatureSensor.Service - .setCharacteristic(this.hap.Characteristic.Name, this.TemperatureSensor.Name) - .getCharacteristic(this.hap.Characteristic.CurrentTemperature) - .setProps({ - unit: Units['CELSIUS'], - validValueRanges: [-273.15, 100], - minValue: -273.15, - maxValue: 100, - minStep: 0.1, - }) - .onGet(() => { - return this.TemperatureSensor!.CurrentTemperature; - }); + this.TemperatureSensor.Service.setCharacteristic(this.hap.Characteristic.Name, this.TemperatureSensor.Name).getCharacteristic(this.hap.Characteristic.CurrentTemperature).setProps({ + unit: Units.CELSIUS, + validValueRanges: [-273.15, 100], + minValue: -273.15, + maxValue: 100, + minStep: 0.1, + }).onGet(() => { + return this.TemperatureSensor!.CurrentTemperature + }) } // Initialize Humidity Sensor Service if (device.hub?.hide_humidity) { if (this.HumiditySensor) { - this.debugLog('Removing Humidity Sensor Service'); - this.HumiditySensor.Service = this.accessory.getService(this.hap.Service.HumiditySensor) as Service; - accessory.removeService(this.HumiditySensor.Service); + this.debugLog('Removing Humidity Sensor Service') + this.HumiditySensor.Service = this.accessory.getService(this.hap.Service.HumiditySensor) as Service + accessory.removeService(this.HumiditySensor.Service) } } else { - accessory.context.HumiditySensor = accessory.context.HumiditySensor ?? {}; + accessory.context.HumiditySensor = accessory.context.HumiditySensor ?? {} this.HumiditySensor = { Name: `${accessory.displayName} Humidity Sensor`, Service: accessory.getService(this.hap.Service.HumiditySensor) ?? this.accessory.addService(this.hap.Service.HumiditySensor) as Service, CurrentRelativeHumidity: accessory.context.CurrentRelativeHumidity ?? 0, - }; - accessory.context.HumiditySensor = this.HumiditySensor as object; + } + accessory.context.HumiditySensor = this.HumiditySensor as object // Initialize Humidity Sensor Characteristics - this.HumiditySensor!.Service - .setCharacteristic(this.hap.Characteristic.Name, this.HumiditySensor.Name) - .getCharacteristic(this.hap.Characteristic.CurrentRelativeHumidity) - .setProps({ - minStep: 0.1, - }) - .onGet(() => { - return this.HumiditySensor!.CurrentRelativeHumidity; - }); + this.HumiditySensor!.Service.setCharacteristic(this.hap.Characteristic.Name, this.HumiditySensor.Name).getCharacteristic(this.hap.Characteristic.CurrentRelativeHumidity).setProps({ + minStep: 0.1, + }).onGet(() => { + return this.HumiditySensor!.CurrentRelativeHumidity + }) } // Initialize Light Sensor Service if (device.hub?.hide_lightsensor) { if (this.LightSensor) { - this.debugLog('Removing Light Sensor Service'); - this.LightSensor.Service = this.accessory.getService(this.hap.Service.LightSensor) as Service; - accessory.removeService(this.LightSensor.Service); + this.debugLog('Removing Light Sensor Service') + this.LightSensor.Service = this.accessory.getService(this.hap.Service.LightSensor) as Service + accessory.removeService(this.LightSensor.Service) } } else { - accessory.context.LightSensor = accessory.context.LightSensor ?? {}; + accessory.context.LightSensor = accessory.context.LightSensor ?? {} this.LightSensor = { Name: `${accessory.displayName} Light Sensor`, Service: accessory.getService(this.hap.Service.LightSensor) ?? this.accessory.addService(this.hap.Service.LightSensor) as Service, CurrentAmbientLightLevel: accessory.context.CurrentAmbientLightLevel ?? 0.0001, - }; - accessory.context.LightSensor = this.LightSensor as object; + } + accessory.context.LightSensor = this.LightSensor as object // Initialize Light Sensor Characteristics - this.LightSensor!.Service - .setCharacteristic(this.hap.Characteristic.Name, this.LightSensor.Name) - .getCharacteristic(this.hap.Characteristic.CurrentAmbientLightLevel) - .setProps({ - minStep: 1, - }) - .onGet(() => { - return this.LightSensor!.CurrentAmbientLightLevel; - }); + this.LightSensor!.Service.setCharacteristic(this.hap.Characteristic.Name, this.LightSensor.Name).getCharacteristic(this.hap.Characteristic.CurrentAmbientLightLevel).setProps({ + minStep: 1, + }).onGet(() => { + return this.LightSensor!.CurrentAmbientLightLevel + }) } // Retrieve initial values and updateHomekit - this.debugLog('Retrieve initial values and update Homekit'); - this.refreshStatus(); + try { + this.debugLog('Retrieve initial values and update Homekit') + this.refreshStatus() + } catch (e: any) { + this.errorLog(`failed to retrieve initial values and update Homekit, Error: ${e}`) + } - //regisiter webhook event handler - this.debugLog('Registering Webhook Event Handler'); - this.registerWebhook(); + // regisiter webhook event handler if enabled + try { + this.debugLog('Registering Webhook Event Handler') + this.registerWebhook() + } catch (e: any) { + this.errorLog(`failed to registerWebhook, Error: ${e}`) + } + + // regisiter platform BLE event handler if enabled + try { + this.debugLog('Registering Platform BLE Event Handler') + this.registerPlatformBLE() + } catch (e: any) { + this.errorLog(`failed to registerPlatformBLE, Error: ${e}`) + } // Start an update interval interval(this.deviceRefreshRate * 1000) .pipe(skipWhile(() => this.hubUpdateInProgress)) .subscribe(async () => { - await this.refreshStatus(); - }); - + await this.refreshStatus() + }) } async BLEparseStatus(): Promise { - await this.debugLog('BLEparseStatus'); - await this.debugLog(`(temperature, humidity, lightLevel) = BLE:(${this.serviceData.temperature}, ${this.serviceData.humidity},` - + ` ${this.serviceData.lightLevel}), current:(${this.TemperatureSensor?.CurrentTemperature}, ${this.HumiditySensor?.CurrentRelativeHumidity},` - + ` ${this.LightSensor?.CurrentAmbientLightLevel})`); + await this.debugLog('BLEparseStatus') + await this.debugLog(`(temperature, humidity, lightLevel) = BLE:(${this.serviceData.temperature}, ${this.serviceData.humidity}, ${this.serviceData.lightLevel}), current:(${this.TemperatureSensor?.CurrentTemperature}, ${this.HumiditySensor?.CurrentRelativeHumidity}, ${this.LightSensor?.CurrentAmbientLightLevel})`) // CurrentTemperature if (!this.device.hub?.hide_temperature && this.TemperatureSensor?.Service) { - this.TemperatureSensor.CurrentTemperature = this.serviceData.temperature; - await this.debugLog(`CurrentTemperature: ${this.TemperatureSensor.CurrentTemperature}°c`); + this.TemperatureSensor.CurrentTemperature = this.serviceData.temperature.c + await this.debugLog(`CurrentTemperature: ${this.TemperatureSensor.CurrentTemperature}°c`) } // CurrentRelativeHumidity if (!this.device.hub?.hide_humidity && this.HumiditySensor?.Service) { - this.HumiditySensor!.CurrentRelativeHumidity = validHumidity(this.serviceData.humidity, 0, 100); - await this.debugLog(`CurrentRelativeHumidity: ${this.HumiditySensor.CurrentRelativeHumidity}%`); + this.HumiditySensor!.CurrentRelativeHumidity = validHumidity(this.serviceData.humidity, 0, 100) + await this.debugLog(`CurrentRelativeHumidity: ${this.HumiditySensor.CurrentRelativeHumidity}%`) } // CurrentAmbientLightLevel if (!this.device.hub?.hide_lightsensor && this.LightSensor?.Service) { - const set_minLux = this.device.blindTilt?.set_minLux ?? 1; - const set_maxLux = this.device.blindTilt?.set_maxLux ?? 6001; - const lightLevel = this.serviceData.lightLevel; - this.LightSensor.CurrentAmbientLightLevel = await this.getLightLevel(lightLevel, set_minLux, set_maxLux, 19); - await this.debugLog(`LightLevel: ${this.serviceData.lightLevel}, CurrentAmbientLightLevel: ${this.LightSensor.CurrentAmbientLightLevel}`); + const set_minLux = this.device.blindTilt?.set_minLux ?? 1 + const set_maxLux = this.device.blindTilt?.set_maxLux ?? 6001 + const lightLevel = this.serviceData.lightLevel + this.LightSensor.CurrentAmbientLightLevel = await this.getLightLevel(lightLevel, set_minLux, set_maxLux, 19) + await this.debugLog(`LightLevel: ${this.serviceData.lightLevel}, CurrentAmbientLightLevel: ${this.LightSensor.CurrentAmbientLightLevel}`) } } async openAPIparseStatus(): Promise { - await this.debugLog('openAPIparseStatus'); - await this.debugLog(`(temperature, humidity, lightLevel) = OpenAPI:(${this.deviceStatus.temperature}, ${this.deviceStatus.humidity},` - + ` ${this.deviceStatus.lightLevel}), current:(${this.TemperatureSensor?.CurrentTemperature}, ${this.HumiditySensor?.CurrentRelativeHumidity},` - + ` ${this.LightSensor?.CurrentAmbientLightLevel})`); + await this.debugLog('openAPIparseStatus') + await this.debugLog(`(temperature, humidity, lightLevel) = OpenAPI:(${this.deviceStatus.temperature}, ${this.deviceStatus.humidity}, ${this.deviceStatus.lightLevel}), current:(${this.TemperatureSensor?.CurrentTemperature}, ${this.HumiditySensor?.CurrentRelativeHumidity}, ${this.LightSensor?.CurrentAmbientLightLevel})`) // CurrentRelativeHumidity if (!this.device.hub?.hide_humidity && this.HumiditySensor?.Service) { - this.HumiditySensor.CurrentRelativeHumidity = this.deviceStatus.humidity; - await this.debugLog(`CurrentRelativeHumidity: ${this.HumiditySensor.CurrentRelativeHumidity}%`); + this.HumiditySensor.CurrentRelativeHumidity = this.deviceStatus.humidity + await this.debugLog(`CurrentRelativeHumidity: ${this.HumiditySensor.CurrentRelativeHumidity}%`) } // CurrentTemperature if (!this.device.hub?.hide_temperature && this.TemperatureSensor?.Service) { - this.TemperatureSensor.CurrentTemperature = this.deviceStatus.temperature; - await this.debugLog(`CurrentTemperature: ${this.TemperatureSensor.CurrentTemperature}°c`); + this.TemperatureSensor.CurrentTemperature = this.deviceStatus.temperature + await this.debugLog(`CurrentTemperature: ${this.TemperatureSensor.CurrentTemperature}°c`) } // LightSensor if (!this.device.hub?.hide_lightsensor && this.LightSensor?.Service) { - const set_minLux = this.device.blindTilt?.set_minLux ?? 1; - const set_maxLux = this.device.blindTilt?.set_maxLux ?? 6001; - const lightLevel = this.deviceStatus.lightLevel; - this.LightSensor.CurrentAmbientLightLevel = await this.getLightLevel(lightLevel, set_minLux, set_maxLux, 19); - await this.debugLog(`LightLevel: ${this.deviceStatus.lightLevel}, CurrentAmbientLightLevel: ${this.LightSensor!.CurrentAmbientLightLevel}`); + const set_minLux = this.device.blindTilt?.set_minLux ?? 1 + const set_maxLux = this.device.blindTilt?.set_maxLux ?? 6001 + const lightLevel = this.deviceStatus.lightLevel + this.LightSensor.CurrentAmbientLightLevel = await this.getLightLevel(lightLevel, set_minLux, set_maxLux, 19) + await this.debugLog(`LightLevel: ${this.deviceStatus.lightLevel}, CurrentAmbientLightLevel: ${this.LightSensor!.CurrentAmbientLightLevel}`) } // Firmware Version if (this.deviceStatus.version) { - const version = this.deviceStatus.version.toString(); - await this.debugLog(`Firmware Version: ${version.replace(/^V|-.*$/g, '')}`); - const deviceVersion = version.replace(/^V|-.*$/g, '') ?? '0.0.0'; + const version = this.deviceStatus.version.toString() + await this.debugLog(`Firmware Version: ${version.replace(/^V|-.*$/g, '')}`) + const deviceVersion = version.replace(/^V|-.*$/g, '') ?? '0.0.0' this.accessory .getService(this.hap.Service.AccessoryInformation)! .setCharacteristic(this.hap.Characteristic.HardwareRevision, deviceVersion) .setCharacteristic(this.hap.Characteristic.FirmwareRevision, deviceVersion) .getCharacteristic(this.hap.Characteristic.FirmwareRevision) - .updateValue(deviceVersion); - this.accessory.context.version = deviceVersion; - await this.debugSuccessLog(`version: ${this.accessory.context.version}`); + .updateValue(deviceVersion) + this.accessory.context.version = deviceVersion + await this.debugSuccessLog(`version: ${this.accessory.context.version}`) } } async parseStatusWebhook(): Promise { - await this.debugLog('parseStatusWebhook'); - await this.debugLog(`(scale, temperature, humidity, lightLevel) = Webhook:(${this.webhookContext.scale},` - + ` ${convertUnits(this.webhookContext.temperature, this.webhookContext.scale, this.device.hub?.convertUnitTo)},` - + ` ${this.webhookContext.humidity}, ${this.webhookContext.lightLevel}), current:(${this.TemperatureSensor?.CurrentTemperature},` - + ` ${this.HumiditySensor?.CurrentRelativeHumidity}, ${this.LightSensor?.CurrentAmbientLightLevel})`); + await this.debugLog('parseStatusWebhook') + await this.debugLog(`(scale, temperature, humidity, lightLevel) = Webhook:(${this.webhookContext.scale}, ${convertUnits(this.webhookContext.temperature, this.webhookContext.scale, this.device.hub?.convertUnitTo)}, ${this.webhookContext.humidity}, ${this.webhookContext.lightLevel}), current:(${this.TemperatureSensor?.CurrentTemperature}, ${this.HumiditySensor?.CurrentRelativeHumidity}, ${this.LightSensor?.CurrentAmbientLightLevel})`) // Check if the scale is not CELSIUS if (this.webhookContext.scale !== 'CELSIUS' && this.device.hub?.convertUnitTo === undefined) { - await this.warnLog(`received a non-CELSIUS Webhook scale: ${this.webhookContext.scale}, Use the *convertUnitsTo* config under Hub settings,` - + ' if displaying incorrectly in HomeKit.'); + await this.warnLog(`received a non-CELSIUS Webhook scale: ${this.webhookContext.scale}, Use the *convertUnitsTo* config under Hub settings, if displaying incorrectly in HomeKit.`) } // CurrentRelativeHumidity if (!this.device.hub?.hide_humidity && this.HumiditySensor?.Service) { - this.HumiditySensor.CurrentRelativeHumidity = this.webhookContext.humidity; - await this.debugLog(`CurrentRelativeHumidity: ${this.HumiditySensor.CurrentRelativeHumidity}`); + this.HumiditySensor.CurrentRelativeHumidity = this.webhookContext.humidity + await this.debugLog(`CurrentRelativeHumidity: ${this.HumiditySensor.CurrentRelativeHumidity}`) } // CurrentTemperature if (!this.device.hub?.hide_temperature && this.TemperatureSensor?.Service) { - this.TemperatureSensor.CurrentTemperature = convertUnits(this.webhookContext.temperature, this.webhookContext.scale, - this.device.hub?.convertUnitTo); - await this.debugLog(`CurrentTemperature: ${this.TemperatureSensor.CurrentTemperature}`); + this.TemperatureSensor.CurrentTemperature = convertUnits(this.webhookContext.temperature, this.webhookContext.scale, this.device.hub?.convertUnitTo) + await this.debugLog(`CurrentTemperature: ${this.TemperatureSensor.CurrentTemperature}`) } // CurrentAmbientLightLevel if (!this.device.hub?.hide_lightsensor && this.LightSensor?.Service) { - const set_minLux = this.device.blindTilt?.set_minLux ?? 1; - const set_maxLux = this.device.blindTilt?.set_maxLux ?? 6001; - this.LightSensor.CurrentAmbientLightLevel = await this.getLightLevel(this.webhookContext.lightLevel, set_minLux, set_maxLux, 19); - await this.debugLog(`CurrentAmbientLightLevel: ${this.LightSensor.CurrentAmbientLightLevel}`); + const set_minLux = this.device.blindTilt?.set_minLux ?? 1 + const set_maxLux = this.device.blindTilt?.set_maxLux ?? 6001 + this.LightSensor.CurrentAmbientLightLevel = await this.getLightLevel(this.webhookContext.lightLevel, set_minLux, set_maxLux, 19) + await this.debugLog(`CurrentAmbientLightLevel: ${this.LightSensor.CurrentAmbientLightLevel}`) } } @@ -277,93 +277,97 @@ export class Hub extends deviceBase { */ async refreshStatus(): Promise { if (!this.device.enableCloudService && this.OpenAPI) { - await this.errorLog(`refreshStatus enableCloudService: ${this.device.enableCloudService}`); - } else if (this.BLE || this.config.options?.BLE) { - await this.BLERefreshStatus(); + await this.errorLog(`refreshStatus enableCloudService: ${this.device.enableCloudService}`) + } else if (this.BLE) { + await this.BLERefreshStatus() } else if (this.OpenAPI && this.platform.config.credentials?.token) { - await this.openAPIRefreshStatus(); + await this.openAPIRefreshStatus() } else { - await this.offlineOff(); - await this.debugWarnLog(`Connection Type: ${this.device.connectionType}, refreshStatus will not happen.`); + await this.offlineOff() + await this.debugWarnLog(`Connection Type: ${this.device.connectionType}, refreshStatus will not happen.`) } } async BLERefreshStatus(): Promise { - await this.debugLog('BLERefreshStatus'); + await this.debugLog('BLERefreshStatus') + const switchbot = await this.switchbotBLE() + if (switchbot === undefined) { + await this.BLERefreshConnection(switchbot) + } else { + // Start to monitor advertisement packets + (async () => { + // Start to monitor advertisement packets + const serviceData = await this.monitorAdvertisementPackets(switchbot) as hub2ServiceData + // Update HomeKit + if (serviceData.model === SwitchBotBLEModel.Hub2 && serviceData.modelName === SwitchBotBLEModelName.Hub2) { + this.serviceData = serviceData + await this.BLEparseStatus() + await this.updateHomeKitCharacteristics() + } else { + await this.errorLog(`failed to get serviceData, serviceData: ${serviceData}`) + await this.BLERefreshConnection(switchbot) + } + })() + } + } + + async registerPlatformBLE(): Promise { + await this.debugLog('registerPlatformBLE') if (this.config.options?.BLE) { - await this.debugLog('is listening to Platform BLE.'); - this.device.bleMac = this.device.deviceId!.match(/.{1,2}/g)!.join(':').toLowerCase(); - await this.debugLog(`bleMac: ${this.device.bleMac}`); + await this.debugLog('is listening to Platform BLE.') + this.device.bleMac = this.device.deviceId!.match(/.{1,2}/g)!.join(':').toLowerCase() + await this.debugLog(`bleMac: ${this.device.bleMac}`) this.platform.bleEventHandler[this.device.bleMac] = async (context: hub2ServiceData) => { try { - await this.debugLog(`received BLE: ${JSON.stringify(context)}`); - this.serviceData = context; - await this.BLEparseStatus(); - await this.updateHomeKitCharacteristics(); + await this.debugLog(`received BLE: ${JSON.stringify(context)}`) + this.serviceData = context + await this.BLEparseStatus() + await this.updateHomeKitCharacteristics() } catch (e: any) { - await this.errorLog(`failed to handle BLE. Received: ${JSON.stringify(context)} Error: ${e}`); + await this.errorLog(`failed to handle BLE. Received: ${JSON.stringify(context)} Error: ${e}`) } - }; - } else { - await this.debugLog('is using Device BLE Scanning.'); - const switchbot = await this.switchbotBLE(); - if (switchbot === undefined) { - await this.BLERefreshConnection(switchbot); - } else { - // Start to monitor advertisement packets - (async () => { - // Start to monitor advertisement packets - const serviceData = await this.monitorAdvertisementPackets(switchbot) as hub2ServiceData; - // Update HomeKit - if (serviceData.model === SwitchBotBLEModel.Hub2 && serviceData.modelName === SwitchBotBLEModelName.Hub2) { - this.serviceData = serviceData; - await this.BLEparseStatus(); - await this.updateHomeKitCharacteristics(); - } else { - await this.errorLog(`failed to get serviceData, serviceData: ${serviceData}`); - await this.BLERefreshConnection(switchbot); - } - })(); } + } else { + await this.debugLog('is not listening to Platform BLE') } } async openAPIRefreshStatus(): Promise { - await this.debugLog('openAPIRefreshStatus'); + await this.debugLog('openAPIRefreshStatus') try { - const { body, statusCode } = await this.deviceRefreshStatus(); - const deviceStatus: any = await body.json(); - await this.debugLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`);; + const { body, statusCode } = await this.deviceRefreshStatus() + const deviceStatus: any = await body.json() + await this.debugLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`) if (await this.successfulStatusCodes(statusCode, deviceStatus)) { - await this.debugSuccessLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`); - this.deviceStatus = deviceStatus.body; - await this.openAPIparseStatus(); - await this.updateHomeKitCharacteristics(); + await this.debugSuccessLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`) + this.deviceStatus = deviceStatus.body + await this.openAPIparseStatus() + await this.updateHomeKitCharacteristics() } else { - await this.debugWarnLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`); - await this.debugWarnLog(statusCode, deviceStatus); + await this.debugWarnLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`) + await this.debugWarnLog(statusCode, deviceStatus) } } catch (e: any) { - await this.apiError(e); - await this.errorLog(`failed openAPIRefreshStatus with ${this.device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`); + await this.apiError(e) + await this.errorLog(`failed openAPIRefreshStatus with ${this.device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`) } } async registerWebhook(): Promise { if (this.device.webhook) { - await this.debugLog('is listening webhook.'); + await this.debugLog('is listening webhook.') this.platform.webhookEventHandler[this.device.deviceId] = async (context: hub2WebhookContext) => { try { - await this.debugLog(`received Webhook: ${JSON.stringify(context)}`); - this.webhookContext = context; - await this.parseStatusWebhook(); - await this.updateHomeKitCharacteristics(); + await this.debugLog(`received Webhook: ${JSON.stringify(context)}`) + this.webhookContext = context + await this.parseStatusWebhook() + await this.updateHomeKitCharacteristics() } catch (e: any) { - await this.errorLog(`failed to handle webhook. Received: ${JSON.stringify(context)} Error: ${e}`); + await this.errorLog(`failed to handle webhook. Received: ${JSON.stringify(context)} Error: ${e}`) } - }; - }else { - await this.debugLog('is not listening webhook.'); + } + } else { + await this.debugLog('is not listening webhook.') } } @@ -374,54 +378,49 @@ export class Hub extends deviceBase { async updateHomeKitCharacteristics(): Promise { // CurrentRelativeHumidity if (!this.device.hub?.hide_humidity && this.HumiditySensor?.Service) { - await this.updateCharacteristic(this.HumiditySensor.Service, this.hap.Characteristic.CurrentRelativeHumidity, - this.HumiditySensor.CurrentRelativeHumidity, 'CurrentRelativeHumidity'); + await this.updateCharacteristic(this.HumiditySensor.Service, this.hap.Characteristic.CurrentRelativeHumidity, this.HumiditySensor.CurrentRelativeHumidity, 'CurrentRelativeHumidity') } // CurrentTemperature if (!this.device.hub?.hide_temperature && this.TemperatureSensor?.Service) { - await this.updateCharacteristic(this.TemperatureSensor.Service, this.hap.Characteristic.CurrentTemperature, - this.TemperatureSensor.CurrentTemperature, 'CurrentTemperature'); + await this.updateCharacteristic(this.TemperatureSensor.Service, this.hap.Characteristic.CurrentTemperature, this.TemperatureSensor.CurrentTemperature, 'CurrentTemperature') } // CurrentAmbientLightLevel if (!this.device.hub?.hide_lightsensor && this.LightSensor?.Service) { - await this.updateCharacteristic(this.LightSensor.Service, this.hap.Characteristic.CurrentAmbientLightLevel, - this.LightSensor.CurrentAmbientLightLevel, 'CurrentAmbientLightLevel'); + await this.updateCharacteristic(this.LightSensor.Service, this.hap.Characteristic.CurrentAmbientLightLevel, this.LightSensor.CurrentAmbientLightLevel, 'CurrentAmbientLightLevel') } } async BLERefreshConnection(switchbot: any): Promise { - await this.errorLog(`wasn't able to establish BLE Connection, node-switchbot: ${switchbot}`); + await this.errorLog(`wasn't able to establish BLE Connection, node-switchbot: ${switchbot}`) if (this.platform.config.credentials?.token && this.device.connectionType === 'BLE/OpenAPI') { - await this.warnLog('Using OpenAPI Connection to Refresh Status'); - await this.openAPIRefreshStatus(); + await this.warnLog('Using OpenAPI Connection to Refresh Status') + await this.openAPIRefreshStatus() } } async offlineOff(): Promise { if (this.device.offline) { if (!this.device.hub?.hide_temperature && this.TemperatureSensor?.Service) { - this.TemperatureSensor.Service.updateCharacteristic(this.hap.Characteristic.CurrentTemperature, this.accessory.context.CurrentTemperature); + this.TemperatureSensor.Service.updateCharacteristic(this.hap.Characteristic.CurrentTemperature, this.accessory.context.CurrentTemperature) } if (!this.device.hub?.hide_humidity && this.HumiditySensor?.Service) { - this.HumiditySensor.Service.updateCharacteristic(this.hap.Characteristic.CurrentRelativeHumidity, - this.accessory.context.CurrentRelativeHumidity); + this.HumiditySensor.Service.updateCharacteristic(this.hap.Characteristic.CurrentRelativeHumidity, this.accessory.context.CurrentRelativeHumidity) } if (!this.device.hub?.hide_lightsensor && this.LightSensor?.Service) { - this.LightSensor.Service.updateCharacteristic(this.hap.Characteristic.CurrentAmbientLightLevel, - this.accessory.context.CurrentAmbientLightLevel); + this.LightSensor.Service.updateCharacteristic(this.hap.Characteristic.CurrentAmbientLightLevel, this.accessory.context.CurrentAmbientLightLevel) } } } async apiError(e: any): Promise { if (!this.device.hub?.hide_temperature && this.TemperatureSensor?.Service) { - this.TemperatureSensor.Service.updateCharacteristic(this.hap.Characteristic.CurrentTemperature, e); + this.TemperatureSensor.Service.updateCharacteristic(this.hap.Characteristic.CurrentTemperature, e) } if (!this.device.hub?.hide_humidity && this.HumiditySensor?.Service) { - this.HumiditySensor.Service.updateCharacteristic(this.hap.Characteristic.CurrentRelativeHumidity, e); + this.HumiditySensor.Service.updateCharacteristic(this.hap.Characteristic.CurrentRelativeHumidity, e) } if (!this.device.hub?.hide_lightsensor && this.LightSensor?.Service) { - this.LightSensor.Service.updateCharacteristic(this.hap.Characteristic.CurrentAmbientLightLevel, e); + this.LightSensor.Service.updateCharacteristic(this.hap.Characteristic.CurrentAmbientLightLevel, e) } } } diff --git a/src/device/humidifier.ts b/src/device/humidifier.ts index 129b9597..9291de5a 100644 --- a/src/device/humidifier.ts +++ b/src/device/humidifier.ts @@ -2,18 +2,24 @@ * * humidifier.ts: @switchbot/homebridge-switchbot. */ -import { deviceBase } from './device.js'; -import { convertUnits, validHumidity } from '../utils.js'; -import { SwitchBotBLEModel, SwitchBotBLEModelName } from 'node-switchbot'; -import { Subject, debounceTime, interval, skipWhile, take, tap } from 'rxjs'; - -import type { devicesConfig } from '../settings.js'; -import type { device } from '../types/devicelist.js'; -import type { SwitchBotPlatform } from '../platform.js'; -import type { humidifierServiceData } from '../types/bledevicestatus.js'; -import type { humidifierStatus } from '../types/devicestatus.js'; -import type { humidifierWebhookContext } from '../types/devicewebhookstatus.js'; -import type { Service, PlatformAccessory, CharacteristicValue } from 'homebridge'; +import type { CharacteristicValue, PlatformAccessory, Service } from 'homebridge' + +import type { SwitchBotPlatform } from '../platform.js' +import type { devicesConfig } from '../settings.js' +import type { humidifierServiceData } from '../types/bledevicestatus.js' +import type { device } from '../types/devicelist.js' +import type { humidifierStatus } from '../types/devicestatus.js' +import type { humidifierWebhookContext } from '../types/devicewebhookstatus.js' + +/* +* For Testing Locally: +* import { SwitchBotBLEModel, SwitchBotBLEModelName } from '/Users/Shared/GitHub/OpenWonderLabs/node-switchbot/dist/index.js'; +*/ +import { SwitchBotBLEModel, SwitchBotBLEModelName } from 'node-switchbot' +import { debounceTime, interval, skipWhile, Subject, take, tap } from 'rxjs' + +import { convertUnits, validHumidity } from '../utils.js' +import { deviceBase } from './device.js' /** * Platform Accessory @@ -23,292 +29,276 @@ import type { Service, PlatformAccessory, CharacteristicValue } from 'homebridge export class Humidifier extends deviceBase { // Services private HumidifierDehumidifier: { - Name: CharacteristicValue; - Service: Service; - Active: CharacteristicValue; - WaterLevel: CharacteristicValue; - CurrentRelativeHumidity: CharacteristicValue; - TargetHumidifierDehumidifierState: CharacteristicValue; - CurrentHumidifierDehumidifierState: CharacteristicValue; - RelativeHumidityHumidifierThreshold: CharacteristicValue; - }; + Name: CharacteristicValue + Service: Service + Active: CharacteristicValue + WaterLevel: CharacteristicValue + CurrentRelativeHumidity: CharacteristicValue + TargetHumidifierDehumidifierState: CharacteristicValue + CurrentHumidifierDehumidifierState: CharacteristicValue + RelativeHumidityHumidifierThreshold: CharacteristicValue + } private TemperatureSensor?: { - Name: CharacteristicValue; - Service: Service; - CurrentTemperature: CharacteristicValue; - }; + Name: CharacteristicValue + Service: Service + CurrentTemperature: CharacteristicValue + } // OpenAPI - deviceStatus!: humidifierStatus; + deviceStatus!: humidifierStatus - //Webhook - webhookContext!: humidifierWebhookContext; + // Webhook + webhookContext!: humidifierWebhookContext // BLE - serviceData!: humidifierServiceData; + serviceData!: humidifierServiceData // Updates - humidifierUpdateInProgress!: boolean; - doHumidifierUpdate!: Subject; + humidifierUpdateInProgress!: boolean + doHumidifierUpdate!: Subject constructor( readonly platform: SwitchBotPlatform, accessory: PlatformAccessory, device: device & devicesConfig, ) { - super(platform, accessory, device); + super(platform, accessory, device) // Set category - accessory.category = this.hap.Categories.AIR_HUMIDIFIER; + accessory.category = this.hap.Categories.AIR_HUMIDIFIER // this is subject we use to track when we need to POST changes to the SwitchBot API - this.doHumidifierUpdate = new Subject(); - this.humidifierUpdateInProgress = false; + this.doHumidifierUpdate = new Subject() + this.humidifierUpdateInProgress = false // Initialize the HumidifierDehumidifier Service - accessory.context.HumidifierDehumidifier = accessory.context.HumidifierDehumidifier ?? {}; + accessory.context.HumidifierDehumidifier = accessory.context.HumidifierDehumidifier ?? {} this.HumidifierDehumidifier = { Name: accessory.displayName, - Service: accessory.getService(this.hap.Service.HumidifierDehumidifier) - ?? accessory.addService(this.hap.Service.HumidifierDehumidifier) as Service, + Service: accessory.getService(this.hap.Service.HumidifierDehumidifier) ?? accessory.addService(this.hap.Service.HumidifierDehumidifier) as Service, Active: accessory.context.Active ?? this.hap.Characteristic.Active.ACTIVE, WaterLevel: accessory.context.WaterLevel ?? 100, CurrentRelativeHumidity: accessory.context.CurrentRelativeHumidity ?? 50, - TargetHumidifierDehumidifierState: accessory.context.TargetHumidifierDehumidifierState - ?? this.hap.Characteristic.TargetHumidifierDehumidifierState.HUMIDIFIER, - CurrentHumidifierDehumidifierState: accessory.context.CurrentHumidifierDehumidifierState - ?? this.hap.Characteristic.CurrentHumidifierDehumidifierState.INACTIVE, + TargetHumidifierDehumidifierState: accessory.context.TargetHumidifierDehumidifierState ?? this.hap.Characteristic.TargetHumidifierDehumidifierState.HUMIDIFIER, + CurrentHumidifierDehumidifierState: accessory.context.CurrentHumidifierDehumidifierState ?? this.hap.Characteristic.CurrentHumidifierDehumidifierState.INACTIVE, RelativeHumidityHumidifierThreshold: accessory.context.RelativeHumidityHumidifierThreshold ?? 50, - }; - accessory.context.HumidifierDehumidifier = this.HumidifierDehumidifier as object; + } + accessory.context.HumidifierDehumidifier = this.HumidifierDehumidifier as object // Initialize the HumidifierDehumidifier Characteristics - this.HumidifierDehumidifier.Service - .setCharacteristic(this.hap.Characteristic.Name, this.HumidifierDehumidifier.Name) - .setCharacteristic(this.hap.Characteristic.CurrentHumidifierDehumidifierState, - this.HumidifierDehumidifier.CurrentHumidifierDehumidifierState) - .getCharacteristic(this.hap.Characteristic.TargetHumidifierDehumidifierState) - .setProps({ - validValueRanges: [0, 1], - minValue: 0, - maxValue: 1, - validValues: [0, 1], - }) - .onGet(() => { - return this.HumidifierDehumidifier.TargetHumidifierDehumidifierState; - }) - .onSet(this.TargetHumidifierDehumidifierStateSet.bind(this)); - - this.HumidifierDehumidifier.Service - .getCharacteristic(this.hap.Characteristic.Active) - .onGet(() => { - return this.HumidifierDehumidifier.Active; - }) - .onSet(this.ActiveSet.bind(this)); - - this.HumidifierDehumidifier.Service - .getCharacteristic(this.hap.Characteristic.RelativeHumidityHumidifierThreshold) - .setProps({ - validValueRanges: [0, 100], - minValue: 0, - maxValue: 100, - minStep: device.humidifier?.set_minStep ?? 1, - }) - .onGet(() => { - return this.HumidifierDehumidifier.RelativeHumidityHumidifierThreshold; - }) - .onSet(this.RelativeHumidityHumidifierThresholdSet.bind(this)); + this.HumidifierDehumidifier.Service.setCharacteristic(this.hap.Characteristic.Name, this.HumidifierDehumidifier.Name).setCharacteristic(this.hap.Characteristic.CurrentHumidifierDehumidifierState, this.HumidifierDehumidifier.CurrentHumidifierDehumidifierState).getCharacteristic(this.hap.Characteristic.TargetHumidifierDehumidifierState).setProps({ + validValueRanges: [0, 1], + minValue: 0, + maxValue: 1, + validValues: [0, 1], + }).onGet(() => { + return this.HumidifierDehumidifier.TargetHumidifierDehumidifierState + }).onSet(this.TargetHumidifierDehumidifierStateSet.bind(this)) + + this.HumidifierDehumidifier.Service.getCharacteristic(this.hap.Characteristic.Active).onGet(() => { + return this.HumidifierDehumidifier.Active + }).onSet(this.ActiveSet.bind(this)) + + this.HumidifierDehumidifier.Service.getCharacteristic(this.hap.Characteristic.RelativeHumidityHumidifierThreshold).setProps({ + validValueRanges: [0, 100], + minValue: 0, + maxValue: 100, + minStep: device.humidifier?.set_minStep ?? 1, + }).onGet(() => { + return this.HumidifierDehumidifier.RelativeHumidityHumidifierThreshold + }).onSet(this.RelativeHumidityHumidifierThresholdSet.bind(this)) // Initialize the Temperature Sensor Service if (device.humidifier?.hide_temperature) { if (this.TemperatureSensor) { - this.debugLog('Removing Temperature Sensor Service'); - this.TemperatureSensor!.Service = this.accessory.getService(this.hap.Service.TemperatureSensor) as Service; - accessory.removeService(this.TemperatureSensor!.Service); + this.debugLog('Removing Temperature Sensor Service') + this.TemperatureSensor!.Service = this.accessory.getService(this.hap.Service.TemperatureSensor) as Service + accessory.removeService(this.TemperatureSensor!.Service) } } else { - accessory.context.TemperatureSensor = accessory.context.TemperatureSensor ?? {}; + accessory.context.TemperatureSensor = accessory.context.TemperatureSensor ?? {} this.TemperatureSensor = { Name: `${accessory.displayName} Temperature Sensor`, Service: accessory.getService(this.hap.Service.TemperatureSensor) ?? this.accessory.addService(this.hap.Service.TemperatureSensor) as Service, CurrentTemperature: accessory.context.CurrentTemperature || 30, - }; - accessory.context.TemperatureSensor = this.TemperatureSensor as object; + } + accessory.context.TemperatureSensor = this.TemperatureSensor as object // Initialize the Temperature Sensor Characteristics - this.TemperatureSensor.Service - .setCharacteristic(this.hap.Characteristic.Name, this.TemperatureSensor.Name) - .getCharacteristic(this.hap.Characteristic.CurrentTemperature) - .setProps({ - validValueRanges: [-273.15, 100], - minValue: -273.15, - maxValue: 100, - minStep: 0.1, - }) - .onGet(() => { - return this.TemperatureSensor!.CurrentTemperature; - }); + this.TemperatureSensor.Service.setCharacteristic(this.hap.Characteristic.Name, this.TemperatureSensor.Name).getCharacteristic(this.hap.Characteristic.CurrentTemperature).setProps({ + validValueRanges: [-273.15, 100], + minValue: -273.15, + maxValue: 100, + minStep: 0.1, + }).onGet(() => { + return this.TemperatureSensor!.CurrentTemperature + }) } // Retrieve initial values and updateHomekit - this.debugLog('Retrieve initial values and update Homekit'); - this.refreshStatus(); + try { + this.debugLog('Retrieve initial values and update Homekit') + this.refreshStatus() + } catch (e: any) { + this.errorLog(`failed to retrieve initial values and update Homekit, Error: ${e}`) + } + + // regisiter webhook event handler if enabled + try { + this.debugLog('Registering Webhook Event Handler') + this.registerWebhook() + } catch (e: any) { + this.errorLog(`failed to registerWebhook, Error: ${e}`) + } - //regisiter webhook event handler - this.debugLog('Registering Webhook Event Handler'); - this.registerWebhook(); + // regisiter platform BLE event handler if enabled + try { + this.debugLog('Registering Platform BLE Event Handler') + this.registerPlatformBLE() + } catch (e: any) { + this.errorLog(`failed to registerPlatformBLE, Error: ${e}`) + } // Start an update interval interval(this.deviceRefreshRate * 1000) .pipe(skipWhile(() => this.humidifierUpdateInProgress)) .subscribe(async () => { - await this.refreshStatus(); - }); + await this.refreshStatus() + }) // Watch for Humidifier change events // We put in a debounce of 100ms so we don't make duplicate calls this.doHumidifierUpdate .pipe( tap(() => { - this.humidifierUpdateInProgress = true; + this.humidifierUpdateInProgress = true }), debounceTime(this.devicePushRate * 1000), ) .subscribe(async () => { try { - await this.pushChanges(); + await this.pushChanges() } catch (e: any) { - await this.apiError(e); - await this.errorLog(`failed pushChanges with ${device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`); + await this.apiError(e) + await this.errorLog(`failed pushChanges with ${device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`) } - this.humidifierUpdateInProgress = false; - }); + this.humidifierUpdateInProgress = false + }) } async BLEparseStatus(): Promise { - await this.debugLog('BLEparseStatus'); - await this.debugLog(`(onState, percentage, autoMode) = BLE:(${this.serviceData.onState}, ${this.serviceData.percentage},` - + ` ${this.serviceData.autoMode}), current:(${this.HumidifierDehumidifier.Active}, ${this.HumidifierDehumidifier.CurrentRelativeHumidity},` - + ` ${this.HumidifierDehumidifier.TargetHumidifierDehumidifierState}, ${this.HumidifierDehumidifier.RelativeHumidityHumidifierThreshold})`, - ); + await this.debugLog('BLEparseStatus') + await this.debugLog(`(onState, percentage, autoMode) = BLE:(${this.serviceData.onState}, ${this.serviceData.percentage}, ${this.serviceData.autoMode}), current:(${this.HumidifierDehumidifier.Active}, ${this.HumidifierDehumidifier.CurrentRelativeHumidity}, ${this.HumidifierDehumidifier.TargetHumidifierDehumidifierState}, ${this.HumidifierDehumidifier.RelativeHumidityHumidifierThreshold})`, + ) // Active - this.HumidifierDehumidifier.Active = this.serviceData.onState ? this.hap.Characteristic.Active.ACTIVE : this.hap.Characteristic.Active.INACTIVE; - await this.debugLog(`Active: ${this.HumidifierDehumidifier.Active}`); + this.HumidifierDehumidifier.Active = this.serviceData.onState ? this.hap.Characteristic.Active.ACTIVE : this.hap.Characteristic.Active.INACTIVE + await this.debugLog(`Active: ${this.HumidifierDehumidifier.Active}`) // Current Relative Humidity - this.HumidifierDehumidifier.CurrentRelativeHumidity = validHumidity(this.serviceData.humidity); - await this.debugLog(`CurrentRelativeHumidity: ${this.HumidifierDehumidifier.CurrentRelativeHumidity}`); + this.HumidifierDehumidifier.CurrentRelativeHumidity = validHumidity(this.serviceData.humidity) + await this.debugLog(`CurrentRelativeHumidity: ${this.HumidifierDehumidifier.CurrentRelativeHumidity}`) // Target Humidifier Dehumidifier State switch (this.serviceData.autoMode) { case true: - this.HumidifierDehumidifier.TargetHumidifierDehumidifierState = - this.hap.Characteristic.TargetHumidifierDehumidifierState.HUMIDIFIER_OR_DEHUMIDIFIER; - this.HumidifierDehumidifier.CurrentHumidifierDehumidifierState = this.hap.Characteristic.CurrentHumidifierDehumidifierState.HUMIDIFYING; - this.HumidifierDehumidifier.RelativeHumidityHumidifierThreshold = this.HumidifierDehumidifier.CurrentRelativeHumidity; - break; + this.HumidifierDehumidifier.TargetHumidifierDehumidifierState + = this.hap.Characteristic.TargetHumidifierDehumidifierState.HUMIDIFIER_OR_DEHUMIDIFIER + this.HumidifierDehumidifier.CurrentHumidifierDehumidifierState = this.hap.Characteristic.CurrentHumidifierDehumidifierState.HUMIDIFYING + this.HumidifierDehumidifier.RelativeHumidityHumidifierThreshold = this.HumidifierDehumidifier.CurrentRelativeHumidity + break default: - this.HumidifierDehumidifier.TargetHumidifierDehumidifierState = this.hap.Characteristic.TargetHumidifierDehumidifierState.HUMIDIFIER; - this.HumidifierDehumidifier.RelativeHumidityHumidifierThreshold = this.serviceData.percentage > 100 ? 100 : this.serviceData.percentage; + this.HumidifierDehumidifier.TargetHumidifierDehumidifierState = this.hap.Characteristic.TargetHumidifierDehumidifierState.HUMIDIFIER + this.HumidifierDehumidifier.RelativeHumidityHumidifierThreshold = this.serviceData.percentage > 100 ? 100 : this.serviceData.percentage if (this.HumidifierDehumidifier.CurrentRelativeHumidity > this.HumidifierDehumidifier.RelativeHumidityHumidifierThreshold) { - this.HumidifierDehumidifier.CurrentHumidifierDehumidifierState = this.hap.Characteristic.CurrentHumidifierDehumidifierState.IDLE; + this.HumidifierDehumidifier.CurrentHumidifierDehumidifierState = this.hap.Characteristic.CurrentHumidifierDehumidifierState.IDLE } else if (this.HumidifierDehumidifier.Active === this.hap.Characteristic.Active.INACTIVE) { - this.HumidifierDehumidifier.CurrentHumidifierDehumidifierState = this.hap.Characteristic.CurrentHumidifierDehumidifierState.INACTIVE; + this.HumidifierDehumidifier.CurrentHumidifierDehumidifierState = this.hap.Characteristic.CurrentHumidifierDehumidifierState.INACTIVE } else { - this.HumidifierDehumidifier.CurrentHumidifierDehumidifierState = this.hap.Characteristic.CurrentHumidifierDehumidifierState.HUMIDIFYING; + this.HumidifierDehumidifier.CurrentHumidifierDehumidifierState = this.hap.Characteristic.CurrentHumidifierDehumidifierState.HUMIDIFYING } } - await this.debugLog(`TargetHumidifierDehumidifierState: ${this.HumidifierDehumidifier.TargetHumidifierDehumidifierState},` - + ` RelativeHumidityHumidifierThreshold: ${this.HumidifierDehumidifier.RelativeHumidityHumidifierThreshold},` - + ` CurrentHumidifierDehumidifierState: ${this.HumidifierDehumidifier.CurrentHumidifierDehumidifierState}`); + await this.debugLog(`TargetHumidifierDehumidifierState: ${this.HumidifierDehumidifier.TargetHumidifierDehumidifierState}, RelativeHumidityHumidifierThreshold: ${this.HumidifierDehumidifier.RelativeHumidityHumidifierThreshold}, CurrentHumidifierDehumidifierState: ${this.HumidifierDehumidifier.CurrentHumidifierDehumidifierState}`) } async openAPIparseStatus(): Promise { - await this.debugLog('openAPIparseStatus'); - await this.debugLog(`(power, auto, temperature, lackWater, nebulizationEfficiency, version) = OpenAPI:(${this.deviceStatus.power},` - + ` ${this.deviceStatus.auto}, ${this.deviceStatus.temperature}, ${this.deviceStatus.lackWater},` - + ` ${this.deviceStatus.nebulizationEfficiency}, ${this.deviceStatus.version}), current:(${this.HumidifierDehumidifier.Active},` - + ` ${this.HumidifierDehumidifier.CurrentRelativeHumidity}, ${this.HumidifierDehumidifier.TargetHumidifierDehumidifierState},` - + ` ${this.HumidifierDehumidifier.RelativeHumidityHumidifierThreshold})`); + await this.debugLog('openAPIparseStatus') + await this.debugLog(`(power, auto, temperature, lackWater, nebulizationEfficiency, version) = OpenAPI:(${this.deviceStatus.power}, ${this.deviceStatus.auto}, ${this.deviceStatus.temperature}, ${this.deviceStatus.lackWater}, ${this.deviceStatus.nebulizationEfficiency}, ${this.deviceStatus.version}), current:(${this.HumidifierDehumidifier.Active}, ${this.HumidifierDehumidifier.CurrentRelativeHumidity}, ${this.HumidifierDehumidifier.TargetHumidifierDehumidifierState}, ${this.HumidifierDehumidifier.RelativeHumidityHumidifierThreshold})`) // Active this.HumidifierDehumidifier.Active = this.deviceStatus.power === 'on' - ? this.hap.Characteristic.Active.ACTIVE : this.hap.Characteristic.Active.INACTIVE; - await this.debugLog(`Active: ${this.HumidifierDehumidifier.Active}`); + ? this.hap.Characteristic.Active.ACTIVE + : this.hap.Characteristic.Active.INACTIVE + await this.debugLog(`Active: ${this.HumidifierDehumidifier.Active}`) // Current Relative Humidity - this.HumidifierDehumidifier.CurrentRelativeHumidity = validHumidity(this.deviceStatus.humidity); - await this.debugLog(`CurrentRelativeHumidity: ${this.HumidifierDehumidifier.CurrentRelativeHumidity}`); + this.HumidifierDehumidifier.CurrentRelativeHumidity = validHumidity(this.deviceStatus.humidity) + await this.debugLog(`CurrentRelativeHumidity: ${this.HumidifierDehumidifier.CurrentRelativeHumidity}`) // Current Temperature if (!this.device.humidifier?.hide_temperature && this.TemperatureSensor?.Service) { - this.TemperatureSensor.CurrentTemperature = this.deviceStatus.temperature; - await this.debugLog(`CurrentTemperature: ${this.TemperatureSensor.CurrentTemperature}`); + this.TemperatureSensor.CurrentTemperature = this.deviceStatus.temperature + await this.debugLog(`CurrentTemperature: ${this.TemperatureSensor.CurrentTemperature}`) } // Target Humidifier Dehumidifier State switch (this.deviceStatus.auto) { case true: - this.HumidifierDehumidifier.TargetHumidifierDehumidifierState = - this.hap.Characteristic.TargetHumidifierDehumidifierState.HUMIDIFIER_OR_DEHUMIDIFIER; - this.HumidifierDehumidifier.CurrentHumidifierDehumidifierState = this.hap.Characteristic.CurrentHumidifierDehumidifierState.HUMIDIFYING; - this.HumidifierDehumidifier.RelativeHumidityHumidifierThreshold = this.HumidifierDehumidifier.CurrentRelativeHumidity; - break; + this.HumidifierDehumidifier.TargetHumidifierDehumidifierState + = this.hap.Characteristic.TargetHumidifierDehumidifierState.HUMIDIFIER_OR_DEHUMIDIFIER + this.HumidifierDehumidifier.CurrentHumidifierDehumidifierState = this.hap.Characteristic.CurrentHumidifierDehumidifierState.HUMIDIFYING + this.HumidifierDehumidifier.RelativeHumidityHumidifierThreshold = this.HumidifierDehumidifier.CurrentRelativeHumidity + break default: - this.HumidifierDehumidifier.TargetHumidifierDehumidifierState = this.hap.Characteristic.TargetHumidifierDehumidifierState.HUMIDIFIER; + this.HumidifierDehumidifier.TargetHumidifierDehumidifierState = this.hap.Characteristic.TargetHumidifierDehumidifierState.HUMIDIFIER this.HumidifierDehumidifier.RelativeHumidityHumidifierThreshold = this.deviceStatus.nebulizationEfficiency > 100 - ? 100 : this.deviceStatus.nebulizationEfficiency; + ? 100 + : this.deviceStatus.nebulizationEfficiency if (this.HumidifierDehumidifier.CurrentRelativeHumidity > this.HumidifierDehumidifier.RelativeHumidityHumidifierThreshold) { - this.HumidifierDehumidifier.CurrentHumidifierDehumidifierState = this.hap.Characteristic.CurrentHumidifierDehumidifierState.IDLE; + this.HumidifierDehumidifier.CurrentHumidifierDehumidifierState = this.hap.Characteristic.CurrentHumidifierDehumidifierState.IDLE } else if (this.HumidifierDehumidifier.Active === this.hap.Characteristic.Active.INACTIVE) { - this.HumidifierDehumidifier.CurrentHumidifierDehumidifierState = this.hap.Characteristic.CurrentHumidifierDehumidifierState.INACTIVE; + this.HumidifierDehumidifier.CurrentHumidifierDehumidifierState = this.hap.Characteristic.CurrentHumidifierDehumidifierState.INACTIVE } else { - this.HumidifierDehumidifier.CurrentHumidifierDehumidifierState = this.hap.Characteristic.CurrentHumidifierDehumidifierState.HUMIDIFYING; + this.HumidifierDehumidifier.CurrentHumidifierDehumidifierState = this.hap.Characteristic.CurrentHumidifierDehumidifierState.HUMIDIFYING } } - await this.debugLog(`TargetHumidifierDehumidifierState: ${this.HumidifierDehumidifier.TargetHumidifierDehumidifierState},` - + ` RelativeHumidityHumidifierThreshold: ${this.HumidifierDehumidifier.RelativeHumidityHumidifierThreshold},` - + ` CurrentHumidifierDehumidifierState: ${this.HumidifierDehumidifier.CurrentHumidifierDehumidifierState}`); + await this.debugLog(`TargetHumidifierDehumidifierState: ${this.HumidifierDehumidifier.TargetHumidifierDehumidifierState}, RelativeHumidityHumidifierThreshold: ${this.HumidifierDehumidifier.RelativeHumidityHumidifierThreshold}, CurrentHumidifierDehumidifierState: ${this.HumidifierDehumidifier.CurrentHumidifierDehumidifierState}`) // Water Level if (this.deviceStatus.lackWater) { - this.HumidifierDehumidifier.WaterLevel = 0; + this.HumidifierDehumidifier.WaterLevel = 0 } else { - this.HumidifierDehumidifier.WaterLevel = 100; + this.HumidifierDehumidifier.WaterLevel = 100 } - await this.debugLog(`WaterLevel: ${this.HumidifierDehumidifier.WaterLevel}`); + await this.debugLog(`WaterLevel: ${this.HumidifierDehumidifier.WaterLevel}`) // Firmware Version if (this.deviceStatus.version) { - const version = this.deviceStatus.version.toString(); - await this.debugLog(`Firmware Version: ${version.replace(/^V|-.*$/g, '')}`); - const deviceVersion = version.replace(/^V|-.*$/g, '') ?? '0.0.0'; + const version = this.deviceStatus.version.toString() + await this.debugLog(`Firmware Version: ${version.replace(/^V|-.*$/g, '')}`) + const deviceVersion = version.replace(/^V|-.*$/g, '') ?? '0.0.0' this.accessory .getService(this.hap.Service.AccessoryInformation)! .setCharacteristic(this.hap.Characteristic.HardwareRevision, deviceVersion) .setCharacteristic(this.hap.Characteristic.FirmwareRevision, deviceVersion) .getCharacteristic(this.hap.Characteristic.FirmwareRevision) - .updateValue(deviceVersion); - this.accessory.context.version = deviceVersion; - await this.debugSuccessLog(`version: ${this.accessory.context.version}`); + .updateValue(deviceVersion) + this.accessory.context.version = deviceVersion + await this.debugSuccessLog(`version: ${this.accessory.context.version}`) } } async parseStatusWebhook(): Promise { - await this.debugLog('parseStatusWebhook'); - await this.debugLog(`(temperature, humidity) = Webhook:(${convertUnits(this.webhookContext.temperature, this.webhookContext.scale, - this.device.iosensor?.convertUnitTo)}, ${this.webhookContext.humidity}), current:(${this.TemperatureSensor?.CurrentTemperature},` - + ` ${this.HumidifierDehumidifier.CurrentRelativeHumidity})`); + await this.debugLog('parseStatusWebhook') + await this.debugLog(`(temperature, humidity) = Webhook:(${convertUnits(this.webhookContext.temperature, this.webhookContext.scale, this.device.iosensor?.convertUnitTo)}, ${this.webhookContext.humidity}), current:(${this.TemperatureSensor?.CurrentTemperature}, ${this.HumidifierDehumidifier.CurrentRelativeHumidity})`) // CurrentRelativeHumidity - this.HumidifierDehumidifier.CurrentRelativeHumidity = validHumidity(this.webhookContext.humidity); - await this.debugLog(`CurrentRelativeHumidity: ${this.HumidifierDehumidifier.CurrentRelativeHumidity}`); + this.HumidifierDehumidifier.CurrentRelativeHumidity = validHumidity(this.webhookContext.humidity) + await this.debugLog(`CurrentRelativeHumidity: ${this.HumidifierDehumidifier.CurrentRelativeHumidity}`) // CurrentTemperature if (!this.device.humidifier?.hide_temperature && this.TemperatureSensor?.Service) { - this.TemperatureSensor.CurrentTemperature = convertUnits(this.webhookContext.temperature, this.webhookContext.scale, - this.device.iosensor?.convertUnitTo); - await this.debugLog(`CurrentTemperature: ${this.TemperatureSensor.CurrentTemperature}`); + this.TemperatureSensor.CurrentTemperature = convertUnits(this.webhookContext.temperature, this.webhookContext.scale, this.device.iosensor?.convertUnitTo) + await this.debugLog(`CurrentTemperature: ${this.TemperatureSensor.CurrentTemperature}`) } } @@ -317,91 +307,95 @@ export class Humidifier extends deviceBase { */ async refreshStatus(): Promise { if (!this.device.enableCloudService && this.OpenAPI) { - await this.errorLog(`refreshStatus enableCloudService: ${this.device.enableCloudService}`); - } else if (this.BLE || this.config.options?.BLE) { - await this.BLERefreshStatus(); + await this.errorLog(`refreshStatus enableCloudService: ${this.device.enableCloudService}`) + } else if (this.BLE) { + await this.BLERefreshStatus() } else if (this.OpenAPI && this.platform.config.credentials?.token) { - await this.openAPIRefreshStatus(); + await this.openAPIRefreshStatus() } else { - await this.offlineOff(); - await this.debugWarnLog(`Connection Type: ${this.device.connectionType}, refreshStatus will not happen.`); + await this.offlineOff() + await this.debugWarnLog(`Connection Type: ${this.device.connectionType}, refreshStatus will not happen.`) } } async BLERefreshStatus(): Promise { - await this.debugLog('BLERefreshStatus'); + await this.debugLog('BLERefreshStatus') + const switchbot = await this.switchbotBLE() + if (switchbot === undefined) { + await this.BLERefreshConnection(switchbot) + } else { + // Start to monitor advertisement packets + (async () => { + // Start to monitor advertisement packets + const serviceData = await this.monitorAdvertisementPackets(switchbot) as humidifierServiceData + // Update HomeKit + if (serviceData.model === SwitchBotBLEModel.Humidifier && serviceData.modelName === SwitchBotBLEModelName.Humidifier) { + this.serviceData = serviceData + await this.BLEparseStatus() + await this.updateHomeKitCharacteristics() + } else { + await this.errorLog(`failed to get serviceData, serviceData: ${serviceData}`) + await this.BLERefreshConnection(switchbot) + } + })() + } + } + + async registerPlatformBLE(): Promise { + await this.debugLog('registerPlatformBLE') if (this.config.options?.BLE) { - await this.debugLog('is listening to Platform BLE.'); - this.device.bleMac = this.device.deviceId!.match(/.{1,2}/g)!.join(':').toLowerCase(); - await this.debugLog(`bleMac: ${this.device.bleMac}`); + await this.debugLog('is listening to Platform BLE.') + this.device.bleMac = this.device.deviceId!.match(/.{1,2}/g)!.join(':').toLowerCase() + await this.debugLog(`bleMac: ${this.device.bleMac}`) this.platform.bleEventHandler[this.device.bleMac] = async (context: humidifierServiceData) => { try { - await this.debugLog(`received BLE: ${JSON.stringify(context)}`); - this.serviceData = context; - await this.BLEparseStatus(); - await this.updateHomeKitCharacteristics(); + await this.debugLog(`received BLE: ${JSON.stringify(context)}`) + this.serviceData = context + await this.BLEparseStatus() + await this.updateHomeKitCharacteristics() } catch (e: any) { - await this.errorLog(`failed to handle BLE. Received: ${JSON.stringify(context)} Error: ${e}`); + await this.errorLog(`failed to handle BLE. Received: ${JSON.stringify(context)} Error: ${e}`) } - }; - } else { - await this.debugLog('is using Device BLE Scanning.'); - const switchbot = await this.switchbotBLE(); - if (switchbot === undefined) { - await this.BLERefreshConnection(switchbot); - } else { - // Start to monitor advertisement packets - (async () => { - // Start to monitor advertisement packets - const serviceData = await this.monitorAdvertisementPackets(switchbot) as humidifierServiceData; - // Update HomeKit - if (serviceData.model === SwitchBotBLEModel.Humidifier && serviceData.modelName === SwitchBotBLEModelName.Humidifier) { - this.serviceData = serviceData; - await this.BLEparseStatus(); - await this.updateHomeKitCharacteristics(); - } else { - await this.errorLog(`failed to get serviceData, serviceData: ${serviceData}`); - await this.BLERefreshConnection(switchbot); - } - })(); } + } else { + await this.debugLog('is not listening to Platform BLE') } } async openAPIRefreshStatus(): Promise { - await this.debugLog('openAPIRefreshStatus'); + await this.debugLog('openAPIRefreshStatus') try { - const { body, statusCode } = await this.deviceRefreshStatus(); - const deviceStatus: any = await body.json(); - await this.debugLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`);; + const { body, statusCode } = await this.deviceRefreshStatus() + const deviceStatus: any = await body.json() + await this.debugLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`) if (await this.successfulStatusCodes(statusCode, deviceStatus)) { - await this.debugSuccessLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`); - this.deviceStatus = deviceStatus.body; - await this.openAPIparseStatus(); - await this.updateHomeKitCharacteristics(); + await this.debugSuccessLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`) + this.deviceStatus = deviceStatus.body + await this.openAPIparseStatus() + await this.updateHomeKitCharacteristics() } else { - await this.debugWarnLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`); - await this.debugWarnLog(statusCode, deviceStatus); + await this.debugWarnLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`) + await this.debugWarnLog(statusCode, deviceStatus) } } catch (e: any) { - await this.apiError(e); - await this.errorLog(`failed openAPIRefreshStatus with ${this.device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`); + await this.apiError(e) + await this.errorLog(`failed openAPIRefreshStatus with ${this.device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`) } } async registerWebhook() { if (this.device.webhook) { - await this.debugLog('is listening webhook.'); + await this.debugLog('is listening webhook.') this.platform.webhookEventHandler[this.device.deviceId] = async (context: humidifierWebhookContext) => { try { - await this.debugLog(`received Webhook: ${JSON.stringify(context)}`); - this.webhookContext = context; - await this.parseStatusWebhook(); - await this.updateHomeKitCharacteristics(); + await this.debugLog(`received Webhook: ${JSON.stringify(context)}`) + this.webhookContext = context + await this.parseStatusWebhook() + await this.updateHomeKitCharacteristics() } catch (e: any) { - await this.errorLog(`failed to handle webhook. Received: ${JSON.stringify(context)} Error: ${e}`); + await this.errorLog(`failed to handle webhook. Received: ${JSON.stringify(context)} Error: ${e}`) } - }; + } } } @@ -410,91 +404,86 @@ export class Humidifier extends deviceBase { */ async pushChanges(): Promise { if (!this.device.enableCloudService && this.OpenAPI) { - await this.errorLog(`pushChanges enableCloudService: ${this.device.enableCloudService}`); + await this.errorLog(`pushChanges enableCloudService: ${this.device.enableCloudService}`) } else if (this.BLE) { - await this.BLEpushChanges(); + await this.BLEpushChanges() } else if (this.OpenAPI && this.platform.config.credentials?.token) { - await this.openAPIpushChanges(); + await this.openAPIpushChanges() } else { - await this.offlineOff(); - await this.debugWarnLog(`Connection Type: ${this.device.connectionType}, pushChanges will not happen.`); + await this.offlineOff() + await this.debugWarnLog(`Connection Type: ${this.device.connectionType}, pushChanges will not happen.`) } // Refresh the status from the API interval(15000) .pipe(skipWhile(() => this.humidifierUpdateInProgress)) .pipe(take(1)) .subscribe(async () => { - await this.refreshStatus(); - }); + await this.refreshStatus() + }) } async BLEpushChanges(): Promise { - await this.debugLog('BLEpushChanges'); + await this.debugLog('BLEpushChanges') if ((this.HumidifierDehumidifier.TargetHumidifierDehumidifierState === this.hap.Characteristic.TargetHumidifierDehumidifierState.HUMIDIFIER) && (this.HumidifierDehumidifier.Active === this.hap.Characteristic.Active.ACTIVE) && (this.HumidifierDehumidifier.RelativeHumidityHumidifierThreshold !== this.HumidifierDehumidifier.CurrentRelativeHumidity)) { - const switchbot = await this.platform.connectBLE(this.accessory, this.device); - await this.convertBLEAddress(); + const switchbot = await this.platform.connectBLE(this.accessory, this.device) + await this.convertBLEAddress() if (switchbot !== false) { switchbot .discover({ model: this.device.bleModel, quick: true, id: this.device.bleMac }) .then(async (device_list: any) => { - return await device_list[0].percentage(this.HumidifierDehumidifier.RelativeHumidityHumidifierThreshold); + return await device_list[0].percentage(this.HumidifierDehumidifier.RelativeHumidityHumidifierThreshold) }) .then(async () => { - await this.successLog(`RelativeHumidityHumidifierThreshold: ${this.HumidifierDehumidifier.RelativeHumidityHumidifierThreshold}` - + ' sent over BLE, sent successfully'); - await this.updateHomeKitCharacteristics(); + await this.successLog(`RelativeHumidityHumidifierThreshold: ${this.HumidifierDehumidifier.RelativeHumidityHumidifierThreshold} sent over BLE, sent successfully`) + await this.updateHomeKitCharacteristics() }) .catch(async (e: any) => { - await this.apiError(e); - await this.errorLog(`failed BLEpushChanges with ${this.device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`); - await this.BLEPushConnection(); - }); + await this.apiError(e) + await this.errorLog(`failed BLEpushChanges with ${this.device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`) + await this.BLEPushConnection() + }) } else { - await this.errorLog(`wasn't able to establish BLE Connection, node-switchbot: ${switchbot}`); - await this.BLEPushConnection(); + await this.errorLog(`wasn't able to establish BLE Connection, node-switchbot: ${switchbot}`) + await this.BLEPushConnection() } } else { - await this.debugLog(`No changes (BLEpushChanges), Active: ${this.HumidifierDehumidifier.Active},` - + ` RelativeHumidityHumidifierThreshold: ${this.HumidifierDehumidifier.RelativeHumidityHumidifierThreshold},` - + ` CurrentRelativeHumidity: ${this.HumidifierDehumidifier.CurrentRelativeHumidity}`); + await this.debugLog(`No changes (BLEpushChanges), Active: ${this.HumidifierDehumidifier.Active}, RelativeHumidityHumidifierThreshold: ${this.HumidifierDehumidifier.RelativeHumidityHumidifierThreshold}, CurrentRelativeHumidity: ${this.HumidifierDehumidifier.CurrentRelativeHumidity}`) } } async openAPIpushChanges(): Promise { - await this.debugLog('openAPIpushChanges'); + await this.debugLog('openAPIpushChanges') if ((this.HumidifierDehumidifier.TargetHumidifierDehumidifierState === this.hap.Characteristic.TargetHumidifierDehumidifierState.HUMIDIFIER) && (this.HumidifierDehumidifier.Active === this.hap.Characteristic.Active.ACTIVE) && (this.HumidifierDehumidifier.RelativeHumidityHumidifierThreshold !== this.HumidifierDehumidifier.CurrentRelativeHumidity)) { - await this.debugLog(`Auto Off, RelativeHumidityHumidifierThreshold: ${this.HumidifierDehumidifier.RelativeHumidityHumidifierThreshold}!`); + await this.debugLog(`Auto Off, RelativeHumidityHumidifierThreshold: ${this.HumidifierDehumidifier.RelativeHumidityHumidifierThreshold}!`) const bodyChange = JSON.stringify({ command: 'setMode', parameter: `${this.HumidifierDehumidifier.RelativeHumidityHumidifierThreshold}`, commandType: 'command', - }); - await this.debugLog(`SwitchBot OpenAPI bodyChange: ${JSON.stringify(bodyChange)}`); + }) + await this.debugLog(`SwitchBot OpenAPI bodyChange: ${JSON.stringify(bodyChange)}`) try { - const { body, statusCode } = await this.pushChangeRequest(bodyChange); - const deviceStatus: any = await body.json(); - await this.debugLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`); + const { body, statusCode } = await this.pushChangeRequest(bodyChange) + const deviceStatus: any = await body.json() + await this.debugLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`) if (await this.successfulStatusCodes(statusCode, deviceStatus)) { - await this.debugSuccessLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`); - await this.updateHomeKitCharacteristics(); + await this.debugSuccessLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`) + await this.updateHomeKitCharacteristics() } else { - await this.statusCode(statusCode); - await this.statusCode(deviceStatus.statusCode); + await this.statusCode(statusCode) + await this.statusCode(deviceStatus.statusCode) } } catch (e: any) { - await this.apiError(e); - await this.errorLog(`failed openAPIpushChanges with ${this.device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`); + await this.apiError(e) + await this.errorLog(`failed openAPIpushChanges with ${this.device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`) } - } else if ((this.HumidifierDehumidifier.TargetHumidifierDehumidifierState === - this.hap.Characteristic.TargetHumidifierDehumidifierState.HUMIDIFIER_OR_DEHUMIDIFIER) - && (this.HumidifierDehumidifier.Active === this.hap.Characteristic.Active.ACTIVE)) { - await this.pushAutoChanges(); + } else if ((this.HumidifierDehumidifier.TargetHumidifierDehumidifierState === this.hap.Characteristic.TargetHumidifierDehumidifierState.HUMIDIFIER_OR_DEHUMIDIFIER) && (this.HumidifierDehumidifier.Active === this.hap.Characteristic.Active.ACTIVE)) { + await this.pushAutoChanges() } else { - await this.pushActiveChanges(); + await this.pushActiveChanges() } } @@ -502,35 +491,34 @@ export class Humidifier extends deviceBase { * Pushes the requested changes to the SwitchBot API */ async pushAutoChanges(): Promise { - this.debugLog('pushAutoChanges'); - if ((this.HumidifierDehumidifier.TargetHumidifierDehumidifierState === - this.hap.Characteristic.TargetHumidifierDehumidifierState.HUMIDIFIER_OR_DEHUMIDIFIER) + this.debugLog('pushAutoChanges') + if ((this.HumidifierDehumidifier.TargetHumidifierDehumidifierState + === this.hap.Characteristic.TargetHumidifierDehumidifierState.HUMIDIFIER_OR_DEHUMIDIFIER) && (this.HumidifierDehumidifier.Active === this.hap.Characteristic.Active.ACTIVE)) { - await this.debugLog('Pushing Auto'); + await this.debugLog('Pushing Auto') const bodyChange = JSON.stringify({ command: 'setMode', parameter: 'auto', commandType: 'command', - }); - await this.debugLog(`pushAutoChanges, SwitchBot OpenAPI bodyChange: ${JSON.stringify(bodyChange)}`); + }) + await this.debugLog(`pushAutoChanges, SwitchBot OpenAPI bodyChange: ${JSON.stringify(bodyChange)}`) try { - const { body, statusCode } = await this.pushChangeRequest(bodyChange); - const deviceStatus: any = await body.json(); - await this.debugLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`); + const { body, statusCode } = await this.pushChangeRequest(bodyChange) + const deviceStatus: any = await body.json() + await this.debugLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`) if (await this.successfulStatusCodes(statusCode, deviceStatus)) { - await this.debugSuccessLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`); - await this.updateHomeKitCharacteristics(); + await this.debugSuccessLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`) + await this.updateHomeKitCharacteristics() } else { - await this.statusCode(statusCode); - await this.statusCode(deviceStatus.statusCode); + await this.statusCode(statusCode) + await this.statusCode(deviceStatus.statusCode) } } catch (e: any) { - await this.apiError(e); - await this.errorLog(`failed pushAutoChanges with ${this.device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`); + await this.apiError(e) + await this.errorLog(`failed pushAutoChanges with ${this.device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`) } } else { - await this.debugLog('No changes (pushAutoChanges), TargetHumidifierDehumidifierState:' - + ` ${this.HumidifierDehumidifier.TargetHumidifierDehumidifierState}, Active: ${this.HumidifierDehumidifier.Active}`); + await this.debugLog(`No changes (pushAutoChanges), TargetHumidifierDehumidifierState: ${this.HumidifierDehumidifier.TargetHumidifierDehumidifierState}, Active: ${this.HumidifierDehumidifier.Active}`) } } @@ -538,32 +526,32 @@ export class Humidifier extends deviceBase { * Pushes the requested changes to the SwitchBot API */ async pushActiveChanges(): Promise { - await this.debugLog('pushActiveChanges'); + await this.debugLog('pushActiveChanges') if (this.HumidifierDehumidifier.Active === this.hap.Characteristic.Active.INACTIVE) { - await this.debugLog('Pushing Off'); + await this.debugLog('Pushing Off') const bodyChange = JSON.stringify({ command: 'turnOff', parameter: 'default', commandType: 'command', - }); - await this.debugLog(`pushActiveChanges, SwitchBot OpenAPI bodyChange: ${JSON.stringify(bodyChange)}`); + }) + await this.debugLog(`pushActiveChanges, SwitchBot OpenAPI bodyChange: ${JSON.stringify(bodyChange)}`) try { - const { body, statusCode } = await this.pushChangeRequest(bodyChange); - const deviceStatus: any = await body.json(); - await this.debugLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`); + const { body, statusCode } = await this.pushChangeRequest(bodyChange) + const deviceStatus: any = await body.json() + await this.debugLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`) if (await this.successfulStatusCodes(statusCode, deviceStatus)) { - await this.debugSuccessLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`); - await this.updateHomeKitCharacteristics(); + await this.debugSuccessLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`) + await this.updateHomeKitCharacteristics() } else { - await this.statusCode(statusCode); - await this.statusCode(deviceStatus.statusCode); + await this.statusCode(statusCode) + await this.statusCode(deviceStatus.statusCode) } } catch (e: any) { - await this.apiError(e); - await this.errorLog(`failed pushActiveChanges with ${this.device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`); + await this.apiError(e) + await this.errorLog(`failed pushActiveChanges with ${this.device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`) } } else { - await this.debugLog(`No changes (pushActiveChanges), Active: ${this.HumidifierDehumidifier.Active}`); + await this.debugLog(`No changes (pushActiveChanges), Active: ${this.HumidifierDehumidifier.Active}`) } } @@ -572,13 +560,13 @@ export class Humidifier extends deviceBase { */ async ActiveSet(value: CharacteristicValue): Promise { if (this.HumidifierDehumidifier.Active !== this.accessory.context.Active) { - await this.infoLog(`Set Active: ${value}`); + await this.infoLog(`Set Active: ${value}`) } else { - await this.debugLog(`No Changes, Active: ${value}`); + await this.debugLog(`No Changes, Active: ${value}`) } - this.HumidifierDehumidifier.Active = value; - this.doHumidifierUpdate.next(); + this.HumidifierDehumidifier.Active = value + this.doHumidifierUpdate.next() } /** @@ -586,13 +574,13 @@ export class Humidifier extends deviceBase { */ async TargetHumidifierDehumidifierStateSet(value: CharacteristicValue): Promise { if (this.HumidifierDehumidifier.Active === this.hap.Characteristic.Active.ACTIVE) { - await this.infoLog(`Set TargetHumidifierDehumidifierState: ${value}`); + await this.infoLog(`Set TargetHumidifierDehumidifierState: ${value}`) } else { - await this.debugLog(`No Changes, TargetHumidifierDehumidifierState: ${value}`); + await this.debugLog(`No Changes, TargetHumidifierDehumidifierState: ${value}`) } - this.HumidifierDehumidifier.TargetHumidifierDehumidifierState = value; - this.doHumidifierUpdate.next(); + this.HumidifierDehumidifier.TargetHumidifierDehumidifierState = value + this.doHumidifierUpdate.next() } /** @@ -600,18 +588,18 @@ export class Humidifier extends deviceBase { */ async RelativeHumidityHumidifierThresholdSet(value: CharacteristicValue): Promise { if (this.HumidifierDehumidifier.Active === this.hap.Characteristic.Active.ACTIVE) { - await this.infoLog(`Set RelativeHumidityHumidifierThreshold: ${value}`); + await this.infoLog(`Set RelativeHumidityHumidifierThreshold: ${value}`) } else { - await this.debugLog(`No Changes, RelativeHumidityHumidifierThreshold: ${value}`); + await this.debugLog(`No Changes, RelativeHumidityHumidifierThreshold: ${value}`) } // If the Humidifier is off, turn it on if (this.HumidifierDehumidifier.Active === this.hap.Characteristic.Active.INACTIVE) { - this.HumidifierDehumidifier.Active = this.hap.Characteristic.Active.ACTIVE; - this.HumidifierDehumidifier.CurrentHumidifierDehumidifierState = this.hap.Characteristic.CurrentHumidifierDehumidifierState.IDLE; + this.HumidifierDehumidifier.Active = this.hap.Characteristic.Active.ACTIVE + this.HumidifierDehumidifier.CurrentHumidifierDehumidifierState = this.hap.Characteristic.CurrentHumidifierDehumidifierState.IDLE } - this.HumidifierDehumidifier.RelativeHumidityHumidifierThreshold = value; - this.doHumidifierUpdate.next(); + this.HumidifierDehumidifier.RelativeHumidityHumidifierThreshold = value + this.doHumidifierUpdate.next() } /** @@ -619,64 +607,55 @@ export class Humidifier extends deviceBase { */ async updateHomeKitCharacteristics(): Promise { // CurrentRelativeHumidity - await this.updateCharacteristic(this.HumidifierDehumidifier.Service, this.hap.Characteristic.CurrentRelativeHumidity, - this.HumidifierDehumidifier.CurrentRelativeHumidity, 'CurrentRelativeHumidity'); + await this.updateCharacteristic(this.HumidifierDehumidifier.Service, this.hap.Characteristic.CurrentRelativeHumidity, this.HumidifierDehumidifier.CurrentRelativeHumidity, 'CurrentRelativeHumidity') // WaterLevel - await this.updateCharacteristic(this.HumidifierDehumidifier.Service, this.hap.Characteristic.WaterLevel, - this.HumidifierDehumidifier.WaterLevel, 'WaterLevel'); + await this.updateCharacteristic(this.HumidifierDehumidifier.Service, this.hap.Characteristic.WaterLevel, this.HumidifierDehumidifier.WaterLevel, 'WaterLevel') // CurrentHumidifierDehumidifierState - await this.updateCharacteristic(this.HumidifierDehumidifier.Service, this.hap.Characteristic.CurrentHumidifierDehumidifierState, - this.HumidifierDehumidifier.CurrentHumidifierDehumidifierState, 'CurrentHumidifierDehumidifierState'); + await this.updateCharacteristic(this.HumidifierDehumidifier.Service, this.hap.Characteristic.CurrentHumidifierDehumidifierState, this.HumidifierDehumidifier.CurrentHumidifierDehumidifierState, 'CurrentHumidifierDehumidifierState') // TargetHumidifierDehumidifierState - await this.updateCharacteristic(this.HumidifierDehumidifier.Service, this.hap.Characteristic.TargetHumidifierDehumidifierState, - this.HumidifierDehumidifier.TargetHumidifierDehumidifierState, 'TargetHumidifierDehumidifierState'); + await this.updateCharacteristic(this.HumidifierDehumidifier.Service, this.hap.Characteristic.TargetHumidifierDehumidifierState, this.HumidifierDehumidifier.TargetHumidifierDehumidifierState, 'TargetHumidifierDehumidifierState') // Active - await this.updateCharacteristic(this.HumidifierDehumidifier.Service, this.hap.Characteristic.Active, - this.HumidifierDehumidifier.Active, 'Active'); + await this.updateCharacteristic(this.HumidifierDehumidifier.Service, this.hap.Characteristic.Active, this.HumidifierDehumidifier.Active, 'Active') // RelativeHumidityHumidifierThreshold - await this.updateCharacteristic(this.HumidifierDehumidifier.Service, this.hap.Characteristic.RelativeHumidityHumidifierThreshold, - this.HumidifierDehumidifier.RelativeHumidityHumidifierThreshold, 'RelativeHumidityHumidifierThreshold'); + await this.updateCharacteristic(this.HumidifierDehumidifier.Service, this.hap.Characteristic.RelativeHumidityHumidifierThreshold, this.HumidifierDehumidifier.RelativeHumidityHumidifierThreshold, 'RelativeHumidityHumidifierThreshold') // CurrentTemperature if (!this.device.humidifier?.hide_temperature && this.TemperatureSensor?.Service) { - await this.updateCharacteristic(this.TemperatureSensor.Service, this.hap.Characteristic.CurrentTemperature, - this.TemperatureSensor.CurrentTemperature, 'CurrentTemperature'); + await this.updateCharacteristic(this.TemperatureSensor.Service, this.hap.Characteristic.CurrentTemperature, this.TemperatureSensor.CurrentTemperature, 'CurrentTemperature') } } async BLEPushConnection() { if (this.platform.config.credentials?.token && this.device.connectionType === 'BLE/OpenAPI') { - await this.warnLog('Using OpenAPI Connection to Push Changes'); - await this.openAPIpushChanges(); + await this.warnLog('Using OpenAPI Connection to Push Changes') + await this.openAPIpushChanges() } } async BLERefreshConnection(switchbot: any): Promise { - await this.errorLog(`wasn't able to establish BLE Connection, node-switchbot: ${switchbot}`); + await this.errorLog(`wasn't able to establish BLE Connection, node-switchbot: ${switchbot}`) if (this.platform.config.credentials?.token && this.device.connectionType === 'BLE/OpenAPI') { - await this.warnLog('Using OpenAPI Connection to Refresh Status'); - await this.openAPIRefreshStatus(); + await this.warnLog('Using OpenAPI Connection to Refresh Status') + await this.openAPIRefreshStatus() } } async offlineOff(): Promise { if (this.device.offline) { - this.HumidifierDehumidifier.Service.updateCharacteristic(this.hap.Characteristic.CurrentHumidifierDehumidifierState, - this.hap.Characteristic.CurrentHumidifierDehumidifierState.INACTIVE); - this.HumidifierDehumidifier.Service.updateCharacteristic(this.hap.Characteristic.TargetHumidifierDehumidifierState, - this.hap.Characteristic.TargetHumidifierDehumidifierState.HUMIDIFIER); - this.HumidifierDehumidifier.Service.updateCharacteristic(this.hap.Characteristic.Active, this.hap.Characteristic.Active.INACTIVE); + this.HumidifierDehumidifier.Service.updateCharacteristic(this.hap.Characteristic.CurrentHumidifierDehumidifierState, this.hap.Characteristic.CurrentHumidifierDehumidifierState.INACTIVE) + this.HumidifierDehumidifier.Service.updateCharacteristic(this.hap.Characteristic.TargetHumidifierDehumidifierState, this.hap.Characteristic.TargetHumidifierDehumidifierState.HUMIDIFIER) + this.HumidifierDehumidifier.Service.updateCharacteristic(this.hap.Characteristic.Active, this.hap.Characteristic.Active.INACTIVE) } } async apiError(e: any): Promise { - this.HumidifierDehumidifier.Service.updateCharacteristic(this.hap.Characteristic.CurrentRelativeHumidity, e); - this.HumidifierDehumidifier.Service.updateCharacteristic(this.hap.Characteristic.WaterLevel, e); - this.HumidifierDehumidifier.Service.updateCharacteristic(this.hap.Characteristic.CurrentHumidifierDehumidifierState, e); - this.HumidifierDehumidifier.Service.updateCharacteristic(this.hap.Characteristic.TargetHumidifierDehumidifierState, e); - this.HumidifierDehumidifier.Service.updateCharacteristic(this.hap.Characteristic.Active, e); - this.HumidifierDehumidifier.Service.updateCharacteristic(this.hap.Characteristic.RelativeHumidityHumidifierThreshold, e); + this.HumidifierDehumidifier.Service.updateCharacteristic(this.hap.Characteristic.CurrentRelativeHumidity, e) + this.HumidifierDehumidifier.Service.updateCharacteristic(this.hap.Characteristic.WaterLevel, e) + this.HumidifierDehumidifier.Service.updateCharacteristic(this.hap.Characteristic.CurrentHumidifierDehumidifierState, e) + this.HumidifierDehumidifier.Service.updateCharacteristic(this.hap.Characteristic.TargetHumidifierDehumidifierState, e) + this.HumidifierDehumidifier.Service.updateCharacteristic(this.hap.Characteristic.Active, e) + this.HumidifierDehumidifier.Service.updateCharacteristic(this.hap.Characteristic.RelativeHumidityHumidifierThreshold, e) if (!this.device.humidifier?.hide_temperature && this.TemperatureSensor?.Service) { - this.TemperatureSensor.Service.updateCharacteristic(this.hap.Characteristic.CurrentTemperature, e); + this.TemperatureSensor.Service.updateCharacteristic(this.hap.Characteristic.CurrentTemperature, e) } } } diff --git a/src/device/iosensor.ts b/src/device/iosensor.ts index 4c4ab71c..93089a9a 100644 --- a/src/device/iosensor.ts +++ b/src/device/iosensor.ts @@ -2,19 +2,25 @@ * * iosensor.ts: @switchbot/homebridge-switchbot. */ -import { Units } from 'homebridge'; -import { deviceBase } from './device.js'; -import { Subject, interval, skipWhile } from 'rxjs'; -import { convertUnits, validHumidity } from '../utils.js'; -import { SwitchBotBLEModel, SwitchBotBLEModelName } from 'node-switchbot'; - -import type { devicesConfig } from '../settings.js'; -import type { device } from '../types/devicelist.js'; -import type { SwitchBotPlatform } from '../platform.js'; -import type { outdoorMeterServiceData } from '../types/bledevicestatus.js'; -import type { outdoorMeterStatus } from '../types/devicestatus.js'; -import type { outdoorMeterWebhookContext } from '../types/devicewebhookstatus.js'; -import type { CharacteristicValue, PlatformAccessory, Service } from 'homebridge'; +import type { CharacteristicValue, PlatformAccessory, Service } from 'homebridge' + +import type { SwitchBotPlatform } from '../platform.js' +import type { devicesConfig } from '../settings.js' +import type { outdoorMeterServiceData } from '../types/bledevicestatus.js' +import type { device } from '../types/devicelist.js' +import type { outdoorMeterStatus } from '../types/devicestatus.js' +import type { outdoorMeterWebhookContext } from '../types/devicewebhookstatus.js' + +import { Units } from 'homebridge' +/* +* For Testing Locally: +* import { SwitchBotBLEModel, SwitchBotBLEModelName } from '/Users/Shared/GitHub/OpenWonderLabs/node-switchbot/dist/index.js'; +*/ +import { SwitchBotBLEModel, SwitchBotBLEModelName } from 'node-switchbot' +import { interval, skipWhile, Subject } from 'rxjs' + +import { convertUnits, validHumidity } from '../utils.js' +import { deviceBase } from './device.js' /** * Platform Accessory @@ -24,244 +30,240 @@ import type { CharacteristicValue, PlatformAccessory, Service } from 'homebridge export class IOSensor extends deviceBase { // Services private Battery: { - Name: CharacteristicValue; - Service: Service; - BatteryLevel: CharacteristicValue; - StatusLowBattery: CharacteristicValue; - }; + Name: CharacteristicValue + Service: Service + BatteryLevel: CharacteristicValue + StatusLowBattery: CharacteristicValue + } private HumiditySensor?: { - Name: CharacteristicValue; - Service: Service; - CurrentRelativeHumidity: CharacteristicValue; - }; + Name: CharacteristicValue + Service: Service + CurrentRelativeHumidity: CharacteristicValue + } private TemperatureSensor?: { - Name: CharacteristicValue; - Service: Service; - CurrentTemperature: CharacteristicValue; - }; + Name: CharacteristicValue + Service: Service + CurrentTemperature: CharacteristicValue + } // OpenAPI - deviceStatus!: outdoorMeterStatus; + deviceStatus!: outdoorMeterStatus - //Webhook - webhookContext!: outdoorMeterWebhookContext; + // Webhook + webhookContext!: outdoorMeterWebhookContext // BLE - serviceData!: outdoorMeterServiceData; + serviceData!: outdoorMeterServiceData // Updates - ioSensorUpdateInProgress!: boolean; - doIOSensorUpdate: Subject; + ioSensorUpdateInProgress!: boolean + doIOSensorUpdate: Subject constructor( readonly platform: SwitchBotPlatform, accessory: PlatformAccessory, device: device & devicesConfig, ) { - super(platform, accessory, device); + super(platform, accessory, device) // Set category - accessory.category = this.hap.Categories.SENSOR; + accessory.category = this.hap.Categories.SENSOR // this is subject we use to track when we need to POST changes to the SwitchBot API - this.doIOSensorUpdate = new Subject(); - this.ioSensorUpdateInProgress = false; + this.doIOSensorUpdate = new Subject() + this.ioSensorUpdateInProgress = false // Initialize Battery Service - accessory.context.Battery = accessory.context.Battery ?? {}; + accessory.context.Battery = accessory.context.Battery ?? {} this.Battery = { Name: `${accessory.displayName} Battery`, Service: accessory.getService(this.hap.Service.Battery) ?? accessory.addService(this.hap.Service.Battery) as Service, BatteryLevel: accessory.context.BatteryLevel ?? 100, StatusLowBattery: accessory.context.StatusLowBattery ?? this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL, - }; - accessory.context.Battery = this.Battery as object; + } + accessory.context.Battery = this.Battery as object // Initialize Battery Characteristics - this.Battery.Service - .setCharacteristic(this.hap.Characteristic.Name, this.Battery.Name) - .setCharacteristic(this.hap.Characteristic.ChargingState, this.hap.Characteristic.ChargingState.NOT_CHARGEABLE) - .getCharacteristic(this.hap.Characteristic.BatteryLevel) - .onGet(() => { - return this.Battery.BatteryLevel; - }); - - this.Battery.Service - .getCharacteristic(this.hap.Characteristic.StatusLowBattery) - .onGet(() => { - return this.Battery.StatusLowBattery; - }); - accessory.context.BatteryName = this.Battery.Name; + this.Battery.Service.setCharacteristic(this.hap.Characteristic.Name, this.Battery.Name).setCharacteristic(this.hap.Characteristic.ChargingState, this.hap.Characteristic.ChargingState.NOT_CHARGEABLE).getCharacteristic(this.hap.Characteristic.BatteryLevel).onGet(() => { + return this.Battery.BatteryLevel + }) + + this.Battery.Service.getCharacteristic(this.hap.Characteristic.StatusLowBattery).onGet(() => { + return this.Battery.StatusLowBattery + }) + accessory.context.BatteryName = this.Battery.Name // InitializeTemperature Sensor Service if (device.iosensor?.hide_temperature) { if (this.TemperatureSensor) { - this.debugLog('Removing Temperature Sensor Service'); - this.TemperatureSensor.Service = this.accessory.getService(this.hap.Service.TemperatureSensor) as Service; - accessory.removeService(this.TemperatureSensor.Service); + this.debugLog('Removing Temperature Sensor Service') + this.TemperatureSensor.Service = this.accessory.getService(this.hap.Service.TemperatureSensor) as Service + accessory.removeService(this.TemperatureSensor.Service) } } else { - accessory.context.TemperatureSensor = accessory.context.TemperatureSensor ?? {}; + accessory.context.TemperatureSensor = accessory.context.TemperatureSensor ?? {} this.TemperatureSensor = { Name: `${accessory.displayName} Temperature Sensor`, Service: accessory.getService(this.hap.Service.TemperatureSensor) ?? this.accessory.addService(this.hap.Service.TemperatureSensor) as Service, CurrentTemperature: accessory.context.CurrentTemperature ?? 30, - }; - accessory.context.TemperatureSensor = this.TemperatureSensor as object; + } + accessory.context.TemperatureSensor = this.TemperatureSensor as object // Initialize Temperature Sensor Characteristics - this.TemperatureSensor.Service - .setCharacteristic(this.hap.Characteristic.Name, this.TemperatureSensor.Name) - .getCharacteristic(this.hap.Characteristic.CurrentTemperature) - .setProps({ - unit: Units['CELSIUS'], - validValueRanges: [-273.15, 100], - minValue: -273.15, - maxValue: 100, - minStep: 0.1, - }) - .onGet(() => { - return this.TemperatureSensor!.CurrentTemperature; - }); + this.TemperatureSensor.Service.setCharacteristic(this.hap.Characteristic.Name, this.TemperatureSensor.Name).getCharacteristic(this.hap.Characteristic.CurrentTemperature).setProps({ + unit: Units.CELSIUS, + validValueRanges: [-273.15, 100], + minValue: -273.15, + maxValue: 100, + minStep: 0.1, + }).onGet(() => { + return this.TemperatureSensor!.CurrentTemperature + }) } // Initialize Humidity Sensor Service if (device.iosensor?.hide_humidity) { if (this.HumiditySensor) { - this.debugLog('Removing Humidity Sensor Service'); - this.HumiditySensor.Service = this.accessory.getService(this.hap.Service.HumiditySensor) as Service; - accessory.removeService(this.HumiditySensor.Service); + this.debugLog('Removing Humidity Sensor Service') + this.HumiditySensor.Service = this.accessory.getService(this.hap.Service.HumiditySensor) as Service + accessory.removeService(this.HumiditySensor.Service) } } else { - accessory.context.HumiditySensor = accessory.context.HumiditySensor ?? {}; + accessory.context.HumiditySensor = accessory.context.HumiditySensor ?? {} this.HumiditySensor = { Name: `${accessory.displayName} Humidity Sensor`, Service: accessory.getService(this.hap.Service.HumiditySensor) ?? this.accessory.addService(this.hap.Service.HumiditySensor) as Service, CurrentRelativeHumidity: accessory.context.CurrentRelativeHumidity ?? 50, - }; - accessory.context.HumiditySensor = this.HumiditySensor as object; + } + accessory.context.HumiditySensor = this.HumiditySensor as object // Initialize Humidity Sensor Characteristics - this.HumiditySensor.Service - .setCharacteristic(this.hap.Characteristic.Name, this.HumiditySensor.Name) - .getCharacteristic(this.hap.Characteristic.CurrentRelativeHumidity) - .setProps({ - minStep: 0.1, - }) - .onGet(() => { - return this.HumiditySensor!.CurrentRelativeHumidity; - }); + this.HumiditySensor.Service.setCharacteristic(this.hap.Characteristic.Name, this.HumiditySensor.Name).getCharacteristic(this.hap.Characteristic.CurrentRelativeHumidity).setProps({ + minStep: 0.1, + }).onGet(() => { + return this.HumiditySensor!.CurrentRelativeHumidity + }) } // Retrieve initial values and updateHomekit - this.debugLog('Retrieve initial values and update Homekit'); - this.refreshStatus(); + try { + this.debugLog('Retrieve initial values and update Homekit') + this.refreshStatus() + } catch (e: any) { + this.errorLog(`failed to retrieve initial values and update Homekit, Error: ${e}`) + } + + // regisiter webhook event handler if enabled + try { + this.debugLog('Registering Webhook Event Handler') + this.registerWebhook() + } catch (e: any) { + this.errorLog(`failed to registerWebhook, Error: ${e}`) + } - //regisiter webhook event handler - this.debugLog('Registering Webhook Event Handler'); - this.registerWebhook(); + // regisiter platform BLE event handler if enabled + try { + this.debugLog('Registering Platform BLE Event Handler') + this.registerPlatformBLE() + } catch (e: any) { + this.errorLog(`failed to registerPlatformBLE, Error: ${e}`) + } // Start an update interval interval(this.deviceRefreshRate * 1000) .pipe(skipWhile(() => this.ioSensorUpdateInProgress)) .subscribe(async () => { - await this.refreshStatus(); - }); + await this.refreshStatus() + }) } async BLEparseStatus(): Promise { - await this.debugLog('BLEparseStatus'); - await this.debugLog(`(battery, temperature, humidity) = BLE:(${this.serviceData.battery}, ${this.serviceData.temperature.c},` - + ` ${this.serviceData.humidity}), current:(${this.Battery.BatteryLevel}, ${this.TemperatureSensor?.CurrentTemperature},` - + ` ${this.HumiditySensor?.CurrentRelativeHumidity})`); + await this.debugLog('BLEparseStatus') + await this.debugLog(`(battery, temperature, humidity) = BLE:(${this.serviceData.battery}, ${this.serviceData.temperature.c}, ${this.serviceData.humidity}), current:(${this.Battery.BatteryLevel}, ${this.TemperatureSensor?.CurrentTemperature}, ${this.HumiditySensor?.CurrentRelativeHumidity})`) // BatteryLevel - this.Battery.BatteryLevel = this.serviceData.battery; - await this.debugLog(`BatteryLevel: ${this.Battery.BatteryLevel}`); + this.Battery.BatteryLevel = this.serviceData.battery + await this.debugLog(`BatteryLevel: ${this.Battery.BatteryLevel}`) // StatusLowBattery this.Battery.StatusLowBattery = this.Battery.BatteryLevel < 10 - ? this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW : this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL; - await this.debugLog(`StatusLowBattery: ${this.Battery.StatusLowBattery}`); + ? this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW + : this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL + await this.debugLog(`StatusLowBattery: ${this.Battery.StatusLowBattery}`) // CurrentRelativeHumidity if (!this.device.iosensor?.hide_humidity && this.HumiditySensor?.Service) { - this.HumiditySensor.CurrentRelativeHumidity = validHumidity(this.serviceData.humidity, 0, 100); - await this.debugLog(`CurrentRelativeHumidity: ${this.HumiditySensor.CurrentRelativeHumidity}%`); + this.HumiditySensor.CurrentRelativeHumidity = validHumidity(this.serviceData.humidity, 0, 100) + await this.debugLog(`CurrentRelativeHumidity: ${this.HumiditySensor.CurrentRelativeHumidity}%`) } // Current Temperature if (!this.device.meter?.hide_temperature && this.TemperatureSensor?.Service) { - const CELSIUS = this.serviceData.temperature.c < 0 ? 0 : this.serviceData.temperature.c > 100 ? 100 : this.serviceData.temperature.c; - this.TemperatureSensor.CurrentTemperature = CELSIUS; - await this.debugLog(`Temperature: ${this.TemperatureSensor.CurrentTemperature}°c`); + const CELSIUS = this.serviceData.temperature.c < 0 ? 0 : this.serviceData.temperature.c > 100 ? 100 : this.serviceData.temperature.c + this.TemperatureSensor.CurrentTemperature = CELSIUS + await this.debugLog(`Temperature: ${this.TemperatureSensor.CurrentTemperature}°c`) } } async openAPIparseStatus(): Promise { - await this.debugLog('openAPIparseStatus'); - await this.debugLog(`(battery, temperature, humidity) = OpenAPI:(${this.deviceStatus.battery}, ${this.deviceStatus.temperature},` - + ` ${this.deviceStatus.humidity}), current:(${this.Battery.BatteryLevel}, ${this.TemperatureSensor?.CurrentTemperature},` - + ` ${this.HumiditySensor?.CurrentRelativeHumidity})`); + await this.debugLog('openAPIparseStatus') + await this.debugLog(`(battery, temperature, humidity) = OpenAPI:(${this.deviceStatus.battery}, ${this.deviceStatus.temperature}, ${this.deviceStatus.humidity}), current:(${this.Battery.BatteryLevel}, ${this.TemperatureSensor?.CurrentTemperature}, ${this.HumiditySensor?.CurrentRelativeHumidity})`) // BatteryLevel - this.Battery.BatteryLevel = this.deviceStatus.battery; - await this.debugLog(`BatteryLevel: ${this.Battery.BatteryLevel}`); + this.Battery.BatteryLevel = this.deviceStatus.battery + await this.debugLog(`BatteryLevel: ${this.Battery.BatteryLevel}`) // StatusLowBattery this.Battery.StatusLowBattery = this.Battery.BatteryLevel < 10 - ? this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW : this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL; - await this.debugLog(`StatusLowBattery: ${this.Battery.StatusLowBattery}`); + ? this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW + : this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL + await this.debugLog(`StatusLowBattery: ${this.Battery.StatusLowBattery}`) // CurrentRelativeHumidity if (!this.device.iosensor?.hide_humidity && this.HumiditySensor?.Service) { - this.HumiditySensor.CurrentRelativeHumidity = this.deviceStatus.humidity; - await this.debugLog(`CurrentRelativeHumidity: ${this.HumiditySensor.CurrentRelativeHumidity}%`); + this.HumiditySensor.CurrentRelativeHumidity = this.deviceStatus.humidity + await this.debugLog(`CurrentRelativeHumidity: ${this.HumiditySensor.CurrentRelativeHumidity}%`) } // Current Temperature if (!this.device.meter?.hide_temperature && this.TemperatureSensor?.Service) { - this.TemperatureSensor.CurrentTemperature = this.deviceStatus.temperature; - await this.debugLog(`CurrentTemperature: ${this.TemperatureSensor.CurrentTemperature}°c`); + this.TemperatureSensor.CurrentTemperature = this.deviceStatus.temperature + await this.debugLog(`CurrentTemperature: ${this.TemperatureSensor.CurrentTemperature}°c`) } // Firmware Version if (this.deviceStatus.version) { - const version = this.deviceStatus.version.toString(); - await this.debugLog(`Firmware Version: ${version.replace(/^V|-.*$/g, '')}`); - const deviceVersion = version.replace(/^V|-.*$/g, '') ?? '0.0.0'; + const version = this.deviceStatus.version.toString() + await this.debugLog(`Firmware Version: ${version.replace(/^V|-.*$/g, '')}`) + const deviceVersion = version.replace(/^V|-.*$/g, '') ?? '0.0.0' this.accessory .getService(this.hap.Service.AccessoryInformation)! .setCharacteristic(this.hap.Characteristic.HardwareRevision, deviceVersion) .setCharacteristic(this.hap.Characteristic.FirmwareRevision, deviceVersion) .getCharacteristic(this.hap.Characteristic.FirmwareRevision) - .updateValue(deviceVersion); - this.accessory.context.version = deviceVersion; - await this.debugSuccessLog(`version: ${this.accessory.context.version}`); + .updateValue(deviceVersion) + this.accessory.context.version = deviceVersion + await this.debugSuccessLog(`version: ${this.accessory.context.version}`) } } async parseStatusWebhook(): Promise { - await this.debugLog('parseStatusWebhook'); - await this.debugLog(`(scale, temperature, humidity) = Webhook:(${this.webhookContext.scale}, ${convertUnits(this.webhookContext.temperature, - this.webhookContext.scale, this.device.iosensor?.convertUnitTo)}, ${this.webhookContext.humidity}),` - + ` current:(${this.TemperatureSensor?.CurrentTemperature}, ${this.HumiditySensor?.CurrentRelativeHumidity})`); + await this.debugLog('parseStatusWebhook') + await this.debugLog(`(scale, temperature, humidity) = Webhook:(${this.webhookContext.scale}, ${convertUnits(this.webhookContext.temperature, this.webhookContext.scale, this.device.iosensor?.convertUnitTo)}, ${this.webhookContext.humidity}), current:(${this.TemperatureSensor?.CurrentTemperature}, ${this.HumiditySensor?.CurrentRelativeHumidity})`) if (this.webhookContext.scale !== 'CELSIUS' && this.device.iosensor?.convertUnitTo === undefined) { - await this.warnLog(`received a non-CELSIUS Webhook scale: ${this.webhookContext.scale}, Use the *convertUnitsTo* config under Hub settings,` - + ' if displaying incorrectly in HomeKit.'); + await this.warnLog(`received a non-CELSIUS Webhook scale: ${this.webhookContext.scale}, Use the *convertUnitsTo* config under Hub settings, if displaying incorrectly in HomeKit.`) } // CurrentRelativeHumidity if (this.device.iosensor?.hide_humidity && this.HumiditySensor?.Service) { - this.HumiditySensor.CurrentRelativeHumidity = this.webhookContext.humidity; - await this.debugLog(`CurrentRelativeHumidity: ${this.HumiditySensor.CurrentRelativeHumidity}%`); + this.HumiditySensor.CurrentRelativeHumidity = this.webhookContext.humidity + await this.debugLog(`CurrentRelativeHumidity: ${this.HumiditySensor.CurrentRelativeHumidity}%`) } // CurrentTemperature if (this.device.iosensor?.hide_temperature && this.TemperatureSensor?.Service) { - this.TemperatureSensor.CurrentTemperature = convertUnits(this.webhookContext.temperature, this.webhookContext.scale, - this.device.iosensor?.convertUnitTo); - await this.debugLog(`CurrentTemperature: ${this.TemperatureSensor.CurrentTemperature}°c`); + this.TemperatureSensor.CurrentTemperature = convertUnits(this.webhookContext.temperature, this.webhookContext.scale, this.device.iosensor?.convertUnitTo) + await this.debugLog(`CurrentTemperature: ${this.TemperatureSensor.CurrentTemperature}°c`) } } @@ -270,93 +272,97 @@ export class IOSensor extends deviceBase { */ async refreshStatus(): Promise { if (!this.device.enableCloudService && this.OpenAPI) { - await this.errorLog(`refreshStatus enableCloudService: ${this.device.enableCloudService}`); - } else if (this.BLE || this.config.options?.BLE) { - await this.BLERefreshStatus(); + await this.errorLog(`refreshStatus enableCloudService: ${this.device.enableCloudService}`) + } else if (this.BLE) { + await this.BLERefreshStatus() } else if (this.OpenAPI && this.platform.config.credentials?.token) { - await this.openAPIRefreshStatus(); + await this.openAPIRefreshStatus() } else { - await this.offlineOff(); - await this.debugWarnLog(`Connection Type: ${this.device.connectionType}, refreshStatus will not happen.`); + await this.offlineOff() + await this.debugWarnLog(`Connection Type: ${this.device.connectionType}, refreshStatus will not happen.`) } } async BLERefreshStatus(): Promise { - await this.debugLog('BLERefreshStatus'); + await this.debugLog('BLERefreshStatus') + const switchbot = await this.switchbotBLE() + if (switchbot === undefined) { + await this.BLERefreshConnection(switchbot) + } else { + // Start to monitor advertisement packets + (async () => { + // Start to monitor advertisement packets + const serviceData = await this.monitorAdvertisementPackets(switchbot) as outdoorMeterServiceData + // Update HomeKit + if (serviceData.model === SwitchBotBLEModel.OutdoorMeter && serviceData.modelName === SwitchBotBLEModelName.OutdoorMeter) { + this.serviceData = serviceData + await this.BLEparseStatus() + await this.updateHomeKitCharacteristics() + } else { + await this.errorLog(`failed to get serviceData, serviceData: ${serviceData}`) + await this.BLERefreshConnection(switchbot) + } + })() + } + } + + async registerPlatformBLE(): Promise { + await this.debugLog('registerPlatformBLE') if (this.config.options?.BLE) { - await this.debugLog('is listening to Platform BLE.'); - this.device.bleMac = this.device.deviceId!.match(/.{1,2}/g)!.join(':').toLowerCase(); - await this.debugLog(`bleMac: ${this.device.bleMac}`); + await this.debugLog('is listening to Platform BLE.') + this.device.bleMac = this.device.deviceId!.match(/.{1,2}/g)!.join(':').toLowerCase() + await this.debugLog(`bleMac: ${this.device.bleMac}`) this.platform.bleEventHandler[this.device.bleMac] = async (context: outdoorMeterServiceData) => { try { - await this.debugLog(`received BLE: ${JSON.stringify(context)}`); - this.serviceData = context; - await this.BLEparseStatus(); - await this.updateHomeKitCharacteristics(); + await this.debugLog(`received BLE: ${JSON.stringify(context)}`) + this.serviceData = context + await this.BLEparseStatus() + await this.updateHomeKitCharacteristics() } catch (e: any) { - await this.errorLog(`failed to handle BLE. Received: ${JSON.stringify(context)} Error: ${e}`); + await this.errorLog(`failed to handle BLE. Received: ${JSON.stringify(context)} Error: ${e}`) } - }; - } else { - await this.debugLog('is using Device BLE Scanning.'); - const switchbot = await this.switchbotBLE(); - if (switchbot === undefined) { - await this.BLERefreshConnection(switchbot); - } else { - // Start to monitor advertisement packets - (async () => { - // Start to monitor advertisement packets - const serviceData = await this.monitorAdvertisementPackets(switchbot) as outdoorMeterServiceData; - // Update HomeKit - if (serviceData.model === SwitchBotBLEModel.OutdoorMeter && serviceData.modelName === SwitchBotBLEModelName.OutdoorMeter) { - this.serviceData = serviceData; - await this.BLEparseStatus(); - await this.updateHomeKitCharacteristics(); - } else { - await this.errorLog(`failed to get serviceData, serviceData: ${serviceData}`); - await this.BLERefreshConnection(switchbot); - } - })(); } + } else { + await this.debugLog('is not listening to Platform BLE') } } async openAPIRefreshStatus(): Promise { - await this.debugLog('openAPIRefreshStatus'); + await this.debugLog('openAPIRefreshStatus') try { - const { body, statusCode } = await this.deviceRefreshStatus(); - const deviceStatus: any = await body.json(); - await this.debugLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`);; + const { body, statusCode } = await this.deviceRefreshStatus() + const deviceStatus: any = await body.json() + await this.debugLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`) if (await this.successfulStatusCodes(statusCode, deviceStatus)) { - await this.debugSuccessLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`); - this.deviceStatus = deviceStatus.body; - await this.openAPIparseStatus(); - await this.updateHomeKitCharacteristics(); + await this.debugSuccessLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`) + this.deviceStatus = deviceStatus.body + await this.openAPIparseStatus() + await this.updateHomeKitCharacteristics() } else { - await this.debugWarnLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`); - await this.debugWarnLog(statusCode, deviceStatus); + await this.debugWarnLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`) + await this.debugWarnLog(statusCode, deviceStatus) } } catch (e: any) { - await this.apiError(e); - await this.errorLog(`failed openAPIRefreshStatus with ${this.device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`); + await this.apiError(e) + await this.errorLog(`failed openAPIRefreshStatus with ${this.device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`) } } async registerWebhook() { if (this.device.webhook) { - await this.debugLog('is listening webhook.'); + await this.debugLog('is listening webhook.') this.platform.webhookEventHandler[this.device.deviceId] = async (context: outdoorMeterWebhookContext) => { try { - await this.debugLog(`received Webhook: ${JSON.stringify(context)}`); - this.webhookContext = context; - await this.parseStatusWebhook(); - await this.updateHomeKitCharacteristics(); + await this.debugLog(`received Webhook: ${JSON.stringify(context)}`) + this.webhookContext = context + await this.parseStatusWebhook() + await this.updateHomeKitCharacteristics() } catch (e: any) { - await this.errorLog(`failed to handle webhook. Received: ${JSON.stringify(context)} Error: ${e}`); + await this.errorLog(`failed to handle webhook. Received: ${JSON.stringify(context)} Error: ${e}`) } - }; + } } else { - await this.debugLog('is not listening webhook.'); + await this.debugLog('is not listening webhook.') } } @@ -366,49 +372,45 @@ export class IOSensor extends deviceBase { async updateHomeKitCharacteristics(): Promise { // CurrentRelativeHumidity if (!this.device.iosensor?.hide_humidity && this.HumiditySensor?.Service) { - await this.updateCharacteristic(this.HumiditySensor.Service, this.hap.Characteristic.CurrentRelativeHumidity, - this.HumiditySensor.CurrentRelativeHumidity, 'CurrentRelativeHumidity'); + await this.updateCharacteristic(this.HumiditySensor.Service, this.hap.Characteristic.CurrentRelativeHumidity, this.HumiditySensor.CurrentRelativeHumidity, 'CurrentRelativeHumidity') } // CurrentTemperature if (!this.device.iosensor?.hide_temperature && this.TemperatureSensor?.Service) { - await this.updateCharacteristic(this.TemperatureSensor.Service, this.hap.Characteristic.CurrentTemperature, - this.TemperatureSensor.CurrentTemperature, 'CurrentTemperature'); + await this.updateCharacteristic(this.TemperatureSensor.Service, this.hap.Characteristic.CurrentTemperature, this.TemperatureSensor.CurrentTemperature, 'CurrentTemperature') } // BatteryLevel - await this.updateCharacteristic(this.Battery.Service, this.hap.Characteristic.BatteryLevel, - this.Battery.BatteryLevel, 'BatteryLevel'); + await this.updateCharacteristic(this.Battery.Service, this.hap.Characteristic.BatteryLevel, this.Battery.BatteryLevel, 'BatteryLevel') // StatusLowBattery - await this.updateCharacteristic(this.Battery.Service, this.hap.Characteristic.StatusLowBattery, - this.Battery.StatusLowBattery, 'StatusLowBattery'); + await this.updateCharacteristic(this.Battery.Service, this.hap.Characteristic.StatusLowBattery, this.Battery.StatusLowBattery, 'StatusLowBattery') } async BLERefreshConnection(switchbot: any): Promise { - await this.errorLog(`wasn't able to establish BLE Connection, node-switchbot: ${switchbot}`); + await this.errorLog(`wasn't able to establish BLE Connection, node-switchbot: ${switchbot}`) if (this.platform.config.credentials?.token && this.device.connectionType === 'BLE/OpenAPI') { - await this.warnLog('Using OpenAPI Connection to Refresh Status'); - await this.openAPIRefreshStatus(); + await this.warnLog('Using OpenAPI Connection to Refresh Status') + await this.openAPIRefreshStatus() } } async offlineOff(): Promise { if (this.device.offline) { if (!this.device.iosensor?.hide_humidity && this.HumiditySensor?.Service) { - this.HumiditySensor.Service.updateCharacteristic(this.hap.Characteristic.CurrentRelativeHumidity, 50); + this.HumiditySensor.Service.updateCharacteristic(this.hap.Characteristic.CurrentRelativeHumidity, 50) } if (!this.device.iosensor?.hide_temperature && this.TemperatureSensor?.Service) { - this.TemperatureSensor.Service.updateCharacteristic(this.hap.Characteristic.CurrentTemperature, 30); + this.TemperatureSensor.Service.updateCharacteristic(this.hap.Characteristic.CurrentTemperature, 30) } } } async apiError(e: any): Promise { if (!this.device.iosensor?.hide_humidity && this.HumiditySensor?.Service) { - this.HumiditySensor.Service.updateCharacteristic(this.hap.Characteristic.CurrentRelativeHumidity, e); + this.HumiditySensor.Service.updateCharacteristic(this.hap.Characteristic.CurrentRelativeHumidity, e) } if (!this.device.iosensor?.hide_temperature && this.TemperatureSensor?.Service) { - this.TemperatureSensor.Service.updateCharacteristic(this.hap.Characteristic.CurrentTemperature, e); + this.TemperatureSensor.Service.updateCharacteristic(this.hap.Characteristic.CurrentTemperature, e) } - this.Battery.Service.updateCharacteristic(this.hap.Characteristic.BatteryLevel, e); - this.Battery.Service.updateCharacteristic(this.hap.Characteristic.StatusLowBattery, e); + this.Battery.Service.updateCharacteristic(this.hap.Characteristic.BatteryLevel, e) + this.Battery.Service.updateCharacteristic(this.hap.Characteristic.StatusLowBattery, e) } } diff --git a/src/device/lightstrip.ts b/src/device/lightstrip.ts index f21ac843..72bf4ecd 100644 --- a/src/device/lightstrip.ts +++ b/src/device/lightstrip.ts @@ -2,18 +2,24 @@ * * lightstrip.ts: @switchbot/homebridge-switchbot. */ -import { deviceBase } from './device.js'; -import { hs2rgb, rgb2hs, m2hs } from '../utils.js'; -import { SwitchBotBLEModel, SwitchBotBLEModelName } from 'node-switchbot'; -import { Subject, debounceTime, interval, skipWhile, take, tap } from 'rxjs'; - -import type { devicesConfig } from '../settings.js'; -import type { device } from '../types/devicelist.js'; -import type { SwitchBotPlatform } from '../platform.js'; -import type { stripLightStatus } from '../types/devicestatus.js'; -import type { stripLightServiceData } from '../types/bledevicestatus.js'; -import type { stripLightWebhookContext } from '../types/devicewebhookstatus.js'; -import type { Service, PlatformAccessory, CharacteristicValue, ControllerConstructor, Controller, ControllerServiceMap } from 'homebridge'; +import type { CharacteristicValue, Controller, ControllerConstructor, ControllerServiceMap, PlatformAccessory, Service } from 'homebridge' + +import type { SwitchBotPlatform } from '../platform.js' +import type { devicesConfig } from '../settings.js' +import type { stripLightServiceData } from '../types/bledevicestatus.js' +import type { device } from '../types/devicelist.js' +import type { stripLightStatus } from '../types/devicestatus.js' +import type { stripLightWebhookContext } from '../types/devicewebhookstatus.js' + +/* +* For Testing Locally: +* import { SwitchBotBLEModel, SwitchBotBLEModelName } from '/Users/Shared/GitHub/OpenWonderLabs/node-switchbot/dist/index.js'; +*/ +import { SwitchBotBLEModel, SwitchBotBLEModelName } from 'node-switchbot' +import { debounceTime, interval, skipWhile, Subject, take, tap } from 'rxjs' + +import { hs2rgb, m2hs, rgb2hs } from '../utils.js' +import { deviceBase } from './device.js' /** * Platform Accessory @@ -23,51 +29,51 @@ import type { Service, PlatformAccessory, CharacteristicValue, ControllerConstru export class StripLight extends deviceBase { // Services private LightBulb: { - Name: CharacteristicValue; - Service: Service; - On: CharacteristicValue; - Hue: CharacteristicValue; - Saturation: CharacteristicValue; - Brightness: CharacteristicValue; - ColorTemperature?: CharacteristicValue; - }; + Name: CharacteristicValue + Service: Service + On: CharacteristicValue + Hue: CharacteristicValue + Saturation: CharacteristicValue + Brightness: CharacteristicValue + ColorTemperature?: CharacteristicValue + } // OpenAPI - deviceStatus!: stripLightStatus; + deviceStatus!: stripLightStatus - //Webhook - webhookContext!: stripLightWebhookContext; + // Webhook + webhookContext!: stripLightWebhookContext // BLE - serviceData!: stripLightServiceData; + serviceData!: stripLightServiceData // Adaptive Lighting - adaptiveLighting!: boolean; - adaptiveLightingShift!: number; - AdaptiveLightingController?: ControllerConstructor | Controller; + adaptiveLighting!: boolean + adaptiveLightingShift!: number + AdaptiveLightingController?: ControllerConstructor | Controller // Updates - stripLightUpdateInProgress!: boolean; - doStripLightUpdate!: Subject; + stripLightUpdateInProgress!: boolean + doStripLightUpdate!: Subject constructor( readonly platform: SwitchBotPlatform, accessory: PlatformAccessory, device: device & devicesConfig, ) { - super(platform, accessory, device); + super(platform, accessory, device) // Set category - accessory.category = this.hap.Categories.LIGHTBULB; + accessory.category = this.hap.Categories.LIGHTBULB // Adaptive Lighting - this.getAdaptiveLightingSettings(accessory, device); + this.getAdaptiveLightingSettings(accessory, device) // this is subject we use to track when we need to POST changes to the SwitchBot API - this.doStripLightUpdate = new Subject(); - this.stripLightUpdateInProgress = false; + this.doStripLightUpdate = new Subject() + this.stripLightUpdateInProgress = false // Initialize the LightBulb Service - accessory.context.LightBulb = accessory.context.LightBulb ?? {}; + accessory.context.LightBulb = accessory.context.LightBulb ?? {} this.LightBulb = { Name: accessory.displayName, Service: accessory.getService(this.hap.Service.Lightbulb) ?? accessory.addService(this.hap.Service.Lightbulb) as Service, @@ -76,226 +82,216 @@ export class StripLight extends deviceBase { Saturation: accessory.context.Saturation ?? 0, Brightness: accessory.context.Brightness ?? 0, ColorTemperature: accessory.context.ColorTemperature ?? 140, - }; - accessory.context.LightBulb = this.LightBulb as object; + } + accessory.context.LightBulb = this.LightBulb as object if (this.adaptiveLighting && this.adaptiveLightingShift === -1 && this.LightBulb) { - accessory.removeService(this.LightBulb.Service); - this.LightBulb.Service = accessory.addService(this.hap.Service.Lightbulb); - accessory.context.adaptiveLighting = false; - this.debugLog(`adaptiveLighting: ${this.adaptiveLighting}`); + accessory.removeService(this.LightBulb.Service) + this.LightBulb.Service = accessory.addService(this.hap.Service.Lightbulb) + accessory.context.adaptiveLighting = false + this.debugLog(`adaptiveLighting: ${this.adaptiveLighting}`) } else if (this.adaptiveLighting && this.adaptiveLightingShift >= 0 && this.LightBulb) { this.AdaptiveLightingController = new platform.api.hap.AdaptiveLightingController(this.LightBulb.Service, { controllerMode: this.hap.AdaptiveLightingControllerMode.AUTOMATIC, customTemperatureAdjustment: this.adaptiveLightingShift, - }); - accessory.configureController(this.AdaptiveLightingController); - accessory.context.adaptiveLighting = true; + }) + accessory.configureController(this.AdaptiveLightingController) + accessory.context.adaptiveLighting = true this.debugLog(`adaptiveLighting: ${this.adaptiveLighting}, adaptiveLightingShift: ${this.adaptiveLightingShift}`, - ); + ) } else { - accessory.context.adaptiveLighting = false; - this.debugLog(`adaptiveLighting: ${accessory.context.adaptiveLighting}`); + accessory.context.adaptiveLighting = false + this.debugLog(`adaptiveLighting: ${accessory.context.adaptiveLighting}`) } // Initialize LightBulb Characteristics - this.LightBulb.Service - .setCharacteristic(this.hap.Characteristic.Name, this.LightBulb.Name) - .getCharacteristic(this.hap.Characteristic.On) - .onGet(() => { - return this.LightBulb.On; - }) - .onSet(this.OnSet.bind(this)); + this.LightBulb.Service.setCharacteristic(this.hap.Characteristic.Name, this.LightBulb.Name).getCharacteristic(this.hap.Characteristic.On).onGet(() => { + return this.LightBulb.On + }).onSet(this.OnSet.bind(this)) // Initialize LightBulb Brightness Characteristic - this.LightBulb.Service - .getCharacteristic(this.hap.Characteristic.Brightness) - .setProps({ - minStep: device.striplight?.set_minStep ?? 1, - minValue: 0, - maxValue: 100, - validValueRanges: [0, 100], - }) - .onGet(() => { - return this.LightBulb.Brightness; - }) - .onSet(this.BrightnessSet.bind(this)); + this.LightBulb.Service.getCharacteristic(this.hap.Characteristic.Brightness).setProps({ + minStep: device.striplight?.set_minStep ?? 1, + minValue: 0, + maxValue: 100, + validValueRanges: [0, 100], + }).onGet(() => { + return this.LightBulb.Brightness + }).onSet(this.BrightnessSet.bind(this)) // Initialize LightBulb ColorTemperature Characteristic - this.LightBulb.Service - .getCharacteristic(this.hap.Characteristic.ColorTemperature) - .setProps({ - minValue: 140, - maxValue: 500, - validValueRanges: [140, 500], - }) - .onGet(() => { - return this.LightBulb.ColorTemperature!; - }) - .onSet(this.ColorTemperatureSet.bind(this)); + this.LightBulb.Service.getCharacteristic(this.hap.Characteristic.ColorTemperature).setProps({ + minValue: 140, + maxValue: 500, + validValueRanges: [140, 500], + }).onGet(() => { + return this.LightBulb.ColorTemperature! + }).onSet(this.ColorTemperatureSet.bind(this)) // Initialize LightBulb Hue Characteristic - this.LightBulb.Service - .getCharacteristic(this.hap.Characteristic.Hue) - .setProps({ - minValue: 0, - maxValue: 360, - validValueRanges: [0, 360], - }) - .onGet(() => { - return this.LightBulb.Hue; - }) - .onSet(this.HueSet.bind(this)); + this.LightBulb.Service.getCharacteristic(this.hap.Characteristic.Hue).setProps({ + minValue: 0, + maxValue: 360, + validValueRanges: [0, 360], + }).onGet(() => { + return this.LightBulb.Hue + }).onSet(this.HueSet.bind(this)) // Initialize LightBulb Saturation Characteristic - this.LightBulb.Service - .getCharacteristic(this.hap.Characteristic.Saturation) - .setProps({ - minValue: 0, - maxValue: 100, - validValueRanges: [0, 100], - }) - .onGet(() => { - return this.LightBulb.Saturation; - }) - .onSet(this.SaturationSet.bind(this)); + this.LightBulb.Service.getCharacteristic(this.hap.Characteristic.Saturation).setProps({ + minValue: 0, + maxValue: 100, + validValueRanges: [0, 100], + }).onGet(() => { + return this.LightBulb.Saturation + }).onSet(this.SaturationSet.bind(this)) // Retrieve initial values and updateHomekit - this.debugLog('Retrieve initial values and update Homekit'); - this.refreshStatus(); + try { + this.debugLog('Retrieve initial values and update Homekit') + this.refreshStatus() + } catch (e: any) { + this.errorLog(`failed to retrieve initial values and update Homekit, Error: ${e}`) + } + + // regisiter webhook event handler if enabled + try { + this.debugLog('Registering Webhook Event Handler') + this.registerWebhook() + } catch (e: any) { + this.errorLog(`failed to registerWebhook, Error: ${e}`) + } - //regisiter webhook event handler - this.debugLog('Registering Webhook Event Handler'); - this.registerWebhook(); + // regisiter platform BLE event handler if enabled + try { + this.debugLog('Registering Platform BLE Event Handler') + this.registerPlatformBLE() + } catch (e: any) { + this.errorLog(`failed to registerPlatformBLE, Error: ${e}`) + } // Start an update interval interval(this.deviceRefreshRate * 1000) .pipe(skipWhile(() => this.stripLightUpdateInProgress)) .subscribe(async () => { - await this.refreshStatus(); - }); + await this.refreshStatus() + }) // Watch for Bulb change events // We put in a debounce of 1000ms so we don't make duplicate calls this.doStripLightUpdate .pipe( tap(() => { - this.stripLightUpdateInProgress = true; + this.stripLightUpdateInProgress = true }), debounceTime(this.devicePushRate * 1000), ) .subscribe(async () => { try { - await this.pushChanges(); + await this.pushChanges() } catch (e: any) { - await this.apiError(e); - await this.errorLog(`failed pushChanges with ${device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`); + await this.apiError(e) + await this.errorLog(`failed pushChanges with ${device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`) } - this.stripLightUpdateInProgress = false; - }); + this.stripLightUpdateInProgress = false + }) } async BLEparseStatus(): Promise { - await this.debugLog('BLEparseStatus'); - await this.debugLog(`(power, brightness, color) = BLE:(${this.serviceData.power}, ${this.serviceData.brightness},` - + ` ${this.serviceData.red}:${this.serviceData.green}:${this.serviceData.blue}), current:(${this.LightBulb.On},` - + ` ${this.LightBulb.Brightness}, ${this.LightBulb.Hue}, ${this.LightBulb.Saturation})`); + await this.debugLog('BLEparseStatus') + await this.debugLog(`(power, brightness, color) = BLE:(${this.serviceData.power}, ${this.serviceData.brightness}, ${this.serviceData.red}:${this.serviceData.green}:${this.serviceData.blue}), current:(${this.LightBulb.On}, ${this.LightBulb.Brightness}, ${this.LightBulb.Hue}, ${this.LightBulb.Saturation})`) // On - this.LightBulb.On = this.serviceData.power; - await this.debugLog(`On: ${this.LightBulb.On}`); + this.LightBulb.On = this.serviceData.power + await this.debugLog(`On: ${this.LightBulb.On}`) // Brightness - this.LightBulb.Brightness = this.serviceData.brightness; - await this.debugLog(`Brightness: ${this.LightBulb.Brightness}`); + this.LightBulb.Brightness = this.serviceData.brightness + await this.debugLog(`Brightness: ${this.LightBulb.Brightness}`) // Color, Hue & Brightness - await this.debugLog(`red: ${this.serviceData.red}, green: ${this.serviceData.green}, blue: ${this.serviceData.blue}`); - const [hue, saturation] = rgb2hs(this.serviceData.red, this.serviceData.green, this.serviceData.blue); - await this.debugLog(`hs: ${JSON.stringify(rgb2hs(this.serviceData.red, this.serviceData.green, this.serviceData.blue))}`); + await this.debugLog(`red: ${this.serviceData.red}, green: ${this.serviceData.green}, blue: ${this.serviceData.blue}`) + const [hue, saturation] = rgb2hs(this.serviceData.red, this.serviceData.green, this.serviceData.blue) + await this.debugLog(`hs: ${JSON.stringify(rgb2hs(this.serviceData.red, this.serviceData.green, this.serviceData.blue))}`) // Hue - this.LightBulb.Hue = hue; - this.debugLog(`Hue: ${this.LightBulb.Hue}`); + this.LightBulb.Hue = hue + this.debugLog(`Hue: ${this.LightBulb.Hue}`) // Saturation - this.LightBulb.Saturation = saturation; - this.debugLog(`Saturation: ${this.LightBulb.Saturation}`); + this.LightBulb.Saturation = saturation + this.debugLog(`Saturation: ${this.LightBulb.Saturation}`) } async openAPIparseStatus(): Promise { - await this.debugLog('openAPIparseStatus'); - await this.debugLog(`(power, brightness, color) = API:(${this.deviceStatus.power}, ${this.deviceStatus.brightness},` - + ` ${this.deviceStatus.color}), current:(${this.LightBulb.On}, ${this.LightBulb.Brightness}, ${this.LightBulb.Hue},` - + ` ${this.LightBulb.Saturation})`); + await this.debugLog('openAPIparseStatus') + await this.debugLog(`(power, brightness, color) = API:(${this.deviceStatus.power}, ${this.deviceStatus.brightness}, ${this.deviceStatus.color}), current:(${this.LightBulb.On}, ${this.LightBulb.Brightness}, ${this.LightBulb.Hue}, ${this.LightBulb.Saturation})`) // On - this.LightBulb.On = this.deviceStatus.power === 'on' ? true : false; - await this.debugLog(`On: ${this.LightBulb.On}`); + this.LightBulb.On = this.deviceStatus.power === 'on' + await this.debugLog(`On: ${this.LightBulb.On}`) // Brightness - this.LightBulb.Brightness = this.deviceStatus.brightness; - await this.debugLog(`Brightness: ${this.LightBulb.Brightness}`); + this.LightBulb.Brightness = this.deviceStatus.brightness + await this.debugLog(`Brightness: ${this.LightBulb.Brightness}`) // Color, Hue & Brightness - await this.debugLog(`color: ${JSON.stringify(this.deviceStatus.color)}`); - const [red, green, blue] = this.deviceStatus.color.split(':'); - await this.debugLog(`red: ${JSON.stringify(red)}, green: ${JSON.stringify(green)}, blue: ${JSON.stringify(blue)}`); - const [hue, saturation] = rgb2hs(red, green, blue); - await this.debugLog(`hs: ${JSON.stringify(rgb2hs(red, green, blue))}`); + await this.debugLog(`color: ${JSON.stringify(this.deviceStatus.color)}`) + const [red, green, blue] = this.deviceStatus.color.split(':') + await this.debugLog(`red: ${JSON.stringify(red)}, green: ${JSON.stringify(green)}, blue: ${JSON.stringify(blue)}`) + const [hue, saturation] = rgb2hs(red, green, blue) + await this.debugLog(`hs: ${JSON.stringify(rgb2hs(red, green, blue))}`) // Hue - this.LightBulb.Hue = hue; - await this.debugLog(`Hue: ${this.LightBulb.Hue}`); + this.LightBulb.Hue = hue + await this.debugLog(`Hue: ${this.LightBulb.Hue}`) // Saturation - this.LightBulb.Saturation = saturation; - await this.debugLog(`Saturation: ${this.LightBulb.Saturation}`); + this.LightBulb.Saturation = saturation + await this.debugLog(`Saturation: ${this.LightBulb.Saturation}`) // Firmware Version if (this.deviceStatus.version) { - const version = this.deviceStatus.version.toString(); - await this.debugLog(`Firmware Version: ${version.replace(/^V|-.*$/g, '')}`); - const deviceVersion = version.replace(/^V|-.*$/g, '') ?? '0.0.0'; + const version = this.deviceStatus.version.toString() + await this.debugLog(`Firmware Version: ${version.replace(/^V|-.*$/g, '')}`) + const deviceVersion = version.replace(/^V|-.*$/g, '') ?? '0.0.0' this.accessory .getService(this.hap.Service.AccessoryInformation)! .setCharacteristic(this.hap.Characteristic.HardwareRevision, deviceVersion) .setCharacteristic(this.hap.Characteristic.FirmwareRevision, deviceVersion) .getCharacteristic(this.hap.Characteristic.FirmwareRevision) - .updateValue(deviceVersion); - this.accessory.context.version = deviceVersion; - await this.debugSuccessLog(`version: ${this.accessory.context.version}`); + .updateValue(deviceVersion) + this.accessory.context.version = deviceVersion + await this.debugSuccessLog(`version: ${this.accessory.context.version}`) } } async parseStatusWebhook(): Promise { - await this.debugLog('parseStatusWebhook'); - await this.debugLog(`(powerState, brightness, color) = Webhook:(${this.webhookContext.powerState}, ${this.webhookContext.brightness},` - + ` ${this.webhookContext.color}), current:(${this.LightBulb.On}, ${this.LightBulb.Brightness}, ${this.LightBulb.Hue},` - + ` ${this.LightBulb.Saturation})`); + await this.debugLog('parseStatusWebhook') + await this.debugLog(`(powerState, brightness, color) = Webhook:(${this.webhookContext.powerState}, ${this.webhookContext.brightness}, ${this.webhookContext.color}), current:(${this.LightBulb.On}, ${this.LightBulb.Brightness}, ${this.LightBulb.Hue}, ${this.LightBulb.Saturation})`) // On - this.LightBulb.On = this.webhookContext.powerState === 'ON' ? true : false; - await this.debugLog(`On: ${this.LightBulb.On}`); + this.LightBulb.On = this.webhookContext.powerState === 'ON' + await this.debugLog(`On: ${this.LightBulb.On}`) // Brightness - this.LightBulb.Brightness = this.webhookContext.brightness; - await this.debugLog(`Brightness: ${this.LightBulb.Brightness}`); + this.LightBulb.Brightness = this.webhookContext.brightness + await this.debugLog(`Brightness: ${this.LightBulb.Brightness}`) // Color, Hue & Brightness - await this.debugLog(`color: ${JSON.stringify(this.webhookContext.color)}`); - const [red, green, blue] = this.webhookContext.color.split(':'); - await this.debugLog(`red: ${JSON.stringify(red)}, green: ${JSON.stringify(green)}, blue: ${JSON.stringify(blue)}`); - const [hue, saturation] = rgb2hs(red, green, blue); - await this.debugLog(`hs: ${JSON.stringify(rgb2hs(red, green, blue))}`); + await this.debugLog(`color: ${JSON.stringify(this.webhookContext.color)}`) + const [red, green, blue] = this.webhookContext.color.split(':') + await this.debugLog(`red: ${JSON.stringify(red)}, green: ${JSON.stringify(green)}, blue: ${JSON.stringify(blue)}`) + const [hue, saturation] = rgb2hs(red, green, blue) + await this.debugLog(`hs: ${JSON.stringify(rgb2hs(red, green, blue))}`) // Hue - this.LightBulb.Hue = hue; - await this.debugLog(`Hue: ${this.LightBulb.Hue}`); + this.LightBulb.Hue = hue + await this.debugLog(`Hue: ${this.LightBulb.Hue}`) // Saturation - this.LightBulb.Saturation = saturation; - await this.debugLog(`Saturation: ${this.LightBulb.Saturation}`); + this.LightBulb.Saturation = saturation + await this.debugLog(`Saturation: ${this.LightBulb.Saturation}`) } /** @@ -303,99 +299,103 @@ export class StripLight extends deviceBase { */ async refreshStatus(): Promise { if (!this.device.enableCloudService && this.OpenAPI) { - await this.errorLog(`refreshStatus enableCloudService: ${this.device.enableCloudService}`); - } else if (this.BLE || this.config.options?.BLE) { - await this.BLERefreshStatus(); + await this.errorLog(`refreshStatus enableCloudService: ${this.device.enableCloudService}`) + } else if (this.BLE) { + await this.BLERefreshStatus() } else if (this.OpenAPI && this.platform.config.credentials?.token) { - await this.openAPIRefreshStatus(); + await this.openAPIRefreshStatus() } else { - await this.offlineOff(); - await this.debugWarnLog(`Connection Type: ${this.device.connectionType}, refreshStatus will not happen.`); + await this.offlineOff() + await this.debugWarnLog(`Connection Type: ${this.device.connectionType}, refreshStatus will not happen.`) } } async BLERefreshStatus(): Promise { - await this.debugLog('BLERefreshStatus'); + await this.debugLog('BLERefreshStatus') + const switchbot = await this.switchbotBLE() + if (switchbot === undefined) { + await this.BLERefreshConnection(switchbot) + } else { + // Start to monitor advertisement packets + (async () => { + // Start to monitor advertisement packets + const serviceData = await this.monitorAdvertisementPackets(switchbot) as stripLightServiceData + // Update HomeKit + if (serviceData.model === SwitchBotBLEModel.StripLight && serviceData.modelName === SwitchBotBLEModelName.StripLight) { + this.serviceData = serviceData + await this.BLEparseStatus() + await this.updateHomeKitCharacteristics() + } else { + await this.errorLog(`failed to get serviceData, serviceData: ${serviceData}`) + await this.BLERefreshConnection(switchbot) + } + })() + } + } + + async registerPlatformBLE(): Promise { + await this.debugLog('registerPlatformBLE') if (this.config.options?.BLE) { - await this.debugLog('is listening to Platform BLE.'); - this.device.bleMac = this.device.deviceId!.match(/.{1,2}/g)!.join(':').toLowerCase(); - await this.debugLog(`bleMac: ${this.device.bleMac}`); + await this.debugLog('is listening to Platform BLE.') + this.device.bleMac = this.device.deviceId!.match(/.{1,2}/g)!.join(':').toLowerCase() + await this.debugLog(`bleMac: ${this.device.bleMac}`) this.platform.bleEventHandler[this.device.bleMac] = async (context: stripLightServiceData) => { try { - await this.debugLog(`received BLE: ${JSON.stringify(context)}`); - this.serviceData = context; - await this.BLEparseStatus(); - await this.updateHomeKitCharacteristics(); + await this.debugLog(`received BLE: ${JSON.stringify(context)}`) + this.serviceData = context + await this.BLEparseStatus() + await this.updateHomeKitCharacteristics() } catch (e: any) { - await this.errorLog(`failed to handle BLE. Received: ${JSON.stringify(context)} Error: ${e}`); + await this.errorLog(`failed to handle BLE. Received: ${JSON.stringify(context)} Error: ${e}`) } - }; - } else { - await this.debugLog('is using Device BLE Scanning.'); - const switchbot = await this.switchbotBLE(); - if (switchbot === undefined) { - await this.BLERefreshConnection(switchbot); - } else { - // Start to monitor advertisement packets - (async () => { - // Start to monitor advertisement packets - const serviceData = await this.monitorAdvertisementPackets(switchbot) as stripLightServiceData; - // Update HomeKit - if (serviceData.model === SwitchBotBLEModel.StripLight && serviceData.modelName === SwitchBotBLEModelName.StripLight) { - this.serviceData = serviceData; - await this.BLEparseStatus(); - await this.updateHomeKitCharacteristics(); - } else { - await this.errorLog(`failed to get serviceData, serviceData: ${serviceData}`); - await this.BLERefreshConnection(switchbot); - } - })(); } + } else { + await this.debugLog('is not listening to Platform BLE') } } async openAPIRefreshStatus(): Promise { - await this.debugLog('openAPIRefreshStatus'); + await this.debugLog('openAPIRefreshStatus') try { - const { body, statusCode } = await this.deviceRefreshStatus(); - const deviceStatus: any = await body.json(); - await this.debugLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`);; + const { body, statusCode } = await this.deviceRefreshStatus() + const deviceStatus: any = await body.json() + await this.debugLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`) if (await this.successfulStatusCodes(statusCode, deviceStatus)) { - await this.debugSuccessLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`); - this.deviceStatus = deviceStatus.body; - await this.openAPIparseStatus(); - await this.updateHomeKitCharacteristics(); + await this.debugSuccessLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`) + this.deviceStatus = deviceStatus.body + await this.openAPIparseStatus() + await this.updateHomeKitCharacteristics() } else { - await this.debugWarnLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`); - await this.debugWarnLog(statusCode, deviceStatus); + await this.debugWarnLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`) + await this.debugWarnLog(statusCode, deviceStatus) } } catch (e: any) { - await this.apiError(e); - await this.errorLog(`failed openAPIRefreshStatus with ${this.device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`); + await this.apiError(e) + await this.errorLog(`failed openAPIRefreshStatus with ${this.device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`) } } async registerWebhook() { if (this.device.webhook) { - await this.debugLog('is listening webhook.'); + await this.debugLog('is listening webhook.') this.platform.webhookEventHandler[this.device.deviceId] = async (context: stripLightWebhookContext) => { try { - await this.debugLog(`received Webhook: ${JSON.stringify(context)}`); - this.webhookContext = context; - await this.parseStatusWebhook(); - await this.updateHomeKitCharacteristics(); + await this.debugLog(`received Webhook: ${JSON.stringify(context)}`) + this.webhookContext = context + await this.parseStatusWebhook() + await this.updateHomeKitCharacteristics() } catch (e: any) { - await this.errorLog(`failed to handle webhook. Received: ${JSON.stringify(context)} Error: ${e}`); + await this.errorLog(`failed to handle webhook. Received: ${JSON.stringify(context)} Error: ${e}`) } - }; + } } else { - await this.debugLog('is not listening webhook.'); + await this.debugLog('is not listening webhook.') } } /** * Pushes the requested changes to the SwitchBot API - * deviceType commandType Command command parameter Description + * deviceType commandType Command command parameter Description * Strip Light - "command" "turnOn" "default" = set to ON state | * Strip Light - "command" "turnOff" "default" = set to OFF state | * Strip Light - "command" "toggle" "default" = toggle state | @@ -405,250 +405,242 @@ export class StripLight extends deviceBase { */ async pushChanges(): Promise { if (!this.device.enableCloudService && this.OpenAPI) { - this.errorLog(`pushChanges enableCloudService: ${this.device.enableCloudService}`); + this.errorLog(`pushChanges enableCloudService: ${this.device.enableCloudService}`) } else if (this.BLE) { - await this.BLEpushChanges(); + await this.BLEpushChanges() if (this.LightBulb.On) { // Push Brightness Update - await this.debugLog(`Brightness: ${this.LightBulb.Brightness}`); - await this.BLEpushBrightnessChanges(); + await this.debugLog(`Brightness: ${this.LightBulb.Brightness}`) + await this.BLEpushBrightnessChanges() // Push Hue & Saturation Update - await this.debugLog(`Hue: ${this.LightBulb.Hue}, Saturation: ${this.LightBulb.Saturation}`); - await this.BLEpushRGBChanges(); + await this.debugLog(`Hue: ${this.LightBulb.Hue}, Saturation: ${this.LightBulb.Saturation}`) + await this.BLEpushRGBChanges() // Set ColorTemperature if (this.LightBulb.ColorTemperature !== this.accessory.context.ColorTemperature) { - const kelvin = Math.round(1000000 / Number(this.LightBulb.ColorTemperature)); - this.accessory.context.kelvin = kelvin; + const kelvin = Math.round(1000000 / Number(this.LightBulb.ColorTemperature)) + this.accessory.context.kelvin = kelvin } else { - await this.debugLog(`No pushColorTemperatureChanges, ColorTemperature: ${this.LightBulb.ColorTemperature},` - + ` ColorTemperatureCached: ${this.accessory.context.ColorTemperature}`); + await this.debugLog(`No pushColorTemperatureChanges, ColorTemperature: ${this.LightBulb.ColorTemperature}, ColorTemperatureCached: ${this.accessory.context.ColorTemperature}`) } } else { - await this.debugLog('BLE (Brightness), (Hue), & (Saturation) changes will not happen, as the device is OFF.'); + await this.debugLog('BLE (Brightness), (Hue), & (Saturation) changes will not happen, as the device is OFF.') } } else if (this.OpenAPI && this.platform.config.credentials?.token) { - await this.openAPIpushChanges(); + await this.openAPIpushChanges() if (this.LightBulb.On) { // Push Brightness Update - await this.debugLog(`Brightness: ${this.LightBulb.Brightness}`); - await this.pushBrightnessChanges(); + await this.debugLog(`Brightness: ${this.LightBulb.Brightness}`) + await this.pushBrightnessChanges() // Push Hue & Saturation Update - await this.debugLog(`Hue: ${this.LightBulb.Hue}, Saturation: ${this.LightBulb.Saturation}`); - await this.pushHueSaturationChanges(); + await this.debugLog(`Hue: ${this.LightBulb.Hue}, Saturation: ${this.LightBulb.Saturation}`) + await this.pushHueSaturationChanges() } else { - await this.debugLog('openAPI (Brightness), (ColorTemperature), (Hue), & (Saturation) changes will not happen, as the device is OFF.'); + await this.debugLog('openAPI (Brightness), (ColorTemperature), (Hue), & (Saturation) changes will not happen, as the device is OFF.') } } else { - await this.offlineOff(); - await this.debugWarnLog(`Connection Type: ${this.device.connectionType}, pushChanges will not happen.`); + await this.offlineOff() + await this.debugWarnLog(`Connection Type: ${this.device.connectionType}, pushChanges will not happen.`) } // Refresh the status from the API interval(15000) .pipe(skipWhile(() => this.stripLightUpdateInProgress)) .pipe(take(1)) .subscribe(async () => { - await this.refreshStatus(); - }); + await this.refreshStatus() + }) } async BLEpushChanges(): Promise { - await this.debugLog('BLEpushChanges'); + await this.debugLog('BLEpushChanges') if (this.LightBulb.On !== this.accessory.context.On) { - await this.debugLog(`BLEpushChanges On: ${this.LightBulb.On}, OnCached: ${this.accessory.context.On}`); - const switchbot = await this.platform.connectBLE(this.accessory, this.device); - await this.convertBLEAddress(); + await this.debugLog(`BLEpushChanges On: ${this.LightBulb.On}, OnCached: ${this.accessory.context.On}`) + const switchbot = await this.platform.connectBLE(this.accessory, this.device) + await this.convertBLEAddress() if (switchbot !== false) { switchbot .discover({ model: this.device.bleModel, id: this.device.bleMac }) .then(async (device_list: any) => { - await this.infoLog(`On: ${this.LightBulb.On}`); + await this.infoLog(`On: ${this.LightBulb.On}`) return await this.retryBLE({ max: await this.maxRetryBLE(), fn: async () => { if (this.LightBulb.On) { - return await device_list[0].turnOn({ id: this.device.bleMac }); + return await device_list[0].turnOn({ id: this.device.bleMac }) } else { - return await device_list[0].turnOff({ id: this.device.bleMac }); + return await device_list[0].turnOff({ id: this.device.bleMac }) } }, - }); + }) }) .then(async () => { - await this.successLog(`On: ${this.LightBulb.On} sent over SwitchBot BLE, sent successfully`); - await this.updateHomeKitCharacteristics(); + await this.successLog(`On: ${this.LightBulb.On} sent over SwitchBot BLE, sent successfully`) + await this.updateHomeKitCharacteristics() }) .catch(async (e: any) => { - await this.apiError(e); - await this.errorLog(`failed BLEpushChanges with ${this.device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`); - await this.BLEPushConnection(); - }); + await this.apiError(e) + await this.errorLog(`failed BLEpushChanges with ${this.device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`) + await this.BLEPushConnection() + }) } else { - await this.errorLog(`wasn't able to establish BLE Connection, node-switchbot: ${switchbot}`); - await this.BLEPushConnection(); + await this.errorLog(`wasn't able to establish BLE Connection, node-switchbot: ${switchbot}`) + await this.BLEPushConnection() } } else { - await this.debugLog(`No changes (BLEpushChanges), On: ${this.LightBulb.On}, OnCached: ${this.accessory.context.On}`); + await this.debugLog(`No changes (BLEpushChanges), On: ${this.LightBulb.On}, OnCached: ${this.accessory.context.On}`) } } async BLEpushBrightnessChanges(): Promise { - await this.debugLog('BLEpushBrightnessChanges'); + await this.debugLog('BLEpushBrightnessChanges') if (this.LightBulb.Brightness !== this.accessory.context.Brightness) { - const switchbot = await this.platform.connectBLE(this.accessory, this.device); - await this.convertBLEAddress(); + const switchbot = await this.platform.connectBLE(this.accessory, this.device) + await this.convertBLEAddress() if (switchbot !== false) { switchbot .discover({ model: this.device.bleModel, id: this.device.bleMac }) .then(async (device_list: any) => { - await this.infoLog(`Brightness: ${this.LightBulb.Brightness}`); - return await device_list[0].setBrightness(this.LightBulb.Brightness); + await this.infoLog(`Brightness: ${this.LightBulb.Brightness}`) + return await device_list[0].setBrightness(this.LightBulb.Brightness) }) .then(async () => { - await this.successLog(`Brightness: ${this.LightBulb.Brightness} sent over SwitchBot BLE, sent successfully`); - await this.updateHomeKitCharacteristics(); + await this.successLog(`Brightness: ${this.LightBulb.Brightness} sent over SwitchBot BLE, sent successfully`) + await this.updateHomeKitCharacteristics() }) .catch(async (e: any) => { - await this.apiError(e); - await this.errorLog(`failed BLEpushChanges with ${this.device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`); - await this.BLEPushConnection(); - }); + await this.apiError(e) + await this.errorLog(`failed BLEpushChanges with ${this.device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`) + await this.BLEPushConnection() + }) } else { - await this.errorLog(`wasn't able to establish BLE Connection, node-switchbot: ${switchbot}`); - await this.BLEPushConnection(); + await this.errorLog(`wasn't able to establish BLE Connection, node-switchbot: ${switchbot}`) + await this.BLEPushConnection() } } else { - await this.debugLog(`No changes (BLEpushBrightnessChanges), Brightness: ${this.LightBulb.Brightness},` - + ` BrightnessCached: ${this.accessory.context.Brightness}`); + await this.debugLog(`No changes (BLEpushBrightnessChanges), Brightness: ${this.LightBulb.Brightness}, BrightnessCached: ${this.accessory.context.Brightness}`) } } async BLEpushRGBChanges(): Promise { - await this.debugLog('BLEpushRGBChanges'); + await this.debugLog('BLEpushRGBChanges') if ((this.LightBulb.Hue !== this.accessory.context.Hue) || (this.LightBulb.Saturation !== this.accessory.context.Saturation)) { - await this.debugLog(`Hue: ${JSON.stringify(this.LightBulb.Hue)}, Saturation: ${JSON.stringify(this.LightBulb.Saturation)}`); - const [red, green, blue] = hs2rgb(this.LightBulb.Hue, this.LightBulb.Saturation); - await this.debugLog(`rgb: ${JSON.stringify([red, green, blue])}`); - const switchbot = await this.platform.connectBLE(this.accessory, this.device); - await this.convertBLEAddress(); + await this.debugLog(`Hue: ${JSON.stringify(this.LightBulb.Hue)}, Saturation: ${JSON.stringify(this.LightBulb.Saturation)}`) + const [red, green, blue] = hs2rgb(this.LightBulb.Hue, this.LightBulb.Saturation) + await this.debugLog(`rgb: ${JSON.stringify([red, green, blue])}`) + const switchbot = await this.platform.connectBLE(this.accessory, this.device) + await this.convertBLEAddress() if (switchbot !== false) { switchbot .discover({ model: this.device.bleModel, id: this.device.bleMac }) .then(async (device_list: any) => { - await this.infoLog(`RGB: ${(this.LightBulb.Brightness, red, green, blue)}`); - return await device_list[0].setRGB(this.LightBulb.Brightness, red, green, blue); + await this.infoLog(`RGB: ${(this.LightBulb.Brightness, red, green, blue)}`) + return await device_list[0].setRGB(this.LightBulb.Brightness, red, green, blue) }) .then(async () => { - await this.successLog(`RGB: ${(this.LightBulb.Brightness, red, green, blue)} sent over SwitchBot BLE, sent successfully`); - await this.updateHomeKitCharacteristics(); + await this.successLog(`RGB: ${(this.LightBulb.Brightness, red, green, blue)} sent over SwitchBot BLE, sent successfully`) + await this.updateHomeKitCharacteristics() }) .catch(async (e: any) => { - await this.apiError(e); - await this.errorLog(`failed BLEpushRGBChanges with ${this.device.connectionType} Connection,` - + ` Error Message: ${JSON.stringify(e.message)}`); - await this.BLEPushConnection(); - }); + await this.apiError(e) + await this.errorLog(`failed BLEpushRGBChanges with ${this.device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`) + await this.BLEPushConnection() + }) } else { - await this.errorLog(`wasn't able to establish BLE Connection, node-switchbot: ${switchbot}`); - await this.BLEPushConnection(); + await this.errorLog(`wasn't able to establish BLE Connection, node-switchbot: ${switchbot}`) + await this.BLEPushConnection() } } else { - await this.debugLog(`No changes (BLEpushRGBChanges), Hue: ${this.LightBulb.Hue}, HueCached: ${this.accessory.context.Hue},` - + ` Saturation: ${this.LightBulb.Saturation}, SaturationCached: ${this.accessory.context.Saturation}`); + await this.debugLog(`No changes (BLEpushRGBChanges), Hue: ${this.LightBulb.Hue}, HueCached: ${this.accessory.context.Hue}, Saturation: ${this.LightBulb.Saturation}, SaturationCached: ${this.accessory.context.Saturation}`) } } async openAPIpushChanges() { - await this.debugLog('openAPIpushChanges'); + await this.debugLog('openAPIpushChanges') if (this.LightBulb.On !== this.accessory.context.On) { - const command = this.LightBulb.On ? 'turnOn' : 'turnOff'; + const command = this.LightBulb.On ? 'turnOn' : 'turnOff' const bodyChange = JSON.stringify({ command: `${command}`, parameter: 'default', commandType: 'command', - }); - await this.debugLog(`SwitchBot OpenAPI bodyChange: ${JSON.stringify(bodyChange)}`); + }) + await this.debugLog(`SwitchBot OpenAPI bodyChange: ${JSON.stringify(bodyChange)}`) try { - const { body, statusCode } = await this.pushChangeRequest(bodyChange); - const deviceStatus: any = await body.json(); - await this.debugLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`); + const { body, statusCode } = await this.pushChangeRequest(bodyChange) + const deviceStatus: any = await body.json() + await this.debugLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`) if (await this.successfulStatusCodes(statusCode, deviceStatus)) { - await this.debugSuccessLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`); - await this.updateHomeKitCharacteristics(); + await this.debugSuccessLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`) + await this.updateHomeKitCharacteristics() } else { - await this.statusCode(statusCode); - await this.statusCode(deviceStatus.statusCode); + await this.statusCode(statusCode) + await this.statusCode(deviceStatus.statusCode) } } catch (e: any) { - await this.apiError(e); - await this.errorLog(`failed openAPIpushChanges with ${this.device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`); + await this.apiError(e) + await this.errorLog(`failed openAPIpushChanges with ${this.device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`) } } else { - await this.debugLog(`No changes (openAPIpushChanges), On: ${this.LightBulb.On}, OnCached: ${this.accessory.context.On}`); + await this.debugLog(`No changes (openAPIpushChanges), On: ${this.LightBulb.On}, OnCached: ${this.accessory.context.On}`) } } async pushHueSaturationChanges(): Promise { - await this.debugLog('pushHueSaturationChanges'); + await this.debugLog('pushHueSaturationChanges') if ((this.LightBulb.Hue !== this.accessory.context.Hue) || (this.LightBulb.Saturation !== this.accessory.context.Saturation)) { - await this.debugLog(`Hue: ${JSON.stringify(this.LightBulb.Hue)}, Saturation: ${JSON.stringify(this.LightBulb.Saturation)}`); - const [red, green, blue] = hs2rgb(this.LightBulb.Hue, this.LightBulb.Saturation); - await this.debugLog(`rgb: ${JSON.stringify([red, green, blue])}`); + await this.debugLog(`Hue: ${JSON.stringify(this.LightBulb.Hue)}, Saturation: ${JSON.stringify(this.LightBulb.Saturation)}`) + const [red, green, blue] = hs2rgb(this.LightBulb.Hue, this.LightBulb.Saturation) + await this.debugLog(`rgb: ${JSON.stringify([red, green, blue])}`) const bodyChange = JSON.stringify({ command: 'setColor', parameter: `${red}:${green}:${blue}`, commandType: 'command', - }); - await this.debugLog(`SwitchBot OpenAPI bodyChange: ${JSON.stringify(bodyChange)}`); + }) + await this.debugLog(`SwitchBot OpenAPI bodyChange: ${JSON.stringify(bodyChange)}`) try { - const { body, statusCode } = await this.pushChangeRequest(bodyChange); - const deviceStatus: any = await body.json(); - await this.debugLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`); + const { body, statusCode } = await this.pushChangeRequest(bodyChange) + const deviceStatus: any = await body.json() + await this.debugLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`) if (await this.successfulStatusCodes(statusCode, deviceStatus)) { - await this.debugSuccessLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`); - await this.updateHomeKitCharacteristics(); + await this.debugSuccessLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`) + await this.updateHomeKitCharacteristics() } else { - await this.statusCode(statusCode); - await this.statusCode(deviceStatus.statusCode); + await this.statusCode(statusCode) + await this.statusCode(deviceStatus.statusCode) } } catch (e: any) { - await this.apiError(e); - await this.errorLog(`failed pushHueSaturationChanges with ${this.device.connectionType} Connection,` - + ` Error Message: ${JSON.stringify(e.message)}`); + await this.apiError(e) + await this.errorLog(`failed pushHueSaturationChanges with ${this.device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`) } } else { - await this.debugLog(`No changes (pushHueSaturationChanges), Hue: ${this.LightBulb.Hue}, HueCached: ${this.accessory.context.Hue},` - + ` Saturation: ${this.LightBulb.Saturation}, SaturationCached: ${this.accessory.context.Saturation}`); + await this.debugLog(`No changes (pushHueSaturationChanges), Hue: ${this.LightBulb.Hue}, HueCached: ${this.accessory.context.Hue}, Saturation: ${this.LightBulb.Saturation}, SaturationCached: ${this.accessory.context.Saturation}`) } } async pushBrightnessChanges(): Promise { - await this.debugLog('pushBrightnessChanges'); + await this.debugLog('pushBrightnessChanges') if (this.LightBulb.Brightness !== this.accessory.context.Brightness) { const bodyChange = JSON.stringify({ command: 'setBrightness', parameter: `${this.LightBulb.Brightness}`, commandType: 'command', - }); - await this.debugLog(`SwitchBot OpenAPI bodyChange: ${JSON.stringify(bodyChange)}`); + }) + await this.debugLog(`SwitchBot OpenAPI bodyChange: ${JSON.stringify(bodyChange)}`) try { - const { body, statusCode } = await this.pushChangeRequest(bodyChange); - const deviceStatus: any = await body.json(); - await this.debugLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`); + const { body, statusCode } = await this.pushChangeRequest(bodyChange) + const deviceStatus: any = await body.json() + await this.debugLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`) if (await this.successfulStatusCodes(statusCode, deviceStatus)) { - await this.debugSuccessLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`); - await this.updateHomeKitCharacteristics(); + await this.debugSuccessLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`) + await this.updateHomeKitCharacteristics() } else { - await this.statusCode(statusCode); - await this.statusCode(deviceStatus.statusCode); + await this.statusCode(statusCode) + await this.statusCode(deviceStatus.statusCode) } } catch (e: any) { - await this.apiError(e); - await this.errorLog(`failed pushBrightnessChanges with ${this.device.connectionType} Connection,` - + ` Error Message: ${JSON.stringify(e.message)}`); + await this.apiError(e) + await this.errorLog(`failed pushBrightnessChanges with ${this.device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`) } } else { - await this.debugLog(`No changes (pushBrightnessChanges), Brightness: ${this.LightBulb.Brightness},` - + ` BrightnessCached: ${this.accessory.context.Brightness}`); + await this.debugLog(`No changes (pushBrightnessChanges), Brightness: ${this.LightBulb.Brightness}, BrightnessCached: ${this.accessory.context.Brightness}`) } } @@ -657,13 +649,13 @@ export class StripLight extends deviceBase { */ async OnSet(value: CharacteristicValue): Promise { if (this.LightBulb.On !== this.accessory.context.On) { - await this.infoLog(`Set On: ${value}`); + await this.infoLog(`Set On: ${value}`) } else { - await this.debugLog(`No Changes, On: ${value}`); + await this.debugLog(`No Changes, On: ${value}`) } - this.LightBulb.On = value; - this.doStripLightUpdate.next(); + this.LightBulb.On = value + this.doStripLightUpdate.next() } /** @@ -671,17 +663,17 @@ export class StripLight extends deviceBase { */ async BrightnessSet(value: CharacteristicValue): Promise { if (this.LightBulb.On && (this.LightBulb.Brightness !== this.accessory.context.Brightness)) { - await this.infoLog(`Set Brightness: ${value}`); + await this.infoLog(`Set Brightness: ${value}`) } else { if (this.LightBulb.On) { - this.debugLog(`No Changes, Brightness: ${value}`); + this.debugLog(`No Changes, Brightness: ${value}`) } else { - this.debugLog(`Set Brightness: ${value}, On: ${this.LightBulb.On}`); + this.debugLog(`Set Brightness: ${value}, On: ${this.LightBulb.On}`) } } - this.LightBulb.Brightness = value; - this.doStripLightUpdate.next(); + this.LightBulb.Brightness = value + this.doStripLightUpdate.next() } /** @@ -689,33 +681,33 @@ export class StripLight extends deviceBase { */ async ColorTemperatureSet(value: CharacteristicValue): Promise { if (this.LightBulb.On && (this.LightBulb.ColorTemperature !== this.accessory.context.ColorTemperature)) { - this.infoLog(`Set ColorTemperature: ${value}`); + this.infoLog(`Set ColorTemperature: ${value}`) } else { if (this.LightBulb.On) { - this.debugLog(`No Changes, ColorTemperature: ${value}`); + this.debugLog(`No Changes, ColorTemperature: ${value}`) } else { - this.debugLog(`Set ColorTemperature: ${value}, On: ${this.LightBulb.On}`); + this.debugLog(`Set ColorTemperature: ${value}, On: ${this.LightBulb.On}`) } } - const minKelvin = 2000; - const maxKelvin = 9000; + const minKelvin = 2000 + const maxKelvin = 9000 // Convert mired to kelvin to nearest 100 (SwitchBot seems to need this) - const kelvin = Math.round(1000000 / Number(value) / 100) * 100; + const kelvin = Math.round(1000000 / Number(value) / 100) * 100 // Check and increase/decrease kelvin to range of device - const k = Math.min(Math.max(kelvin, minKelvin), maxKelvin); + const k = Math.min(Math.max(kelvin, minKelvin), maxKelvin) if (!this.accessory.context.On || this.accessory.context.maxKelvin === k) { - return; + return } // Updating the hue/sat to the corresponding values mimics native adaptive lighting - const hs = m2hs(value); - this.LightBulb.Service.updateCharacteristic(this.hap.Characteristic.Hue, hs[0]); - this.LightBulb.Service.updateCharacteristic(this.hap.Characteristic.Saturation, hs[1]); + const hs = m2hs(value) + this.LightBulb.Service.updateCharacteristic(this.hap.Characteristic.Hue, hs[0]) + this.LightBulb.Service.updateCharacteristic(this.hap.Characteristic.Saturation, hs[1]) - this.LightBulb.ColorTemperature = value; - this.doStripLightUpdate.next(); + this.LightBulb.ColorTemperature = value + this.doStripLightUpdate.next() } /** @@ -723,19 +715,19 @@ export class StripLight extends deviceBase { */ async HueSet(value: CharacteristicValue): Promise { if (this.LightBulb.On && (this.LightBulb.Hue !== this.accessory.context.Hue)) { - this.infoLog(`Set Hue: ${value}`); + this.infoLog(`Set Hue: ${value}`) } else { if (this.LightBulb.On) { - this.debugLog(`No Changes, Hue: ${value}`); + this.debugLog(`No Changes, Hue: ${value}`) } else { - this.debugLog(`Set Hue: ${value}, On: ${this.LightBulb.On}`); + this.debugLog(`Set Hue: ${value}, On: ${this.LightBulb.On}`) } } - this.LightBulb.Service.updateCharacteristic(this.hap.Characteristic.ColorTemperature, 140); + this.LightBulb.Service.updateCharacteristic(this.hap.Characteristic.ColorTemperature, 140) - this.LightBulb.Hue = value; - this.doStripLightUpdate.next(); + this.LightBulb.Hue = value + this.doStripLightUpdate.next() } /** @@ -743,79 +735,74 @@ export class StripLight extends deviceBase { */ async SaturationSet(value: CharacteristicValue): Promise { if (this.LightBulb.On && (this.LightBulb.Saturation !== this.accessory.context.Saturation)) { - this.infoLog(`Set Saturation: ${value}`); + this.infoLog(`Set Saturation: ${value}`) } else { if (this.LightBulb.On) { - this.debugLog(`No Changes, Saturation: ${value}`); + this.debugLog(`No Changes, Saturation: ${value}`) } else { - this.debugLog(`Set Saturation: ${value}, On: ${this.LightBulb.On}`); + this.debugLog(`Set Saturation: ${value}, On: ${this.LightBulb.On}`) } } - this.LightBulb.Service.updateCharacteristic(this.hap.Characteristic.ColorTemperature, 140); + this.LightBulb.Service.updateCharacteristic(this.hap.Characteristic.ColorTemperature, 140) - this.LightBulb.Saturation = value; - this.doStripLightUpdate.next(); + this.LightBulb.Saturation = value + this.doStripLightUpdate.next() } async updateHomeKitCharacteristics(): Promise { // On - await this.updateCharacteristic(this.LightBulb.Service, this.hap.Characteristic.On, - this.LightBulb.On, 'On'); + await this.updateCharacteristic(this.LightBulb.Service, this.hap.Characteristic.On, this.LightBulb.On, 'On') // Brightness - await this.updateCharacteristic(this.LightBulb.Service, this.hap.Characteristic.Brightness, - this.LightBulb.Brightness, 'Brightness'); + await this.updateCharacteristic(this.LightBulb.Service, this.hap.Characteristic.Brightness, this.LightBulb.Brightness, 'Brightness') // ColorTemperature - await this.updateCharacteristic(this.LightBulb.Service, this.hap.Characteristic.ColorTemperature, - this.LightBulb.ColorTemperature, 'ColorTemperature'); + await this.updateCharacteristic(this.LightBulb.Service, this.hap.Characteristic.ColorTemperature, this.LightBulb.ColorTemperature, 'ColorTemperature') // Hue - await this.updateCharacteristic(this.LightBulb.Service, this.hap.Characteristic.Hue, - this.LightBulb.Hue, 'Hue'); + await this.updateCharacteristic(this.LightBulb.Service, this.hap.Characteristic.Hue, this.LightBulb.Hue, 'Hue') // Saturation - await this.updateCharacteristic(this.LightBulb.Service, this.hap.Characteristic.Saturation, - this.LightBulb.Saturation, 'Saturation'); + await this.updateCharacteristic(this.LightBulb.Service, this.hap.Characteristic.Saturation, this.LightBulb.Saturation, 'Saturation') } async getAdaptiveLightingSettings(accessory: PlatformAccessory, device: device & devicesConfig): Promise { // Adaptive Lighting - this.adaptiveLighting = accessory.context.adaptiveLighting ?? true; - await this.debugLog(`adaptiveLighting: ${this.adaptiveLighting}`); + this.adaptiveLighting = accessory.context.adaptiveLighting ?? true + await this.debugLog(`adaptiveLighting: ${this.adaptiveLighting}`) // Adaptive Lighting Shift if (device.striplight?.adaptiveLightingShift) { - this.adaptiveLightingShift = device.striplight.adaptiveLightingShift; - this.debugLog(`adaptiveLightingShift: ${this.adaptiveLightingShift}`); + this.adaptiveLightingShift = device.striplight.adaptiveLightingShift + this.debugLog(`adaptiveLightingShift: ${this.adaptiveLightingShift}`) } else { - this.adaptiveLightingShift = 0; - this.debugLog(`adaptiveLightingShift: ${this.adaptiveLightingShift}`); + this.adaptiveLightingShift = 0 + this.debugLog(`adaptiveLightingShift: ${this.adaptiveLightingShift}`) } } async BLEPushConnection() { if (this.platform.config.credentials?.token && this.device.connectionType === 'BLE/OpenAPI') { - await this.warnLog('Using OpenAPI Connection to Push Changes'); - await this.openAPIpushChanges(); + await this.warnLog('Using OpenAPI Connection to Push Changes') + await this.openAPIpushChanges() } } async BLERefreshConnection(switchbot: any): Promise { - await this.errorLog(`wasn't able to establish BLE Connection, node-switchbot: ${switchbot}`); + await this.errorLog(`wasn't able to establish BLE Connection, node-switchbot: ${switchbot}`) if (this.platform.config.credentials?.token && this.device.connectionType === 'BLE/OpenAPI') { - await this.warnLog('Using OpenAPI Connection to Refresh Status'); - await this.openAPIRefreshStatus(); + await this.warnLog('Using OpenAPI Connection to Refresh Status') + await this.openAPIRefreshStatus() } } async offlineOff(): Promise { if (this.device.offline) { - this.LightBulb.Service.updateCharacteristic(this.hap.Characteristic.On, false); + this.LightBulb.Service.updateCharacteristic(this.hap.Characteristic.On, false) } } async apiError(e: any): Promise { - this.LightBulb.Service.updateCharacteristic(this.hap.Characteristic.On, e); - this.LightBulb.Service.updateCharacteristic(this.hap.Characteristic.Hue, e); - this.LightBulb.Service.updateCharacteristic(this.hap.Characteristic.Brightness, e); - this.LightBulb.Service.updateCharacteristic(this.hap.Characteristic.Saturation, e); - this.LightBulb.Service.updateCharacteristic(this.hap.Characteristic.ColorTemperature, e); + this.LightBulb.Service.updateCharacteristic(this.hap.Characteristic.On, e) + this.LightBulb.Service.updateCharacteristic(this.hap.Characteristic.Hue, e) + this.LightBulb.Service.updateCharacteristic(this.hap.Characteristic.Brightness, e) + this.LightBulb.Service.updateCharacteristic(this.hap.Characteristic.Saturation, e) + this.LightBulb.Service.updateCharacteristic(this.hap.Characteristic.ColorTemperature, e) } } diff --git a/src/device/lock.ts b/src/device/lock.ts index 5ba8fb71..626d839a 100644 --- a/src/device/lock.ts +++ b/src/device/lock.ts @@ -2,293 +2,307 @@ * * lock.ts: @switchbot/homebridge-switchbot. */ -import { deviceBase } from './device.js'; -import { SwitchBotBLEModel, SwitchBotBLEModelName } from 'node-switchbot'; -import { Subject, debounceTime, interval, skipWhile, take, tap } from 'rxjs'; - -import type { devicesConfig } from '../settings.js'; -import type { device } from '../types/devicelist.js'; -import type { SwitchBotPlatform } from '../platform.js'; -import type { Service, PlatformAccessory, CharacteristicValue } from 'homebridge'; -import type { lockServiceData, lockProServiceData } from '../types/bledevicestatus.js'; -import type { lockStatus, lockProStatus } from '../types/devicestatus.js'; -import type { lockProWebhookContext, lockWebhookContext } from '../types/devicewebhookstatus.js'; +import type { CharacteristicValue, PlatformAccessory, Service } from 'homebridge' + +import type { SwitchBotPlatform } from '../platform.js' +import type { devicesConfig } from '../settings.js' +import type { lockProServiceData, lockServiceData } from '../types/bledevicestatus.js' +import type { device } from '../types/devicelist.js' +import type { lockProStatus, lockStatus } from '../types/devicestatus.js' +import type { lockProWebhookContext, lockWebhookContext } from '../types/devicewebhookstatus.js' + +/* +* For Testing Locally: +* import { SwitchBotBLEModel, SwitchBotBLEModelName } from '/Users/Shared/GitHub/OpenWonderLabs/node-switchbot/dist/index.js'; +*/ +import { SwitchBotBLEModel, SwitchBotBLEModelName } from 'node-switchbot' +import { debounceTime, interval, skipWhile, Subject, take, tap } from 'rxjs' + +import { deviceBase } from './device.js' export class Lock extends deviceBase { // Services private LockMechanism: { - Name: CharacteristicValue; - Service: Service; - LockTargetState: CharacteristicValue; - LockCurrentState: CharacteristicValue; - }; + Name: CharacteristicValue + Service: Service + LockTargetState: CharacteristicValue + LockCurrentState: CharacteristicValue + } private Battery: { - Name: CharacteristicValue; - Service: Service; - BatteryLevel: CharacteristicValue; - StatusLowBattery: CharacteristicValue; - }; + Name: CharacteristicValue + Service: Service + BatteryLevel: CharacteristicValue + StatusLowBattery: CharacteristicValue + } private ContactSensor?: { - Name: CharacteristicValue; - Service: Service; - ContactSensorState: CharacteristicValue; - }; + Name: CharacteristicValue + Service: Service + ContactSensorState: CharacteristicValue + } private Switch?: { - Name: CharacteristicValue; - Service: Service; - On: CharacteristicValue; - }; + Name: CharacteristicValue + Service: Service + On: CharacteristicValue + } // OpenAPI - deviceStatus!: lockStatus | lockProStatus; + deviceStatus!: lockStatus | lockProStatus - //Webhook - webhookContext!: lockWebhookContext | lockProWebhookContext; + // Webhook + webhookContext!: lockWebhookContext | lockProWebhookContext // BLE - serviceData!: lockServiceData | lockProServiceData; + serviceData!: lockServiceData | lockProServiceData // Updates - lockUpdateInProgress!: boolean; - doLockUpdate!: Subject; + lockUpdateInProgress!: boolean + doLockUpdate!: Subject constructor( readonly platform: SwitchBotPlatform, accessory: PlatformAccessory, device: device & devicesConfig, ) { - super(platform, accessory, device); + super(platform, accessory, device) // Set category - accessory.category = this.hap.Categories.DOOR_LOCK; + accessory.category = this.hap.Categories.DOOR_LOCK // this is subject we use to track when we need to POST changes to the SwitchBot API - this.doLockUpdate = new Subject(); - this.lockUpdateInProgress = false; + this.doLockUpdate = new Subject() + this.lockUpdateInProgress = false // Initialize LockMechanism Service - accessory.context.LockMechanism = accessory.context.LockMechanism ?? {}; + accessory.context.LockMechanism = accessory.context.LockMechanism ?? {} this.LockMechanism = { Name: accessory.displayName, Service: accessory.getService(this.hap.Service.LockMechanism) ?? accessory.addService(this.hap.Service.LockMechanism) as Service, LockTargetState: accessory.context.LockTargetState ?? this.hap.Characteristic.LockTargetState.SECURED, LockCurrentState: accessory.context.LockCurrentState ?? this.hap.Characteristic.LockCurrentState.SECURED, - }; - accessory.context.LockMechanism = this.LockMechanism as object; + } + accessory.context.LockMechanism = this.LockMechanism as object // Initialize LockMechanism Characteristics - this.LockMechanism.Service - .setCharacteristic(this.hap.Characteristic.Name, this.LockMechanism.Name) - .getCharacteristic(this.hap.Characteristic.LockTargetState) - .onGet(() => { - return this.LockMechanism.LockTargetState; - }) - .onSet(this.LockTargetStateSet.bind(this)); + this.LockMechanism.Service.setCharacteristic(this.hap.Characteristic.Name, this.LockMechanism.Name).getCharacteristic(this.hap.Characteristic.LockTargetState).onGet(() => { + return this.LockMechanism.LockTargetState + }).onSet(this.LockTargetStateSet.bind(this)) // Initialize Battery property - accessory.context.Battery = accessory.context.Battery ?? {}; + accessory.context.Battery = accessory.context.Battery ?? {} this.Battery = { Name: `${accessory.displayName} Battery`, Service: accessory.getService(this.hap.Service.Battery) ?? accessory.addService(this.hap.Service.Battery) as Service, BatteryLevel: accessory.context.BatteryLevel ?? 100, StatusLowBattery: accessory.context.StatusLowBattery ?? this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL, - }; - accessory.context.Battery = this.Battery as object; + } + accessory.context.Battery = this.Battery as object // Initialize Battery Characteristics - this.Battery.Service - .setCharacteristic(this.hap.Characteristic.Name, this.Battery.Name) - .setCharacteristic(this.hap.Characteristic.ChargingState, this.hap.Characteristic.ChargingState.NOT_CHARGEABLE) - .getCharacteristic(this.hap.Characteristic.BatteryLevel) - .onGet(() => { - return this.Battery.BatteryLevel; - }); - - this.Battery.Service - .getCharacteristic(this.hap.Characteristic.StatusLowBattery) - .onGet(() => { - return this.Battery.StatusLowBattery; - }); + this.Battery.Service.setCharacteristic(this.hap.Characteristic.Name, this.Battery.Name).setCharacteristic(this.hap.Characteristic.ChargingState, this.hap.Characteristic.ChargingState.NOT_CHARGEABLE).getCharacteristic(this.hap.Characteristic.BatteryLevel).onGet(() => { + return this.Battery.BatteryLevel + }) + + this.Battery.Service.getCharacteristic(this.hap.Characteristic.StatusLowBattery).onGet(() => { + return this.Battery.StatusLowBattery + }) // Contact Sensor Service if (device.lock?.hide_contactsensor) { if (this.ContactSensor) { - this.debugLog('Removing Contact Sensor Service'); - this.ContactSensor.Service = this.accessory.getService(this.hap.Service.ContactSensor) as Service; - accessory.removeService(this.ContactSensor.Service); + this.debugLog('Removing Contact Sensor Service') + this.ContactSensor.Service = this.accessory.getService(this.hap.Service.ContactSensor) as Service + accessory.removeService(this.ContactSensor.Service) } } else { - accessory.context.ContactSensor = accessory.context.ContactSensor ?? {}; + accessory.context.ContactSensor = accessory.context.ContactSensor ?? {} this.ContactSensor = { Name: `${accessory.displayName} Contact Sensor`, Service: accessory.getService(this.hap.Service.ContactSensor) ?? this.accessory.addService(this.hap.Service.ContactSensor) as Service, ContactSensorState: accessory.context.ContactSensorState ?? this.hap.Characteristic.ContactSensorState.CONTACT_DETECTED, - }; - accessory.context.ContactSensor = this.ContactSensor as object; + } + accessory.context.ContactSensor = this.ContactSensor as object // Initialize Contact Sensor Characteristics - this.ContactSensor.Service - .setCharacteristic(this.hap.Characteristic.Name, this.ContactSensor.Name) - .setCharacteristic(this.hap.Characteristic.StatusActive, true) - .getCharacteristic(this.hap.Characteristic.ContactSensorState) - .onGet(() => { - return this.ContactSensor!.ContactSensorState; - }); + this.ContactSensor.Service.setCharacteristic(this.hap.Characteristic.Name, this.ContactSensor.Name).setCharacteristic(this.hap.Characteristic.StatusActive, true).getCharacteristic(this.hap.Characteristic.ContactSensorState).onGet(() => { + return this.ContactSensor!.ContactSensorState + }) } // Initialize Latch Button Service if (device.lock?.activate_latchbutton === false) { if (this.Switch) { - this.debugLog('Removing Latch Button Service'); - this.Switch.Service = accessory.getService(this.hap.Service.Switch) as Service; - accessory.removeService(this.Switch.Service); + this.debugLog('Removing Latch Button Service') + this.Switch.Service = accessory.getService(this.hap.Service.Switch) as Service + accessory.removeService(this.Switch.Service) } } else { - accessory.context.Switch = accessory.context.Switch ?? {}; + accessory.context.Switch = accessory.context.Switch ?? {} this.Switch = { Name: `${accessory.displayName} Latch`, Service: accessory.getService(this.hap.Service.Switch) ?? accessory.addService(this.hap.Service.Switch) as Service, On: accessory.context.On ?? false, - }; - accessory.context.Switch = this.Switch as object; + } + accessory.context.Switch = this.Switch as object // Initialize Latch Button Characteristics - this.Switch.Service - .setCharacteristic(this.hap.Characteristic.Name, this.Switch.Name) - .getCharacteristic(this.hap.Characteristic.On) - .onGet(() => { - return this.Switch!.On; - }) - .onSet(this.OnSet.bind(this)); + this.Switch.Service.setCharacteristic(this.hap.Characteristic.Name, this.Switch.Name).getCharacteristic(this.hap.Characteristic.On).onGet(() => { + return this.Switch!.On + }).onSet(this.OnSet.bind(this)) } // Retrieve initial values and updateHomekit - this.debugLog('Retrieve initial values and update Homekit'); - this.refreshStatus(); + try { + this.debugLog('Retrieve initial values and update Homekit') + this.refreshStatus() + } catch (e: any) { + this.errorLog(`failed to retrieve initial values and update Homekit, Error: ${e}`) + } - //regisiter webhook event handler - this.debugLog('Registering Webhook Event Handler'); - this.registerWebhook(); + // regisiter webhook event handler if enabled + try { + this.debugLog('Registering Webhook Event Handler') + this.registerWebhook() + } catch (e: any) { + this.errorLog(`failed to registerWebhook, Error: ${e}`) + } + + // regisiter platform BLE event handler if enabled + try { + this.debugLog('Registering Platform BLE Event Handler') + this.registerPlatformBLE() + } catch (e: any) { + this.errorLog(`failed to registerPlatformBLE, Error: ${e}`) + } // Start an update interval interval(this.deviceRefreshRate * 1000) .pipe(skipWhile(() => this.lockUpdateInProgress)) .subscribe(async () => { - await this.refreshStatus(); - }); + await this.refreshStatus() + }) // Watch for Lock change events // We put in a debounce of 100ms so we don't make duplicate calls this.doLockUpdate .pipe( tap(() => { - this.lockUpdateInProgress = true; + this.lockUpdateInProgress = true }), debounceTime(this.devicePushRate * 1000), ) .subscribe(async () => { try { - await this.pushChanges(); + await this.pushChanges() } catch (e: any) { - await this.apiError(e); - await this.errorLog(`failed pushChanges with ${device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`); + await this.apiError(e) + await this.errorLog(`failed pushChanges with ${device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`) } - this.lockUpdateInProgress = false; - }); + this.lockUpdateInProgress = false + }) } async BLEparseStatus(): Promise { - await this.debugLog('BLEparseStatus'); - await this.debugLog(`(lockState) = BLE:(${this.serviceData.status}), current:(${this.LockMechanism.LockCurrentState})`); + await this.debugLog('BLEparseStatus') + await this.debugLog(`(lockState) = BLE:(${this.serviceData.status}), current:(${this.LockMechanism.LockCurrentState})`) // LockCurrentState this.LockMechanism.LockCurrentState = this.serviceData.status === 'locked' - ? this.hap.Characteristic.LockCurrentState.SECURED : this.hap.Characteristic.LockCurrentState.UNSECURED; - await this.debugLog(`LockCurrentState: ${this.LockMechanism.LockCurrentState}`); + ? this.hap.Characteristic.LockCurrentState.SECURED + : this.hap.Characteristic.LockCurrentState.UNSECURED + await this.debugLog(`LockCurrentState: ${this.LockMechanism.LockCurrentState}`) // LockTargetState this.LockMechanism.LockTargetState = this.serviceData.status === 'locked' - ? this.hap.Characteristic.LockTargetState.SECURED : this.hap.Characteristic.LockTargetState.UNSECURED; - await this.debugLog(`LockTargetState: ${this.LockMechanism.LockTargetState}`); + ? this.hap.Characteristic.LockTargetState.SECURED + : this.hap.Characteristic.LockTargetState.UNSECURED + await this.debugLog(`LockTargetState: ${this.LockMechanism.LockTargetState}`) // Contact Sensor if (!this.device.lock?.hide_contactsensor && this.ContactSensor?.Service) { this.ContactSensor.ContactSensorState = this.serviceData.door_open === 'opened' - ? this.hap.Characteristic.ContactSensorState.CONTACT_NOT_DETECTED : this.hap.Characteristic.ContactSensorState.CONTACT_DETECTED; - await this.debugLog(`ContactSensorState: ${this.ContactSensor.ContactSensorState}`); + ? this.hap.Characteristic.ContactSensorState.CONTACT_NOT_DETECTED + : this.hap.Characteristic.ContactSensorState.CONTACT_DETECTED + await this.debugLog(`ContactSensorState: ${this.ContactSensor.ContactSensorState}`) } // BatteryLevel - this.Battery.BatteryLevel = this.serviceData.battery; - await this.debugLog(`BatteryLevel: ${this.Battery.BatteryLevel}`); + this.Battery.BatteryLevel = this.serviceData.battery + await this.debugLog(`BatteryLevel: ${this.Battery.BatteryLevel}`) // StatusLowBattery this.Battery.StatusLowBattery = this.Battery.BatteryLevel < 10 - ? this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW : this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL; - await this.debugLog(`StatusLowBattery: ${this.Battery.StatusLowBattery}`); + ? this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW + : this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL + await this.debugLog(`StatusLowBattery: ${this.Battery.StatusLowBattery}`) } async openAPIparseStatus(): Promise { - await this.debugLog('openAPIparseStatus'); - await this.debugLog(`(lockState) = OpenAPI:(${this.deviceStatus.lockState}), current:(${this.LockMechanism.LockCurrentState})`); + await this.debugLog('openAPIparseStatus') + await this.debugLog(`(lockState) = OpenAPI:(${this.deviceStatus.lockState}), current:(${this.LockMechanism.LockCurrentState})`) // LockCurrentState this.LockMechanism.LockCurrentState = this.deviceStatus.lockState === 'locked' - ? this.hap.Characteristic.LockCurrentState.SECURED : this.hap.Characteristic.LockCurrentState.UNSECURED; - await this.debugLog(`LockCurrentState: ${this.LockMechanism.LockCurrentState}`); + ? this.hap.Characteristic.LockCurrentState.SECURED + : this.hap.Characteristic.LockCurrentState.UNSECURED + await this.debugLog(`LockCurrentState: ${this.LockMechanism.LockCurrentState}`) // LockTargetState this.LockMechanism.LockTargetState = this.deviceStatus.lockState === 'locked' - ? this.hap.Characteristic.LockTargetState.SECURED : this.hap.Characteristic.LockTargetState.UNSECURED; - await this.debugLog(`LockTargetState: ${this.LockMechanism.LockTargetState}`); + ? this.hap.Characteristic.LockTargetState.SECURED + : this.hap.Characteristic.LockTargetState.UNSECURED + await this.debugLog(`LockTargetState: ${this.LockMechanism.LockTargetState}`) // ContactSensorState if (!this.device.lock?.hide_contactsensor && this.ContactSensor?.Service) { this.ContactSensor.ContactSensorState = this.deviceStatus.doorState === 'opened' - ? this.hap.Characteristic.ContactSensorState.CONTACT_NOT_DETECTED : this.hap.Characteristic.ContactSensorState.CONTACT_DETECTED; - await this.debugLog(`ContactSensorState: ${this.ContactSensor.ContactSensorState}`); + ? this.hap.Characteristic.ContactSensorState.CONTACT_NOT_DETECTED + : this.hap.Characteristic.ContactSensorState.CONTACT_DETECTED + await this.debugLog(`ContactSensorState: ${this.ContactSensor.ContactSensorState}`) } // BatteryLevel - this.Battery.BatteryLevel = this.deviceStatus.battery; - await this.debugLog(`BatteryLevel: ${this.Battery.BatteryLevel}`); + this.Battery.BatteryLevel = this.deviceStatus.battery + await this.debugLog(`BatteryLevel: ${this.Battery.BatteryLevel}`) // StatusLowBattery this.Battery.StatusLowBattery = this.Battery.BatteryLevel < 10 - ? this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW : this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL; - await this.debugLog(`StatusLowBattery: ${this.Battery.StatusLowBattery}`); + ? this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW + : this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL + await this.debugLog(`StatusLowBattery: ${this.Battery.StatusLowBattery}`) // Firmware Version if (this.deviceStatus.version) { - const version = this.deviceStatus.version.toString(); - await this.debugLog(`Firmware Version: ${version.replace(/^V|-.*$/g, '')}`); - const deviceVersion = version.replace(/^V|-.*$/g, '') ?? '0.0.0'; + const version = this.deviceStatus.version.toString() + await this.debugLog(`Firmware Version: ${version.replace(/^V|-.*$/g, '')}`) + const deviceVersion = version.replace(/^V|-.*$/g, '') ?? '0.0.0' this.accessory .getService(this.hap.Service.AccessoryInformation)! .setCharacteristic(this.hap.Characteristic.HardwareRevision, deviceVersion) .setCharacteristic(this.hap.Characteristic.FirmwareRevision, deviceVersion) .getCharacteristic(this.hap.Characteristic.FirmwareRevision) - .updateValue(deviceVersion); - this.accessory.context.version = deviceVersion; - await this.debugSuccessLog(`version: ${this.accessory.context.version}`); + .updateValue(deviceVersion) + this.accessory.context.version = deviceVersion + await this.debugSuccessLog(`version: ${this.accessory.context.version}`) } } async parseStatusWebhook(): Promise { - await this.debugLog('parseStatusWebhook'); - await this.debugLog(`(lockState) = Webhook:(${this.webhookContext.lockState}), current:(${this.LockMechanism.LockCurrentState})`); + await this.debugLog('parseStatusWebhook') + await this.debugLog(`(lockState) = Webhook:(${this.webhookContext.lockState}), current:(${this.LockMechanism.LockCurrentState})`) // LockCurrentState this.LockMechanism.LockCurrentState = this.webhookContext.lockState === 'LOCKED' - ? this.hap.Characteristic.LockCurrentState.SECURED : this.hap.Characteristic.LockCurrentState.UNSECURED; - await this.debugLog(`LockCurrentState: ${this.LockMechanism.LockCurrentState}`); + ? this.hap.Characteristic.LockCurrentState.SECURED + : this.hap.Characteristic.LockCurrentState.UNSECURED + await this.debugLog(`LockCurrentState: ${this.LockMechanism.LockCurrentState}`) // LockTargetState this.LockMechanism.LockTargetState = this.webhookContext.lockState === 'LOCKED' - ? this.hap.Characteristic.LockTargetState.SECURED : this.hap.Characteristic.LockTargetState.UNSECURED; - await this.debugLog(`LockTargetState: ${this.LockMechanism.LockTargetState}`); + ? this.hap.Characteristic.LockTargetState.SECURED + : this.hap.Characteristic.LockTargetState.UNSECURED + await this.debugLog(`LockTargetState: ${this.LockMechanism.LockTargetState}`) } /** @@ -296,128 +310,132 @@ export class Lock extends deviceBase { */ async refreshStatus(): Promise { if (!this.device.enableCloudService && this.OpenAPI) { - await this.errorLog(`refreshStatus enableCloudService: ${this.device.enableCloudService}`); - } else if (this.BLE || this.config.options?.BLE) { - await this.BLERefreshStatus(); + await this.errorLog(`refreshStatus enableCloudService: ${this.device.enableCloudService}`) + } else if (this.BLE) { + await this.BLERefreshStatus() } else if (this.OpenAPI && this.platform.config.credentials?.token) { - await this.openAPIRefreshStatus(); + await this.openAPIRefreshStatus() } else { - await this.offlineOff(); - await this.debugWarnLog(`Connection Type: ${this.device.connectionType}, refreshStatus will not happen.`); + await this.offlineOff() + await this.debugWarnLog(`Connection Type: ${this.device.connectionType}, refreshStatus will not happen.`) } } async BLERefreshStatus(): Promise { - await this.debugLog('BLERefreshStatus'); + await this.debugLog('BLERefreshStatus') + const switchbot = await this.switchbotBLE() + if (switchbot === undefined) { + await this.BLERefreshConnection(switchbot) + } else { + // Start to monitor advertisement packets + (async () => { + // Start to monitor advertisement packets + const serviceData = await this.monitorAdvertisementPackets(switchbot) as lockServiceData | lockProServiceData + // Update HomeKit + if ((serviceData.model === SwitchBotBLEModel.Lock || SwitchBotBLEModel.LockPro) + && (serviceData.modelName === SwitchBotBLEModelName.Lock || SwitchBotBLEModelName.LockPro)) { + this.serviceData = serviceData + await this.BLEparseStatus() + await this.updateHomeKitCharacteristics() + } else { + await this.errorLog(`failed to get serviceData, serviceData: ${serviceData}`) + await this.BLERefreshConnection(switchbot) + } + })() + } + } + + async registerPlatformBLE(): Promise { + await this.debugLog('registerPlatformBLE') if (this.config.options?.BLE) { - await this.debugLog('is listening to Platform BLE.'); - this.device.bleMac = this.device.deviceId!.match(/.{1,2}/g)!.join(':').toLowerCase(); - await this.debugLog(`bleMac: ${this.device.bleMac}`); + await this.debugLog('is listening to Platform BLE.') + this.device.bleMac = this.device.deviceId!.match(/.{1,2}/g)!.join(':').toLowerCase() + await this.debugLog(`bleMac: ${this.device.bleMac}`) this.platform.bleEventHandler[this.device.bleMac] = async (context: lockServiceData | lockProServiceData) => { try { - await this.debugLog(`received BLE: ${JSON.stringify(context)}`); - this.serviceData = context; - await this.BLEparseStatus(); - await this.updateHomeKitCharacteristics(); + await this.debugLog(`received BLE: ${JSON.stringify(context)}`) + this.serviceData = context + await this.BLEparseStatus() + await this.updateHomeKitCharacteristics() } catch (e: any) { - await this.errorLog(`failed to handle BLE. Received: ${JSON.stringify(context)} Error: ${e}`); + await this.errorLog(`failed to handle BLE. Received: ${JSON.stringify(context)} Error: ${e}`) } - }; - } else { - await this.debugLog('is using Device BLE Scanning.'); - const switchbot = await this.switchbotBLE(); - if (switchbot === undefined) { - await this.BLERefreshConnection(switchbot); - } else { - // Start to monitor advertisement packets - (async () => { - // Start to monitor advertisement packets - const serviceData = await this.monitorAdvertisementPackets(switchbot) as lockServiceData | lockProServiceData; - // Update HomeKit - if ((serviceData.model === SwitchBotBLEModel.Lock || SwitchBotBLEModel.LockPro) - && (serviceData.modelName === SwitchBotBLEModelName.Lock || SwitchBotBLEModelName.LockPro)) { - this.serviceData = serviceData; - await this.BLEparseStatus(); - await this.updateHomeKitCharacteristics(); - } else { - await this.errorLog(`failed to get serviceData, serviceData: ${serviceData}`); - await this.BLERefreshConnection(switchbot); - } - })(); } + } else { + await this.debugLog('is not listening to Platform BLE') } } async openAPIRefreshStatus(): Promise { - await this.debugLog('openAPIRefreshStatus'); + await this.debugLog('openAPIRefreshStatus') try { - const { body, statusCode } = await this.deviceRefreshStatus(); - const deviceStatus: any = await body.json(); - await this.debugLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`);; + const { body, statusCode } = await this.deviceRefreshStatus() + const deviceStatus: any = await body.json() + await this.debugLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`) if (await this.successfulStatusCodes(statusCode, deviceStatus)) { - await this.debugSuccessLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`); - this.deviceStatus = deviceStatus.body; - await this.openAPIparseStatus(); - await this.updateHomeKitCharacteristics(); + await this.debugSuccessLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`) + this.deviceStatus = deviceStatus.body + await this.openAPIparseStatus() + await this.updateHomeKitCharacteristics() } else { - await this.debugWarnLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`); - await this.debugWarnLog(statusCode, deviceStatus); + await this.debugWarnLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`) + await this.debugWarnLog(statusCode, deviceStatus) } } catch (e: any) { - await this.apiError(e); - await this.errorLog(`failed openAPIRefreshStatus with ${this.device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`); + await this.apiError(e) + await this.errorLog(`failed openAPIRefreshStatus with ${this.device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`) } } async registerWebhook() { if (this.device.webhook) { - await this.debugLog('is listening webhook.'); + await this.debugLog('is listening webhook.') this.platform.webhookEventHandler[this.device.deviceId] = async (context: lockWebhookContext | lockProWebhookContext) => { try { - await this.debugLog(`received Webhook: ${JSON.stringify(context)}`); - this.webhookContext = context; - await this.parseStatusWebhook(); - await this.updateHomeKitCharacteristics(); + await this.debugLog(`received Webhook: ${JSON.stringify(context)}`) + this.webhookContext = context + await this.parseStatusWebhook() + await this.updateHomeKitCharacteristics() } catch (e: any) { - await this.errorLog(`failed to handle webhook. Received: ${JSON.stringify(context)} Error: ${e}`); + await this.errorLog(`failed to handle webhook. Received: ${JSON.stringify(context)} Error: ${e}`) } - }; + } } else { - await this.debugLog('is not listening webhook.'); + await this.debugLog('is not listening webhook.') } } /** * Pushes the requested changes to the SwitchBot API - * deviceType commandType Command command parameter Description - * Lock - "command" "lock" "default" = set to ???? state - * Lock - "command" "unlock" "default" = set to ???? state - LockCurrentState + * deviceType commandType Command command parameter Description + * Lock - "command" "lock" "default" = set to ???? state + * Lock - "command" "unlock" "default" = set to ???? state - LockCurrentState */ async pushChanges(): Promise { if (!this.device.enableCloudService && this.OpenAPI) { - await this.errorLog(`pushChanges enableCloudService: ${this.device.enableCloudService}`); + await this.errorLog(`pushChanges enableCloudService: ${this.device.enableCloudService}`) } else if (this.BLE) { - await this.BLEpushChanges(); + await this.BLEpushChanges() } else if (this.OpenAPI && this.platform.config.credentials?.token) { - await this.openAPIpushChanges(); + await this.openAPIpushChanges() } else { - await this.offlineOff(); - await this.debugWarnLog(`Connection Type: ${this.device.connectionType}, pushChanges will not happen.`); + await this.offlineOff() + await this.debugWarnLog(`Connection Type: ${this.device.connectionType}, pushChanges will not happen.`) } // Refresh the status from the API interval(15000) .pipe(skipWhile(() => this.lockUpdateInProgress)) .pipe(take(1)) .subscribe(async () => { - await this.refreshStatus(); - }); + await this.refreshStatus() + }) } async BLEpushChanges(): Promise { - await this.debugLog('BLEpushChanges'); + await this.debugLog('BLEpushChanges') if (this.LockMechanism.LockTargetState !== this.accessory.context.LockTargetState) { - const switchbot = await this.platform.connectBLE(this.accessory, this.device); - await this.convertBLEAddress(); + const switchbot = await this.platform.connectBLE(this.accessory, this.device) + await this.convertBLEAddress() if (switchbot !== false) { switchbot .discover({ model: this.device.bleModel, id: this.device.bleMac }) @@ -426,61 +444,59 @@ export class Lock extends deviceBase { max: await this.maxRetryBLE(), fn: async () => { if (this.LockMechanism.LockTargetState === this.hap.Characteristic.LockTargetState.SECURED) { - return await device_list[0].lock({ id: this.device.bleMac }); + return await device_list[0].lock({ id: this.device.bleMac }) } else { - return await device_list[0].unlock({ id: this.device.bleMac }); + return await device_list[0].unlock({ id: this.device.bleMac }) } }, - }); + }) }) .then(async () => { - await this.successLog(`LockTargetState: ${this.LockMechanism.LockTargetState} sent over SwitchBot BLE, sent successfully`); - await this.updateHomeKitCharacteristics(); + await this.successLog(`LockTargetState: ${this.LockMechanism.LockTargetState} sent over SwitchBot BLE, sent successfully`) + await this.updateHomeKitCharacteristics() }) .catch(async (e: any) => { - await this.apiError(e); - await this.errorLog(`failed BLEpushChanges with ${this.device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`); - await this.BLEPushConnection(); - }); + await this.apiError(e) + await this.errorLog(`failed BLEpushChanges with ${this.device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`) + await this.BLEPushConnection() + }) } else { - await this.errorLog(`wasn't able to establish BLE Connection, node-switchbot: ${switchbot}`); - await this.BLEPushConnection(); + await this.errorLog(`wasn't able to establish BLE Connection, node-switchbot: ${switchbot}`) + await this.BLEPushConnection() } } else { - await this.debugLog(`No changes (BLEpushChanges), LockTargetState: ${this.LockMechanism.LockTargetState},` - + ` LockCurrentState: ${this.LockMechanism.LockCurrentState}`); + await this.debugLog(`No changes (BLEpushChanges), LockTargetState: ${this.LockMechanism.LockTargetState}, LockCurrentState: ${this.LockMechanism.LockCurrentState}`) } } async openAPIpushChanges(LatchUnlock?: boolean): Promise { - await this.debugLog('openAPIpushChanges'); + await this.debugLog('openAPIpushChanges') if ((this.LockMechanism.LockTargetState !== this.accessory.context.LockTargetState) || LatchUnlock) { // Determine the command based on the LockTargetState or the forceUnlock parameter - const command = LatchUnlock ? 'unlock' : this.LockMechanism.LockTargetState ? 'lock' : 'unlock'; + const command = LatchUnlock ? 'unlock' : this.LockMechanism.LockTargetState ? 'lock' : 'unlock' const bodyChange = JSON.stringify({ command: `${command}`, parameter: 'default', commandType: 'command', - }); - await this.debugLog(`SwitchBot OpenAPI bodyChange: ${JSON.stringify(bodyChange)}`); + }) + await this.debugLog(`SwitchBot OpenAPI bodyChange: ${JSON.stringify(bodyChange)}`) try { - const { body, statusCode } = await this.pushChangeRequest(bodyChange); - const deviceStatus: any = await body.json(); - await this.debugLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`); + const { body, statusCode } = await this.pushChangeRequest(bodyChange) + const deviceStatus: any = await body.json() + await this.debugLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`) if (await this.successfulStatusCodes(statusCode, deviceStatus)) { - await this.debugSuccessLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`); - await this.updateHomeKitCharacteristics(); + await this.debugSuccessLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`) + await this.updateHomeKitCharacteristics() } else { - await this.statusCode(statusCode); - await this.statusCode(deviceStatus.statusCode); + await this.statusCode(statusCode) + await this.statusCode(deviceStatus.statusCode) } } catch (e: any) { - await this.apiError(e); - await this.errorLog(`failed openAPIpushChanges with ${this.device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`); + await this.apiError(e) + await this.errorLog(`failed openAPIpushChanges with ${this.device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`) } } else { - await this.debugLog(`No changes (openAPIpushChanges), LockCurrentState: ${this.LockMechanism.LockCurrentState},` - + ` TargetPosition: ${this.LockMechanism.LockTargetState}`); + await this.debugLog(`No changes (openAPIpushChanges), LockCurrentState: ${this.LockMechanism.LockCurrentState}, TargetPosition: ${this.LockMechanism.LockTargetState}`) } } @@ -489,106 +505,100 @@ export class Lock extends deviceBase { */ async LockTargetStateSet(value: CharacteristicValue): Promise { if (this.LockMechanism.LockTargetState !== this.accessory.context.LockTargetState) { - await this.infoLog(`Set LockTargetState: ${value}`); + await this.infoLog(`Set LockTargetState: ${value}`) } else { - await this.debugLog(`No Changes, LockTargetState: ${value}`); + await this.debugLog(`No Changes, LockTargetState: ${value}`) } - this.LockMechanism.LockTargetState = value; - this.doLockUpdate.next(); + this.LockMechanism.LockTargetState = value + this.doLockUpdate.next() } /** * Handle requests to set the value of the "On" characteristic */ async OnSet(value: CharacteristicValue): Promise { - await this.debugLog(`Latch Button Set On: ${value}`); + await this.debugLog(`Latch Button Set On: ${value}`) if (value) { - await this.debugLog('Attempting to open the latch'); + await this.debugLog('Attempting to open the latch') this.openAPIpushChanges(value as boolean).then(async () => { - await this.debugLog('Latch opened successfully'); - await this.debugLog(`SwitchService is: ${this.Switch?.Service ? 'available' : 'not available'}`); + await this.debugLog('Latch opened successfully') + await this.debugLog(`SwitchService is: ${this.Switch?.Service ? 'available' : 'not available'}`) // simulate button press to turn the switch back off if (this.Switch?.Service) { - const SwitchService = this.Switch.Service; + const SwitchService = this.Switch.Service // Simulate a button press by waiting a short period before turning the switch off - setTimeout(async() => { - SwitchService.getCharacteristic(this.hap.Characteristic.On).updateValue(false); - await this.debugLog('Latch button switched off automatically.'); - }, 500); // 500 ms delay + setTimeout(async () => { + SwitchService.getCharacteristic(this.hap.Characteristic.On).updateValue(false) + await this.debugLog('Latch button switched off automatically.') + }, 500) // 500 ms delay } }).catch(async (e: any) => { // Log the error if the operation failed - await this.debugLog(`Error opening latch: ${e}`); + await this.debugLog(`Error opening latch: ${e}`) // Ensure we turn the switch back off even in case of an error if (this.Switch?.Service) { - this.Switch.Service.getCharacteristic(this.hap.Characteristic.On).updateValue(false); - await this.debugLog('Latch button switched off after an error.'); + this.Switch.Service.getCharacteristic(this.hap.Characteristic.On).updateValue(false) + await this.debugLog('Latch button switched off after an error.') } - }); + }) } else { - await this.debugLog('Switch is off, nothing to do'); + await this.debugLog('Switch is off, nothing to do') } - this.Switch!.On = value; - this.doLockUpdate.next(); + this.Switch!.On = value + this.doLockUpdate.next() } async updateHomeKitCharacteristics(): Promise { // LockCurrentState - await this.updateCharacteristic(this.LockMechanism.Service, this.hap.Characteristic.LockTargetState, - this.LockMechanism.LockTargetState, 'LockTargetState'); + await this.updateCharacteristic(this.LockMechanism.Service, this.hap.Characteristic.LockTargetState, this.LockMechanism.LockTargetState, 'LockTargetState') // LockCurrentState - await this.updateCharacteristic(this.LockMechanism.Service, this.hap.Characteristic.LockCurrentState, - this.LockMechanism.LockCurrentState, 'LockCurrentState'); + await this.updateCharacteristic(this.LockMechanism.Service, this.hap.Characteristic.LockCurrentState, this.LockMechanism.LockCurrentState, 'LockCurrentState') // ContactSensorState if (!this.device.lock?.hide_contactsensor && this.ContactSensor?.Service) { - await this.updateCharacteristic(this.ContactSensor.Service, this.hap.Characteristic.ContactSensorState, - this.ContactSensor.ContactSensorState, 'ContactSensorState'); + await this.updateCharacteristic(this.ContactSensor.Service, this.hap.Characteristic.ContactSensorState, this.ContactSensor.ContactSensorState, 'ContactSensorState') } // BatteryLevel - await this.updateCharacteristic(this.Battery.Service, this.hap.Characteristic.BatteryLevel, - this.Battery.BatteryLevel, 'BatteryLevel'); + await this.updateCharacteristic(this.Battery.Service, this.hap.Characteristic.BatteryLevel, this.Battery.BatteryLevel, 'BatteryLevel') // StatusLowBattery - await this.updateCharacteristic(this.Battery.Service, this.hap.Characteristic.StatusLowBattery, - this.Battery.StatusLowBattery, 'StatusLowBattery'); + await this.updateCharacteristic(this.Battery.Service, this.hap.Characteristic.StatusLowBattery, this.Battery.StatusLowBattery, 'StatusLowBattery') } async BLEPushConnection() { if (this.platform.config.credentials?.token && this.device.connectionType === 'BLE/OpenAPI') { - await this.warnLog('Using OpenAPI Connection to Push Changes'); - await this.openAPIpushChanges(); + await this.warnLog('Using OpenAPI Connection to Push Changes') + await this.openAPIpushChanges() } } async BLERefreshConnection(switchbot: any): Promise { - await this.errorLog(`wasn't able to establish BLE Connection, node-switchbot: ${switchbot}`); + await this.errorLog(`wasn't able to establish BLE Connection, node-switchbot: ${switchbot}`) if (this.platform.config.credentials?.token && this.device.connectionType === 'BLE/OpenAPI') { - await this.warnLog('Using OpenAPI Connection to Refresh Status'); - await this.openAPIRefreshStatus(); + await this.warnLog('Using OpenAPI Connection to Refresh Status') + await this.openAPIRefreshStatus() } } async offlineOff(): Promise { if (this.device.offline) { - this.LockMechanism.Service.updateCharacteristic(this.hap.Characteristic.LockTargetState, this.hap.Characteristic.LockTargetState.SECURED); - this.LockMechanism.Service.updateCharacteristic(this.hap.Characteristic.LockCurrentState, this.hap.Characteristic.LockCurrentState.SECURED); + this.LockMechanism.Service.updateCharacteristic(this.hap.Characteristic.LockTargetState, this.hap.Characteristic.LockTargetState.SECURED) + this.LockMechanism.Service.updateCharacteristic(this.hap.Characteristic.LockCurrentState, this.hap.Characteristic.LockCurrentState.SECURED) if (!this.device.lock?.hide_contactsensor && this.ContactSensor?.Service) { - this.ContactSensor.Service.updateCharacteristic(this.hap.Characteristic.ContactSensorState, - this.hap.Characteristic.ContactSensorState.CONTACT_DETECTED); + this.ContactSensor.Service.updateCharacteristic(this.hap.Characteristic.ContactSensorState, this.hap.Characteristic.ContactSensorState.CONTACT_DETECTED) } } } async apiError(e: any): Promise { - this.LockMechanism.Service.updateCharacteristic(this.hap.Characteristic.LockTargetState, e); - this.LockMechanism.Service.updateCharacteristic(this.hap.Characteristic.LockCurrentState, e); + this.LockMechanism.Service.updateCharacteristic(this.hap.Characteristic.LockTargetState, e) + this.LockMechanism.Service.updateCharacteristic(this.hap.Characteristic.LockCurrentState, e) if (!this.device.lock?.hide_contactsensor && this.ContactSensor?.Service) { - this.ContactSensor.Service.updateCharacteristic(this.hap.Characteristic.ContactSensorState, e); + this.ContactSensor.Service.updateCharacteristic(this.hap.Characteristic.ContactSensorState, e) } - this.Battery.Service.updateCharacteristic(this.hap.Characteristic.BatteryLevel, e); - this.Battery.Service.updateCharacteristic(this.hap.Characteristic.StatusLowBattery, e); + this.Battery.Service.updateCharacteristic(this.hap.Characteristic.BatteryLevel, e) + this.Battery.Service.updateCharacteristic(this.hap.Characteristic.StatusLowBattery, e) } } diff --git a/src/device/meter.ts b/src/device/meter.ts index 67246745..c8b9b57b 100644 --- a/src/device/meter.ts +++ b/src/device/meter.ts @@ -2,260 +2,263 @@ * * meter.ts: @switchbot/homebridge-switchbot. */ -import { Units } from 'homebridge'; -import { deviceBase } from './device.js'; -import { Subject, interval, skipWhile } from 'rxjs'; -import { convertUnits, validHumidity } from '../utils.js'; -import { SwitchBotBLEModel, SwitchBotBLEModelName } from 'node-switchbot'; - -import type { devicesConfig } from '../settings.js'; -import type { device } from '../types/devicelist.js'; -import type { SwitchBotPlatform } from '../platform.js'; -import type { meterServiceData } from '../types/bledevicestatus.js'; -import type { meterStatus } from '../types/devicestatus.js'; -import type { meterWebhookContext } from '../types/devicewebhookstatus.js'; -import type { CharacteristicValue, PlatformAccessory, Service } from 'homebridge'; +import type { CharacteristicValue, PlatformAccessory, Service } from 'homebridge' + +import type { SwitchBotPlatform } from '../platform.js' +import type { devicesConfig } from '../settings.js' +import type { meterServiceData } from '../types/bledevicestatus.js' +import type { device } from '../types/devicelist.js' +import type { meterStatus } from '../types/devicestatus.js' +import type { meterWebhookContext } from '../types/devicewebhookstatus.js' + +import { Units } from 'homebridge' +/* +* For Testing Locally: +* import { SwitchBotBLEModel, SwitchBotBLEModelName } from '/Users/Shared/GitHub/OpenWonderLabs/node-switchbot/dist/index.js'; +*/ +import { SwitchBotBLEModel, SwitchBotBLEModelName } from 'node-switchbot' +import { interval, skipWhile, Subject } from 'rxjs' + +import { convertUnits, validHumidity } from '../utils.js' +import { deviceBase } from './device.js' export class Meter extends deviceBase { // Services private Battery: { - Name: CharacteristicValue; - Service: Service; - BatteryLevel: CharacteristicValue; - StatusLowBattery: CharacteristicValue; - }; + Name: CharacteristicValue + Service: Service + BatteryLevel: CharacteristicValue + StatusLowBattery: CharacteristicValue + } private HumiditySensor?: { - Name: CharacteristicValue; - Service: Service; - CurrentRelativeHumidity: CharacteristicValue; - }; + Name: CharacteristicValue + Service: Service + CurrentRelativeHumidity: CharacteristicValue + } private TemperatureSensor?: { - Name: CharacteristicValue; - Service: Service; - CurrentTemperature: CharacteristicValue; - }; + Name: CharacteristicValue + Service: Service + CurrentTemperature: CharacteristicValue + } // OpenAPI - deviceStatus!: meterStatus; + deviceStatus!: meterStatus - //Webhook - webhookContext!: meterWebhookContext; + // Webhook + webhookContext!: meterWebhookContext // BLE - serviceData!: meterServiceData; + serviceData!: meterServiceData // Updates - meterUpdateInProgress!: boolean; - doMeterUpdate: Subject; + meterUpdateInProgress!: boolean + doMeterUpdate: Subject constructor( readonly platform: SwitchBotPlatform, accessory: PlatformAccessory, device: device & devicesConfig, ) { - super(platform, accessory, device); + super(platform, accessory, device) // Set category - accessory.category = this.hap.Categories.SENSOR; + accessory.category = this.hap.Categories.SENSOR // this is subject we use to track when we need to POST changes to the SwitchBot API - this.doMeterUpdate = new Subject(); - this.meterUpdateInProgress = false; + this.doMeterUpdate = new Subject() + this.meterUpdateInProgress = false // Initialize Battery Service - accessory.context.Battery = accessory.context.Battery ?? {}; + accessory.context.Battery = accessory.context.Battery ?? {} this.Battery = { Name: `${accessory.displayName} Battery`, Service: accessory.getService(this.hap.Service.Battery) ?? accessory.addService(this.hap.Service.Battery) as Service, BatteryLevel: accessory.context.BatteryLevel ?? 100, StatusLowBattery: accessory.context.StatusLowBattery ?? this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL, - }; - accessory.context.Battery = this.Battery as object; + } + accessory.context.Battery = this.Battery as object // Initialize Battery Characteristics - this.Battery.Service - .setCharacteristic(this.hap.Characteristic.Name, this.Battery.Name) - .setCharacteristic(this.hap.Characteristic.ChargingState, this.hap.Characteristic.ChargingState.NOT_CHARGEABLE) - .getCharacteristic(this.hap.Characteristic.BatteryLevel) - .onGet(() => { - return this.Battery.BatteryLevel; - }); - - this.Battery.Service - .getCharacteristic(this.hap.Characteristic.StatusLowBattery) - .onGet(() => { - return this.Battery.StatusLowBattery; - }); + this.Battery.Service.setCharacteristic(this.hap.Characteristic.Name, this.Battery.Name).setCharacteristic(this.hap.Characteristic.ChargingState, this.hap.Characteristic.ChargingState.NOT_CHARGEABLE).getCharacteristic(this.hap.Characteristic.BatteryLevel).onGet(() => { + return this.Battery.BatteryLevel + }) + + this.Battery.Service.getCharacteristic(this.hap.Characteristic.StatusLowBattery).onGet(() => { + return this.Battery.StatusLowBattery + }) // Initialize Temperature Sensor Service if (device.meter?.hide_temperature) { if (this.TemperatureSensor) { - this.debugLog('Removing Temperature Sensor Service'); - this.TemperatureSensor.Service = this.accessory.getService(this.hap.Service.TemperatureSensor) as Service; - accessory.removeService(this.TemperatureSensor.Service); + this.debugLog('Removing Temperature Sensor Service') + this.TemperatureSensor.Service = this.accessory.getService(this.hap.Service.TemperatureSensor) as Service + accessory.removeService(this.TemperatureSensor.Service) } } else { - accessory.context.TemperatureSensor = accessory.context.TemperatureSensor ?? {}; + accessory.context.TemperatureSensor = accessory.context.TemperatureSensor ?? {} this.TemperatureSensor = { Name: `${accessory.displayName} Temperature Sensor`, Service: accessory.getService(this.hap.Service.TemperatureSensor) ?? this.accessory.addService(this.hap.Service.TemperatureSensor) as Service, CurrentTemperature: accessory.context.CurrentTemperature ?? 30, - }; - accessory.context.TemperatureSensor = this.TemperatureSensor as object; + } + accessory.context.TemperatureSensor = this.TemperatureSensor as object // Initialize Temperature Sensor Characteristics - this.TemperatureSensor.Service - .setCharacteristic(this.hap.Characteristic.Name, this.TemperatureSensor.Name) - .getCharacteristic(this.hap.Characteristic.CurrentTemperature) - .setProps({ - unit: Units['CELSIUS'], - validValueRanges: [-273.15, 100], - minValue: -273.15, - maxValue: 100, - minStep: 0.1, - }) - .onGet(() => { - return this.TemperatureSensor!.CurrentTemperature!; - }); + this.TemperatureSensor.Service.setCharacteristic(this.hap.Characteristic.Name, this.TemperatureSensor.Name).getCharacteristic(this.hap.Characteristic.CurrentTemperature).setProps({ + unit: Units.CELSIUS, + validValueRanges: [-273.15, 100], + minValue: -273.15, + maxValue: 100, + minStep: 0.1, + }).onGet(() => { + return this.TemperatureSensor!.CurrentTemperature! + }) } // Initialize Humidity Sensor Service if (device.meter?.hide_humidity) { if (this.HumiditySensor) { - this.debugLog('Removing Humidity Sensor Service'); - this.HumiditySensor.Service = this.accessory.getService(this.hap.Service.HumiditySensor) as Service; - accessory.removeService(this.HumiditySensor.Service); + this.debugLog('Removing Humidity Sensor Service') + this.HumiditySensor.Service = this.accessory.getService(this.hap.Service.HumiditySensor) as Service + accessory.removeService(this.HumiditySensor.Service) } } else { - accessory.context.HumiditySensor = accessory.context.HumiditySensor ?? {}; + accessory.context.HumiditySensor = accessory.context.HumiditySensor ?? {} this.HumiditySensor = { Name: `${accessory.displayName} Humidity Sensor`, Service: accessory.getService(this.hap.Service.HumiditySensor) ?? this.accessory.addService(this.hap.Service.HumiditySensor) as Service, CurrentRelativeHumidity: accessory.context.CurrentRelativeHumidity ?? 50, - }; - accessory.context.HumiditySensor = this.HumiditySensor as object; + } + accessory.context.HumiditySensor = this.HumiditySensor as object // Initialize Humidity Sensor Characteristics - this.HumiditySensor.Service - .setCharacteristic(this.hap.Characteristic.Name, this.HumiditySensor.Name) - .getCharacteristic(this.hap.Characteristic.CurrentRelativeHumidity) - .setProps({ - minStep: 0.1, - }) - .onGet(() => { - return this.HumiditySensor!.CurrentRelativeHumidity!; - }); + this.HumiditySensor.Service.setCharacteristic(this.hap.Characteristic.Name, this.HumiditySensor.Name).getCharacteristic(this.hap.Characteristic.CurrentRelativeHumidity).setProps({ + minStep: 0.1, + }).onGet(() => { + return this.HumiditySensor!.CurrentRelativeHumidity! + }) } // Retrieve initial values and updateHomekit - this.debugLog('Retrieve initial values and update Homekit'); - this.refreshStatus(); + try { + this.debugLog('Retrieve initial values and update Homekit') + this.refreshStatus() + } catch (e: any) { + this.errorLog(`failed to retrieve initial values and update Homekit, Error: ${e}`) + } + + // regisiter webhook event handler if enabled + try { + this.debugLog('Registering Webhook Event Handler') + this.registerWebhook() + } catch (e: any) { + this.errorLog(`failed to registerWebhook, Error: ${e}`) + } - //regisiter webhook event handler - this.debugLog('Registering Webhook Event Handler'); - this.registerWebhook(); + // regisiter platform BLE event handler if enabled + try { + this.debugLog('Registering Platform BLE Event Handler') + this.registerPlatformBLE() + } catch (e: any) { + this.errorLog(`failed to registerPlatformBLE, Error: ${e}`) + } // Start an update interval interval(this.deviceRefreshRate * 1000) .pipe(skipWhile(() => this.meterUpdateInProgress)) .subscribe(async () => { - await this.refreshStatus(); - }); + await this.refreshStatus() + }) } async BLEparseStatus(): Promise { - await this.debugLog('BLEparseStatus'); - await this.debugLog(`(scale, temperature, humidity) = BLE:(${this.serviceData.fahrenheit}, ${this.serviceData.temperature.c},` - + ` ${this.serviceData.humidity}), current:(${this.TemperatureSensor?.CurrentTemperature}, ${this.HumiditySensor?.CurrentRelativeHumidity})`); + await this.debugLog('BLEparseStatus') + await this.debugLog(`(scale, temperature, humidity) = BLE:(${this.serviceData.fahrenheit}, ${this.serviceData.temperature.c}, ${this.serviceData.humidity}), current:(${this.TemperatureSensor?.CurrentTemperature}, ${this.HumiditySensor?.CurrentRelativeHumidity})`) // CurrentRelativeHumidity if (!this.device.iosensor?.hide_humidity && this.HumiditySensor?.Service) { - this.HumiditySensor.CurrentRelativeHumidity = validHumidity(this.serviceData.humidity, 0, 100); - await this.debugLog(`CurrentRelativeHumidity: ${this.HumiditySensor.CurrentRelativeHumidity}%`); + this.HumiditySensor.CurrentRelativeHumidity = validHumidity(this.serviceData.humidity, 0, 100) + await this.debugLog(`CurrentRelativeHumidity: ${this.HumiditySensor.CurrentRelativeHumidity}%`) } // Current Temperature if (!this.device.meter?.hide_temperature && this.TemperatureSensor?.Service) { - const CELSIUS = this.serviceData.temperature.c < 0 ? 0 : this.serviceData.temperature.c > 100 ? 100 : this.serviceData.temperature.c; - this.TemperatureSensor.CurrentTemperature = CELSIUS; - await this.debugLog(`CurrentTemperature: ${this.TemperatureSensor.CurrentTemperature}°c`); + const CELSIUS = this.serviceData.temperature.c < 0 ? 0 : this.serviceData.temperature.c > 100 ? 100 : this.serviceData.temperature.c + this.TemperatureSensor.CurrentTemperature = CELSIUS + await this.debugLog(`CurrentTemperature: ${this.TemperatureSensor.CurrentTemperature}°c`) } // BatteryLevel - this.Battery.BatteryLevel = this.serviceData.battery; - await this.debugLog(`BatteryLevel: ${this.Battery.BatteryLevel}`); + this.Battery.BatteryLevel = this.serviceData.battery + await this.debugLog(`BatteryLevel: ${this.Battery.BatteryLevel}`) // StatusLowBattery this.Battery.StatusLowBattery = this.Battery.BatteryLevel < 10 - ? this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW : this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL; - await this.debugLog(`StatusLowBattery: ${this.Battery.StatusLowBattery}`); + ? this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW + : this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL + await this.debugLog(`StatusLowBattery: ${this.Battery.StatusLowBattery}`) } async openAPIparseStatus(): Promise { - await this.debugLog('openAPIparseStatus'); - await this.debugLog(`(battery, temperature, humidity) = OpenAPI:(${this.deviceStatus.battery}, ${this.deviceStatus.temperature},` - + ` ${this.deviceStatus.humidity}), current:(${this.Battery?.BatteryLevel}, ${this.TemperatureSensor?.CurrentTemperature},` - + ` ${this.HumiditySensor?.CurrentRelativeHumidity})`); + await this.debugLog('openAPIparseStatus') + await this.debugLog(`(battery, temperature, humidity) = OpenAPI:(${this.deviceStatus.battery}, ${this.deviceStatus.temperature}, ${this.deviceStatus.humidity}), current:(${this.Battery?.BatteryLevel}, ${this.TemperatureSensor?.CurrentTemperature}, ${this.HumiditySensor?.CurrentRelativeHumidity})`) // CurrentRelativeHumidity if (!this.device.meter?.hide_humidity && this.HumiditySensor?.Service) { - this.HumiditySensor.CurrentRelativeHumidity = this.deviceStatus.humidity; - await this.debugLog(`CurrentRelativeHumidity: ${this.HumiditySensor.CurrentRelativeHumidity}%`); + this.HumiditySensor.CurrentRelativeHumidity = this.deviceStatus.humidity + await this.debugLog(`CurrentRelativeHumidity: ${this.HumiditySensor.CurrentRelativeHumidity}%`) } // CurrentTemperature if (!this.device.meter?.hide_temperature && this.TemperatureSensor?.Service) { - this.TemperatureSensor.CurrentTemperature = this.deviceStatus.temperature; - await this.debugLog(`CurrentTemperature: ${this.TemperatureSensor.CurrentTemperature}°c`); + this.TemperatureSensor.CurrentTemperature = this.deviceStatus.temperature + await this.debugLog(`CurrentTemperature: ${this.TemperatureSensor.CurrentTemperature}°c`) } // BatteryLevel - this.Battery.BatteryLevel = this.deviceStatus.battery; - await this.debugLog(`BatteryLevel: ${this.Battery.BatteryLevel}`); + this.Battery.BatteryLevel = this.deviceStatus.battery + await this.debugLog(`BatteryLevel: ${this.Battery.BatteryLevel}`) // StatusLowBattery this.Battery.StatusLowBattery = this.Battery.BatteryLevel < 10 - ? this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW : this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL; - await this.debugLog(`StatusLowBattery: ${this.Battery.StatusLowBattery}`); + ? this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW + : this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL + await this.debugLog(`StatusLowBattery: ${this.Battery.StatusLowBattery}`) // FirmwareVersion if (this.deviceStatus.version) { - const version = this.deviceStatus.version.toString(); - await this.debugLog(`Firmware Version: ${version.replace(/^V|-.*$/g, '')}`); - const deviceVersion = version.replace(/^V|-.*$/g, '') ?? '0.0.0'; + const version = this.deviceStatus.version.toString() + await this.debugLog(`Firmware Version: ${version.replace(/^V|-.*$/g, '')}`) + const deviceVersion = version.replace(/^V|-.*$/g, '') ?? '0.0.0' this.accessory .getService(this.hap.Service.AccessoryInformation)! .setCharacteristic(this.hap.Characteristic.HardwareRevision, deviceVersion) .setCharacteristic(this.hap.Characteristic.FirmwareRevision, deviceVersion) .getCharacteristic(this.hap.Characteristic.FirmwareRevision) - .updateValue(deviceVersion); - this.accessory.context.version = deviceVersion; - await this.debugSuccessLog(`version: ${this.accessory.context.version}`); + .updateValue(deviceVersion) + this.accessory.context.version = deviceVersion + await this.debugSuccessLog(`version: ${this.accessory.context.version}`) } } async parseStatusWebhook(): Promise { - await this.debugLog('parseStatusWebhook'); - await this.debugLog(`(scale, temperature, humidity) = Webhook:(${this.webhookContext.scale}, ${convertUnits(this.webhookContext.temperature, - this.webhookContext.scale, this.device.meter?.convertUnitTo)}, ${this.webhookContext.humidity}),` - + ` current:(${this.TemperatureSensor?.CurrentTemperature}, ${this.HumiditySensor?.CurrentRelativeHumidity})`); + await this.debugLog('parseStatusWebhook') + await this.debugLog(`(scale, temperature, humidity) = Webhook:(${this.webhookContext.scale}, ${convertUnits(this.webhookContext.temperature, this.webhookContext.scale, this.device.meter?.convertUnitTo)}, ${this.webhookContext.humidity}), current:(${this.TemperatureSensor?.CurrentTemperature}, ${this.HumiditySensor?.CurrentRelativeHumidity})`) // Check if the scale is not CELSIUS if (this.webhookContext.scale !== 'CELSIUS' && this.device.hub?.convertUnitTo === undefined) { - await this.warnLog(`received a non-CELSIUS Webhook scale: ${this.webhookContext.scale}, Use the *convertUnitsTo* config under Hub settings,` - + ' if displaying incorrectly in HomeKit.'); + await this.warnLog(`received a non-CELSIUS Webhook scale: ${this.webhookContext.scale}, Use the *convertUnitsTo* config under Hub settings, if displaying incorrectly in HomeKit.`) } // CurrentRelativeHumidity if (!this.device.meter?.hide_humidity && this.HumiditySensor?.Service) { - this.HumiditySensor.CurrentRelativeHumidity = this.webhookContext.humidity; - await this.debugLog(`CurrentRelativeHumidity: ${this.HumiditySensor.CurrentRelativeHumidity}`); + this.HumiditySensor.CurrentRelativeHumidity = this.webhookContext.humidity + await this.debugLog(`CurrentRelativeHumidity: ${this.HumiditySensor.CurrentRelativeHumidity}`) } // CurrentTemperature if (!this.device.meter?.hide_temperature && this.TemperatureSensor?.Service) { - this.TemperatureSensor.CurrentTemperature = convertUnits(this.webhookContext.temperature, this.webhookContext.scale, - this.device.hub?.convertUnitTo); - await this.debugLog(`CurrentTemperature: ${this.TemperatureSensor.CurrentTemperature}`); + this.TemperatureSensor.CurrentTemperature = convertUnits(this.webhookContext.temperature, this.webhookContext.scale, this.device.hub?.convertUnitTo) + await this.debugLog(`CurrentTemperature: ${this.TemperatureSensor.CurrentTemperature}`) } } @@ -264,93 +267,97 @@ export class Meter extends deviceBase { */ async refreshStatus(): Promise { if (!this.device.enableCloudService && this.OpenAPI) { - await this.errorLog(`refreshStatus enableCloudService: ${this.device.enableCloudService}`); - } else if (this.BLE || this.config.options?.BLE) { - await this.BLERefreshStatus(); + await this.errorLog(`refreshStatus enableCloudService: ${this.device.enableCloudService}`) + } else if (this.BLE) { + await this.BLERefreshStatus() } else if (this.OpenAPI && this.platform.config.credentials?.token) { - await this.openAPIRefreshStatus(); + await this.openAPIRefreshStatus() } else { - await this.offlineOff(); - await this.debugWarnLog(`Connection Type: ${this.device.connectionType}, refreshStatus will not happen.`); + await this.offlineOff() + await this.debugWarnLog(`Connection Type: ${this.device.connectionType}, refreshStatus will not happen.`) } } async BLERefreshStatus(): Promise { - await this.debugLog('BLERefreshStatus'); + await this.debugLog('BLERefreshStatus') + const switchbot = await this.switchbotBLE() + if (switchbot === undefined) { + await this.BLERefreshConnection(switchbot) + } else { + // Start to monitor advertisement packets + (async () => { + // Start to monitor advertisement packets + const serviceData = await this.monitorAdvertisementPackets(switchbot) as meterServiceData + // Update HomeKit + if (serviceData.model === SwitchBotBLEModel.Meter && serviceData.modelName === SwitchBotBLEModelName.Meter) { + this.serviceData = serviceData + await this.BLEparseStatus() + await this.updateHomeKitCharacteristics() + } else { + await this.errorLog(`failed to get serviceData, serviceData: ${serviceData}`) + await this.BLERefreshConnection(switchbot) + } + })() + } + } + + async registerPlatformBLE(): Promise { + await this.debugLog('registerPlatformBLE') if (this.config.options?.BLE) { - await this.debugLog('is listening to Platform BLE.'); - this.device.bleMac = this.device.deviceId!.match(/.{1,2}/g)!.join(':').toLowerCase(); - await this.debugLog(`bleMac: ${this.device.bleMac}`); + await this.debugLog('is listening to Platform BLE.') + this.device.bleMac = this.device.deviceId!.match(/.{1,2}/g)!.join(':').toLowerCase() + await this.debugLog(`bleMac: ${this.device.bleMac}`) this.platform.bleEventHandler[this.device.bleMac] = async (context: meterServiceData) => { try { - await this.debugLog(`received BLE: ${JSON.stringify(context)}`); - this.serviceData = context; - await this.BLEparseStatus(); - await this.updateHomeKitCharacteristics(); + await this.debugLog(`received BLE: ${JSON.stringify(context)}`) + this.serviceData = context + await this.BLEparseStatus() + await this.updateHomeKitCharacteristics() } catch (e: any) { - await this.errorLog(`failed to handle BLE. Received: ${JSON.stringify(context)} Error: ${e}`); + await this.errorLog(`failed to handle BLE. Received: ${JSON.stringify(context)} Error: ${e}`) } - }; - } else { - await this.debugLog('is using Device BLE Scanning.'); - const switchbot = await this.switchbotBLE(); - if (switchbot === undefined) { - await this.BLERefreshConnection(switchbot); - } else { - // Start to monitor advertisement packets - (async () => { - // Start to monitor advertisement packets - const serviceData = await this.monitorAdvertisementPackets(switchbot) as meterServiceData; - // Update HomeKit - if (serviceData.model === SwitchBotBLEModel.Meter && serviceData.modelName === SwitchBotBLEModelName.Meter) { - this.serviceData = serviceData; - await this.BLEparseStatus(); - await this.updateHomeKitCharacteristics(); - } else { - await this.errorLog(`failed to get serviceData, serviceData: ${serviceData}`); - await this.BLERefreshConnection(switchbot); - } - })(); } + } else { + await this.debugLog('is not listening to Platform BLE') } } async openAPIRefreshStatus(): Promise { - await this.debugLog('openAPIRefreshStatus'); + await this.debugLog('openAPIRefreshStatus') try { - const { body, statusCode } = await this.deviceRefreshStatus(); - const deviceStatus: any = await body.json(); - await this.debugLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`);; + const { body, statusCode } = await this.deviceRefreshStatus() + const deviceStatus: any = await body.json() + await this.debugLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`) if (await this.successfulStatusCodes(statusCode, deviceStatus)) { - await this.debugSuccessLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`); - this.deviceStatus = deviceStatus.body; - await this.openAPIparseStatus(); - await this.updateHomeKitCharacteristics(); + await this.debugSuccessLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`) + this.deviceStatus = deviceStatus.body + await this.openAPIparseStatus() + await this.updateHomeKitCharacteristics() } else { - await this.debugWarnLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`); - await this.debugWarnLog(statusCode, deviceStatus); + await this.debugWarnLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`) + await this.debugWarnLog(statusCode, deviceStatus) } } catch (e: any) { - await this.apiError(e); - await this.errorLog(`failed openAPIRefreshStatus with ${this.device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`); + await this.apiError(e) + await this.errorLog(`failed openAPIRefreshStatus with ${this.device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`) } } async registerWebhook() { if (this.device.webhook) { - await this.debugLog('is listening webhook.'); + await this.debugLog('is listening webhook.') this.platform.webhookEventHandler[this.device.deviceId] = async (context: meterWebhookContext) => { try { - await this.debugLog(`received Webhook: ${JSON.stringify(context)}`); - this.webhookContext = context; - await this.parseStatusWebhook(); - await this.updateHomeKitCharacteristics(); + await this.debugLog(`received Webhook: ${JSON.stringify(context)}`) + this.webhookContext = context + await this.parseStatusWebhook() + await this.updateHomeKitCharacteristics() } catch (e: any) { - await this.errorLog(`failed to handle webhook. Received: ${JSON.stringify(context)} Error: ${e}`); + await this.errorLog(`failed to handle webhook. Received: ${JSON.stringify(context)} Error: ${e}`) } - }; + } } else { - await this.debugLog('is not listening webhook.'); + await this.debugLog('is not listening webhook.') } } @@ -360,52 +367,47 @@ export class Meter extends deviceBase { async updateHomeKitCharacteristics(): Promise { // CurrentRelativeHumidity if (!this.device.meter?.hide_humidity && this.HumiditySensor?.Service) { - await this.updateCharacteristic(this.HumiditySensor.Service, this.hap.Characteristic.CurrentRelativeHumidity, - this.HumiditySensor.CurrentRelativeHumidity, 'CurrentRelativeHumidity'); + await this.updateCharacteristic(this.HumiditySensor.Service, this.hap.Characteristic.CurrentRelativeHumidity, this.HumiditySensor.CurrentRelativeHumidity, 'CurrentRelativeHumidity') } // CurrentTemperature if (!this.device.meter?.hide_temperature && this.TemperatureSensor?.Service) { - await this.updateCharacteristic(this.TemperatureSensor.Service, this.hap.Characteristic.CurrentTemperature, - this.TemperatureSensor.CurrentTemperature, 'CurrentTemperature'); + await this.updateCharacteristic(this.TemperatureSensor.Service, this.hap.Characteristic.CurrentTemperature, this.TemperatureSensor.CurrentTemperature, 'CurrentTemperature') } // BatteryLevel - await this.updateCharacteristic(this.Battery.Service, this.hap.Characteristic.BatteryLevel, - this.Battery.BatteryLevel, 'BatteryLevel'); + await this.updateCharacteristic(this.Battery.Service, this.hap.Characteristic.BatteryLevel, this.Battery.BatteryLevel, 'BatteryLevel') // StatusLowBattery - await this.updateCharacteristic(this.Battery.Service, this.hap.Characteristic.StatusLowBattery, - this.Battery.StatusLowBattery, 'StatusLowBattery'); + await this.updateCharacteristic(this.Battery.Service, this.hap.Characteristic.StatusLowBattery, this.Battery.StatusLowBattery, 'StatusLowBattery') } async BLERefreshConnection(switchbot: any): Promise { - await this.errorLog(`wasn't able to establish BLE Connection, node-switchbot: ${switchbot}`); + await this.errorLog(`wasn't able to establish BLE Connection, node-switchbot: ${switchbot}`) if (this.platform.config.credentials?.token && this.device.connectionType === 'BLE/OpenAPI') { - await this.warnLog('Using OpenAPI Connection to Refresh Status'); - await this.openAPIRefreshStatus(); + await this.warnLog('Using OpenAPI Connection to Refresh Status') + await this.openAPIRefreshStatus() } } async offlineOff(): Promise { if (this.device.offline) { if (!this.device.meter?.hide_humidity && this.HumiditySensor?.Service) { - this.HumiditySensor.Service.updateCharacteristic(this.hap.Characteristic.CurrentRelativeHumidity, 50); + this.HumiditySensor.Service.updateCharacteristic(this.hap.Characteristic.CurrentRelativeHumidity, 50) } if (!this.device.meter?.hide_temperature && this.TemperatureSensor?.Service) { - this.TemperatureSensor.Service.updateCharacteristic(this.hap.Characteristic.CurrentTemperature, 30); + this.TemperatureSensor.Service.updateCharacteristic(this.hap.Characteristic.CurrentTemperature, 30) } - this.Battery.Service.updateCharacteristic(this.hap.Characteristic.BatteryLevel, 100); - this.Battery.Service.updateCharacteristic(this.hap.Characteristic.StatusLowBattery, - this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL); + this.Battery.Service.updateCharacteristic(this.hap.Characteristic.BatteryLevel, 100) + this.Battery.Service.updateCharacteristic(this.hap.Characteristic.StatusLowBattery, this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL) } } async apiError(e: any): Promise { if (!this.device.meter?.hide_humidity && this.HumiditySensor?.Service) { - this.HumiditySensor.Service.updateCharacteristic(this.hap.Characteristic.CurrentRelativeHumidity, e); + this.HumiditySensor.Service.updateCharacteristic(this.hap.Characteristic.CurrentRelativeHumidity, e) } if (!this.device.meter?.hide_temperature && this.TemperatureSensor?.Service) { - this.TemperatureSensor.Service.updateCharacteristic(this.hap.Characteristic.CurrentTemperature, e); + this.TemperatureSensor.Service.updateCharacteristic(this.hap.Characteristic.CurrentTemperature, e) } - this.Battery.Service.updateCharacteristic(this.hap.Characteristic.BatteryLevel, e); - this.Battery.Service.updateCharacteristic(this.hap.Characteristic.StatusLowBattery, e); + this.Battery.Service.updateCharacteristic(this.hap.Characteristic.BatteryLevel, e) + this.Battery.Service.updateCharacteristic(this.hap.Characteristic.StatusLowBattery, e) } } diff --git a/src/device/meterplus.ts b/src/device/meterplus.ts index 06374f84..42208bad 100644 --- a/src/device/meterplus.ts +++ b/src/device/meterplus.ts @@ -2,19 +2,25 @@ * * meterplus.ts: @switchbot/homebridge-switchbot. */ -import { Units } from 'homebridge'; -import { deviceBase } from './device.js'; -import { Subject, interval, skipWhile } from 'rxjs'; -import { convertUnits, validHumidity } from '../utils.js'; -import { SwitchBotBLEModel, SwitchBotBLEModelName } from 'node-switchbot'; - -import type { devicesConfig } from '../settings.js'; -import type { device } from '../types/devicelist.js'; -import type { SwitchBotPlatform } from '../platform.js'; -import type { meterPlusServiceData } from '../types/bledevicestatus.js'; -import type { meterPlusStatus } from '../types/devicestatus.js'; -import type { meterPlusWebhookContext } from '../types/devicewebhookstatus.js'; -import type { CharacteristicValue, PlatformAccessory, Service } from 'homebridge'; +import type { CharacteristicValue, PlatformAccessory, Service } from 'homebridge' + +import type { SwitchBotPlatform } from '../platform.js' +import type { devicesConfig } from '../settings.js' +import type { meterPlusServiceData } from '../types/bledevicestatus.js' +import type { device } from '../types/devicelist.js' +import type { meterPlusStatus } from '../types/devicestatus.js' +import type { meterPlusWebhookContext } from '../types/devicewebhookstatus.js' + +import { Units } from 'homebridge' +/* +* For Testing Locally: +* import { SwitchBotBLEModel, SwitchBotBLEModelName } from '/Users/Shared/GitHub/OpenWonderLabs/node-switchbot/dist/index.js'; +*/ +import { SwitchBotBLEModel, SwitchBotBLEModelName } from 'node-switchbot' +import { interval, skipWhile, Subject } from 'rxjs' + +import { convertUnits, validHumidity } from '../utils.js' +import { deviceBase } from './device.js' /** * Platform Accessory @@ -24,239 +30,237 @@ import type { CharacteristicValue, PlatformAccessory, Service } from 'homebridge export class MeterPlus extends deviceBase { // Services private Battery: { - Name: CharacteristicValue; - Service: Service; - BatteryLevel: CharacteristicValue; - StatusLowBattery: CharacteristicValue; - }; + Name: CharacteristicValue + Service: Service + BatteryLevel: CharacteristicValue + StatusLowBattery: CharacteristicValue + } private HumiditySensor?: { - Name: CharacteristicValue; - Service: Service; - CurrentRelativeHumidity: CharacteristicValue; - }; + Name: CharacteristicValue + Service: Service + CurrentRelativeHumidity: CharacteristicValue + } private TemperatureSensor?: { - Name: CharacteristicValue; - Service: Service; - CurrentTemperature: CharacteristicValue; - }; + Name: CharacteristicValue + Service: Service + CurrentTemperature: CharacteristicValue + } // OpenAPI - deviceStatus!: meterPlusStatus; + deviceStatus!: meterPlusStatus - //Webhook - webhookContext!: meterPlusWebhookContext; + // Webhook + webhookContext!: meterPlusWebhookContext // BLE - serviceData!: meterPlusServiceData; + serviceData!: meterPlusServiceData // Updates - meterUpdateInProgress!: boolean; - doMeterUpdate: Subject; + meterUpdateInProgress!: boolean + doMeterUpdate: Subject constructor( readonly platform: SwitchBotPlatform, accessory: PlatformAccessory, device: device & devicesConfig, ) { - super(platform, accessory, device); + super(platform, accessory, device) // Set category - accessory.category = this.hap.Categories.SENSOR; + accessory.category = this.hap.Categories.SENSOR // this is subject we use to track when we need to POST changes to the SwitchBot API - this.doMeterUpdate = new Subject(); - this.meterUpdateInProgress = false; + this.doMeterUpdate = new Subject() + this.meterUpdateInProgress = false // Initialize Battery Service - accessory.context.Battery = accessory.context.Battery ?? {}; + accessory.context.Battery = accessory.context.Battery ?? {} this.Battery = { Name: `${accessory.displayName} Battery`, Service: accessory.getService(this.hap.Service.Battery) ?? accessory.addService(this.hap.Service.Battery) as Service, BatteryLevel: accessory.context.BatteryLevel ?? 100, StatusLowBattery: accessory.context.StatusLowBattery ?? this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL, - }; - accessory.context.Battery = this.Battery as object; + } + accessory.context.Battery = this.Battery as object // Initialize Battery Characteristics - this.Battery.Service - .setCharacteristic(this.hap.Characteristic.Name, this.Battery.Name) - .setCharacteristic(this.hap.Characteristic.ChargingState, this.hap.Characteristic.ChargingState.NOT_CHARGEABLE) - .getCharacteristic(this.hap.Characteristic.BatteryLevel) - .onGet(() => { - return this.Battery.BatteryLevel; - }); - - this.Battery.Service - .getCharacteristic(this.hap.Characteristic.StatusLowBattery) - .onGet(() => { - return this.Battery.StatusLowBattery; - }); + this.Battery.Service.setCharacteristic(this.hap.Characteristic.Name, this.Battery.Name).setCharacteristic(this.hap.Characteristic.ChargingState, this.hap.Characteristic.ChargingState.NOT_CHARGEABLE).getCharacteristic(this.hap.Characteristic.BatteryLevel).onGet(() => { + return this.Battery.BatteryLevel + }) + + this.Battery.Service.getCharacteristic(this.hap.Characteristic.StatusLowBattery).onGet(() => { + return this.Battery.StatusLowBattery + }) // Initialize Temperature Sensor Service if (device.meter?.hide_temperature) { if (this.TemperatureSensor) { - this.debugLog('Removing Temperature Sensor Service'); - this.TemperatureSensor.Service = this.accessory.getService(this.hap.Service.TemperatureSensor) as Service; - accessory.removeService(this.TemperatureSensor.Service); + this.debugLog('Removing Temperature Sensor Service') + this.TemperatureSensor.Service = this.accessory.getService(this.hap.Service.TemperatureSensor) as Service + accessory.removeService(this.TemperatureSensor.Service) } } else { - accessory.context.TemperatureSensor = accessory.context.TemperatureSensor ?? {}; + accessory.context.TemperatureSensor = accessory.context.TemperatureSensor ?? {} this.TemperatureSensor = { Name: `${accessory.displayName} Temperature Sensor`, Service: accessory.getService(this.hap.Service.TemperatureSensor) ?? this.accessory.addService(this.hap.Service.TemperatureSensor) as Service, CurrentTemperature: accessory.context.CurrentTemperature ?? 30, - }; - accessory.context.TemperatureSensor = this.TemperatureSensor as object; + } + accessory.context.TemperatureSensor = this.TemperatureSensor as object // Initialize Temperature Sensor Characteristics - this.TemperatureSensor.Service - .setCharacteristic(this.hap.Characteristic.Name, this.TemperatureSensor.Name) - .getCharacteristic(this.hap.Characteristic.CurrentTemperature) - .setProps({ - unit: Units['CELSIUS'], - validValueRanges: [-273.15, 100], - minValue: -273.15, - maxValue: 100, - minStep: 0.1, - }) - .onGet(() => { - return this.TemperatureSensor!.CurrentTemperature!; - }); + this.TemperatureSensor.Service.setCharacteristic(this.hap.Characteristic.Name, this.TemperatureSensor.Name).getCharacteristic(this.hap.Characteristic.CurrentTemperature).setProps({ + unit: Units.CELSIUS, + validValueRanges: [-273.15, 100], + minValue: -273.15, + maxValue: 100, + minStep: 0.1, + }).onGet(() => { + return this.TemperatureSensor!.CurrentTemperature! + }) } // Initialize Humidity Sensor Service if (device.meter?.hide_humidity) { if (this.HumiditySensor) { - this.debugLog('Removing Humidity Sensor Service'); - this.HumiditySensor.Service = this.accessory.getService(this.hap.Service.HumiditySensor) as Service; - accessory.removeService(this.HumiditySensor.Service); + this.debugLog('Removing Humidity Sensor Service') + this.HumiditySensor.Service = this.accessory.getService(this.hap.Service.HumiditySensor) as Service + accessory.removeService(this.HumiditySensor.Service) } } else { - accessory.context.HumiditySensor = accessory.context.HumiditySensor ?? {}; + accessory.context.HumiditySensor = accessory.context.HumiditySensor ?? {} this.HumiditySensor = { Name: `${accessory.displayName} Humidity Sensor`, Service: accessory.getService(this.hap.Service.HumiditySensor) ?? this.accessory.addService(this.hap.Service.HumiditySensor) as Service, CurrentRelativeHumidity: accessory.context.CurrentRelativeHumidity ?? 50, - }; - accessory.context.HumiditySensor = this.HumiditySensor as object; + } + accessory.context.HumiditySensor = this.HumiditySensor as object // Initialize Humidity Sensor Characteristics - this.HumiditySensor.Service - .setCharacteristic(this.hap.Characteristic.Name, this.HumiditySensor.Name) - .getCharacteristic(this.hap.Characteristic.CurrentRelativeHumidity) - .setProps({ - minStep: 0.1, - }) - .onGet(() => { - return this.HumiditySensor!.CurrentRelativeHumidity!; - }); + this.HumiditySensor.Service.setCharacteristic(this.hap.Characteristic.Name, this.HumiditySensor.Name).getCharacteristic(this.hap.Characteristic.CurrentRelativeHumidity).setProps({ + minStep: 0.1, + }).onGet(() => { + return this.HumiditySensor!.CurrentRelativeHumidity! + }) } // Retrieve initial values and updateHomekit - this.debugLog('Retrieve initial values and update Homekit'); - this.refreshStatus(); + try { + this.debugLog('Retrieve initial values and update Homekit') + this.refreshStatus() + } catch (e: any) { + this.errorLog(`failed to retrieve initial values and update Homekit, Error: ${e}`) + } + + // regisiter webhook event handler if enabled + try { + this.debugLog('Registering Webhook Event Handler') + this.registerWebhook() + } catch (e: any) { + this.errorLog(`failed to registerWebhook, Error: ${e}`) + } - //regisiter webhook event handler - this.debugLog('Registering Webhook Event Handler'); - this.registerWebhook(); + // regisiter platform BLE event handler if enabled + try { + this.debugLog('Registering Platform BLE Event Handler') + this.registerPlatformBLE() + } catch (e: any) { + this.errorLog(`failed to registerPlatformBLE, Error: ${e}`) + } // Start an update interval interval(this.deviceRefreshRate * 1000) .pipe(skipWhile(() => this.meterUpdateInProgress)) .subscribe(async () => { - await this.refreshStatus(); - }); + await this.refreshStatus() + }) } async BLEparseStatus(): Promise { - await this.debugLog('BLEparseStatus'); - await this.debugLog(`(temperature, humidity) = BLE:(${this.serviceData.temperature.c}, ${this.serviceData.humidity}),` - + ` current:(${this.TemperatureSensor?.CurrentTemperature}, ${this.HumiditySensor?.CurrentRelativeHumidity})`); + await this.debugLog('BLEparseStatus') + await this.debugLog(`(temperature, humidity) = BLE:(${this.serviceData.temperature.c}, ${this.serviceData.humidity}), current:(${this.TemperatureSensor?.CurrentTemperature}, ${this.HumiditySensor?.CurrentRelativeHumidity})`) // CurrentRelativeHumidity if (!this.device.iosensor?.hide_humidity && this.HumiditySensor?.Service) { - this.HumiditySensor.CurrentRelativeHumidity = validHumidity(this.serviceData.humidity, 0, 100); - await this.debugLog(`CurrentRelativeHumidity: ${this.HumiditySensor.CurrentRelativeHumidity}%`); + this.HumiditySensor.CurrentRelativeHumidity = validHumidity(this.serviceData.humidity, 0, 100) + await this.debugLog(`CurrentRelativeHumidity: ${this.HumiditySensor.CurrentRelativeHumidity}%`) } // Current Temperature if (!this.device.meter?.hide_temperature && this.TemperatureSensor?.Service) { - const CELSIUS = this.serviceData.temperature.c < 0 ? 0 : this.serviceData.temperature.c > 100 ? 100 : this.serviceData.temperature.c; - this.TemperatureSensor.CurrentTemperature = CELSIUS; - await this.debugLog(`CurrentTemperature: ${this.TemperatureSensor.CurrentTemperature}°c`); + const CELSIUS = this.serviceData.temperature.c < 0 ? 0 : this.serviceData.temperature.c > 100 ? 100 : this.serviceData.temperature.c + this.TemperatureSensor.CurrentTemperature = CELSIUS + await this.debugLog(`CurrentTemperature: ${this.TemperatureSensor.CurrentTemperature}°c`) } // BatteryLevel - this.Battery.BatteryLevel = this.serviceData.battery; - await this.debugLog(`BatteryLevel: ${this.Battery.BatteryLevel}`); + this.Battery.BatteryLevel = this.serviceData.battery + await this.debugLog(`BatteryLevel: ${this.Battery.BatteryLevel}`) // StatusLowBattery this.Battery.StatusLowBattery = this.Battery.BatteryLevel < 10 - ? this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW : this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL; - await this.debugLog(`StatusLowBattery: ${this.Battery.StatusLowBattery}`); + ? this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW + : this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL + await this.debugLog(`StatusLowBattery: ${this.Battery.StatusLowBattery}`) } async openAPIparseStatus(): Promise { - await this.debugLog('openAPIparseStatus'); - await this.debugLog(`(temperature, humidity) = OpenAPI:(${this.deviceStatus.temperature}, ${this.deviceStatus.humidity}),` - + ` current:(${this.TemperatureSensor?.CurrentTemperature}, ${this.HumiditySensor?.CurrentRelativeHumidity})`); + await this.debugLog('openAPIparseStatus') + await this.debugLog(`(temperature, humidity) = OpenAPI:(${this.deviceStatus.temperature}, ${this.deviceStatus.humidity}), current:(${this.TemperatureSensor?.CurrentTemperature}, ${this.HumiditySensor?.CurrentRelativeHumidity})`) // CurrentRelativeHumidity if (!this.device.meter?.hide_humidity && this.HumiditySensor?.Service) { - this.HumiditySensor.CurrentRelativeHumidity = this.deviceStatus.humidity; - await this.debugLog(`CurrentRelativeHumidity: ${this.HumiditySensor.CurrentRelativeHumidity}%`); + this.HumiditySensor.CurrentRelativeHumidity = this.deviceStatus.humidity + await this.debugLog(`CurrentRelativeHumidity: ${this.HumiditySensor.CurrentRelativeHumidity}%`) } // CurrentTemperature if (!this.device.meter?.hide_temperature && this.TemperatureSensor?.Service) { - this.TemperatureSensor.CurrentTemperature = this.deviceStatus.temperature; - await this.debugLog(`CurrentTemperature: ${this.TemperatureSensor.CurrentTemperature}°c`); + this.TemperatureSensor.CurrentTemperature = this.deviceStatus.temperature + await this.debugLog(`CurrentTemperature: ${this.TemperatureSensor.CurrentTemperature}°c`) } // BatteryLevel - this.Battery.BatteryLevel = this.deviceStatus.battery; - await this.debugLog(`BatteryLevel: ${this.Battery.BatteryLevel}`); + this.Battery.BatteryLevel = this.deviceStatus.battery + await this.debugLog(`BatteryLevel: ${this.Battery.BatteryLevel}`) // StatusLowBattery this.Battery.StatusLowBattery = this.Battery.BatteryLevel < 10 - ? this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW : this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL; - await this.debugLog(`StatusLowBattery: ${this.Battery.StatusLowBattery}`); + ? this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW + : this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL + await this.debugLog(`StatusLowBattery: ${this.Battery.StatusLowBattery}`) // FirmwareVersion if (this.deviceStatus.version) { - const version = this.deviceStatus.version.toString(); - this.debugLog(`Firmware Version: ${version.replace(/^V|-.*$/g, '')}`); - const deviceVersion = version.replace(/^V|-.*$/g, '') ?? '0.0.0'; + const version = this.deviceStatus.version.toString() + this.debugLog(`Firmware Version: ${version.replace(/^V|-.*$/g, '')}`) + const deviceVersion = version.replace(/^V|-.*$/g, '') ?? '0.0.0' this.accessory .getService(this.hap.Service.AccessoryInformation)! .setCharacteristic(this.hap.Characteristic.HardwareRevision, deviceVersion) .setCharacteristic(this.hap.Characteristic.FirmwareRevision, deviceVersion) .getCharacteristic(this.hap.Characteristic.FirmwareRevision) - .updateValue(deviceVersion); - this.accessory.context.version = deviceVersion; - this.debugSuccessLog(`v: ${this.accessory.context.version}`); + .updateValue(deviceVersion) + this.accessory.context.version = deviceVersion + this.debugSuccessLog(`v: ${this.accessory.context.version}`) } } async parseStatusWebhook(): Promise { - await this.debugLog('parseStatusWebhook'); - await this.debugLog(`(scale, temperature, humidity) = Webhook:(${this.webhookContext.scale}, ${convertUnits(this.webhookContext.temperature, - this.webhookContext.scale, this.device.meter?.convertUnitTo)}, ${this.webhookContext.humidity}),` - + ` current:(${this.TemperatureSensor?.CurrentTemperature}, ${this.HumiditySensor?.CurrentRelativeHumidity})`); + await this.debugLog('parseStatusWebhook') + await this.debugLog(`(scale, temperature, humidity) = Webhook:(${this.webhookContext.scale}, ${convertUnits(this.webhookContext.temperature, this.webhookContext.scale, this.device.meter?.convertUnitTo)}, ${this.webhookContext.humidity}) current:(${this.TemperatureSensor?.CurrentTemperature}, ${this.HumiditySensor?.CurrentRelativeHumidity})`) // Check if the scale is not CELSIUS if (this.webhookContext.scale !== 'CELSIUS' && this.device.hub?.convertUnitTo === undefined) { - await this.warnLog(`received a non-CELSIUS Webhook scale: ${this.webhookContext.scale}, Use the *convertUnitsTo* config under Hub settings,` - + ' if displaying incorrectly in HomeKit.'); + await this.warnLog(`received a non-CELSIUS Webhook scale: ${this.webhookContext.scale}, Use the *convertUnitsTo* config under Hub settings, if displaying incorrectly in HomeKit.`) } // CurrentRelativeHumidity if (!this.device.meter?.hide_humidity && this.HumiditySensor?.Service) { - this.HumiditySensor.CurrentRelativeHumidity = this.webhookContext.humidity; - await this.debugLog(`CurrentRelativeHumidity: ${this.HumiditySensor.CurrentRelativeHumidity}`); + this.HumiditySensor.CurrentRelativeHumidity = this.webhookContext.humidity + await this.debugLog(`CurrentRelativeHumidity: ${this.HumiditySensor.CurrentRelativeHumidity}`) } // CurrentTemperature if (!this.device.meter?.hide_temperature && this.TemperatureSensor?.Service) { - this.TemperatureSensor.CurrentTemperature = convertUnits(this.webhookContext.temperature, this.webhookContext.scale, - this.device.hub?.convertUnitTo); - await this.debugLog(`CurrentTemperature: ${this.TemperatureSensor.CurrentTemperature}`); + this.TemperatureSensor.CurrentTemperature = convertUnits(this.webhookContext.temperature, this.webhookContext.scale, this.device.hub?.convertUnitTo) + await this.debugLog(`CurrentTemperature: ${this.TemperatureSensor.CurrentTemperature}`) } } @@ -265,93 +269,97 @@ export class MeterPlus extends deviceBase { */ async refreshStatus(): Promise { if (!this.device.enableCloudService && this.OpenAPI) { - await this.errorLog(`refreshStatus enableCloudService: ${this.device.enableCloudService}`); - } else if (this.BLE || this.config.options?.BLE) { - await this.BLERefreshStatus(); + await this.errorLog(`refreshStatus enableCloudService: ${this.device.enableCloudService}`) + } else if (this.BLE) { + await this.BLERefreshStatus() } else if (this.OpenAPI && this.platform.config.credentials?.token) { - await this.openAPIRefreshStatus(); + await this.openAPIRefreshStatus() } else { - await this.offlineOff(); - await this.debugWarnLog(`Connection Type: ${this.device.connectionType}, refreshStatus will not happen.`); + await this.offlineOff() + await this.debugWarnLog(`Connection Type: ${this.device.connectionType}, refreshStatus will not happen.`) } } async BLERefreshStatus(): Promise { - await this.debugLog('BLERefreshStatus'); + await this.debugLog('BLERefreshStatus') + const switchbot = await this.switchbotBLE() + if (switchbot === undefined) { + await this.BLERefreshConnection(switchbot) + } else { + // Start to monitor advertisement packets + (async () => { + // Start to monitor advertisement packets + const serviceData = await this.monitorAdvertisementPackets(switchbot) as meterPlusServiceData + // Update HomeKit + if (serviceData.model === SwitchBotBLEModel.MeterPlus && serviceData.modelName === SwitchBotBLEModelName.MeterPlus) { + this.serviceData = serviceData + await this.BLEparseStatus() + await this.updateHomeKitCharacteristics() + } else { + await this.errorLog(`failed to get serviceData, serviceData: ${serviceData}`) + await this.BLERefreshConnection(switchbot) + } + })() + } + } + + async registerPlatformBLE(): Promise { + await this.debugLog('registerPlatformBLE') if (this.config.options?.BLE) { - await this.debugLog('is listening to Platform BLE.'); - this.device.bleMac = this.device.deviceId!.match(/.{1,2}/g)!.join(':').toLowerCase(); - await this.debugLog(`bleMac: ${this.device.bleMac}`); + await this.debugLog('is listening to Platform BLE.') + this.device.bleMac = this.device.deviceId!.match(/.{1,2}/g)!.join(':').toLowerCase() + await this.debugLog(`bleMac: ${this.device.bleMac}`) this.platform.bleEventHandler[this.device.bleMac] = async (context: meterPlusServiceData) => { try { - await this.debugLog(`received BLE: ${JSON.stringify(context)}`); - this.serviceData = context; - await this.BLEparseStatus(); - await this.updateHomeKitCharacteristics(); + await this.debugLog(`received BLE: ${JSON.stringify(context)}`) + this.serviceData = context + await this.BLEparseStatus() + await this.updateHomeKitCharacteristics() } catch (e: any) { - await this.errorLog(`failed to handle BLE. Received: ${JSON.stringify(context)} Error: ${e}`); + await this.errorLog(`failed to handle BLE. Received: ${JSON.stringify(context)} Error: ${e}`) } - }; - } else { - await this.debugLog('is using Device BLE Scanning.'); - const switchbot = await this.switchbotBLE(); - if (switchbot === undefined) { - await this.BLERefreshConnection(switchbot); - } else { - // Start to monitor advertisement packets - (async () => { - // Start to monitor advertisement packets - const serviceData = await this.monitorAdvertisementPackets(switchbot) as meterPlusServiceData; - // Update HomeKit - if (serviceData.model === SwitchBotBLEModel.MeterPlus && serviceData.modelName === SwitchBotBLEModelName.MeterPlus) { - this.serviceData = serviceData; - await this.BLEparseStatus(); - await this.updateHomeKitCharacteristics(); - } else { - await this.errorLog(`failed to get serviceData, serviceData: ${serviceData}`); - await this.BLERefreshConnection(switchbot); - } - })(); } + } else { + await this.debugLog('is not listening to Platform BLE') } } async openAPIRefreshStatus(): Promise { - await this.debugLog('openAPIRefreshStatus'); + await this.debugLog('openAPIRefreshStatus') try { - const { body, statusCode } = await this.deviceRefreshStatus(); - const deviceStatus: any = await body.json(); - await this.debugLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`);; + const { body, statusCode } = await this.deviceRefreshStatus() + const deviceStatus: any = await body.json() + await this.debugLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`) if (await this.successfulStatusCodes(statusCode, deviceStatus)) { - await this.debugSuccessLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`); - this.deviceStatus = deviceStatus.body; - await this.openAPIparseStatus(); - await this.updateHomeKitCharacteristics(); + await this.debugSuccessLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`) + this.deviceStatus = deviceStatus.body + await this.openAPIparseStatus() + await this.updateHomeKitCharacteristics() } else { - await this.debugWarnLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`); - await this.debugWarnLog(statusCode, deviceStatus); + await this.debugWarnLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`) + await this.debugWarnLog(statusCode, deviceStatus) } } catch (e: any) { - await this.apiError(e); - await this.errorLog(`failed openAPIRefreshStatus with ${this.device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`); + await this.apiError(e) + await this.errorLog(`failed openAPIRefreshStatus with ${this.device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`) } } async registerWebhook() { if (this.device.webhook) { - await this.debugLog('is listening webhook.'); + await this.debugLog('is listening webhook.') this.platform.webhookEventHandler[this.device.deviceId] = async (context: meterPlusWebhookContext) => { try { - await this.debugLog(`received Webhook: ${JSON.stringify(context)}`); - this.webhookContext = context; - await this.parseStatusWebhook(); - await this.updateHomeKitCharacteristics(); + await this.debugLog(`received Webhook: ${JSON.stringify(context)}`) + this.webhookContext = context + await this.parseStatusWebhook() + await this.updateHomeKitCharacteristics() } catch (e: any) { - await this.errorLog(`failed to handle webhook. Received: ${JSON.stringify(context)} Error: ${e}`); + await this.errorLog(`failed to handle webhook. Received: ${JSON.stringify(context)} Error: ${e}`) } - }; + } } else { - await this.debugLog('is not listening webhook.'); + await this.debugLog('is not listening webhook.') } } @@ -361,52 +369,47 @@ export class MeterPlus extends deviceBase { async updateHomeKitCharacteristics(): Promise { // CurrentRelativeHumidity if (!this.device.meter?.hide_humidity && this.HumiditySensor?.Service) { - await this.updateCharacteristic(this.HumiditySensor.Service, this.hap.Characteristic.CurrentRelativeHumidity, - this.HumiditySensor.CurrentRelativeHumidity, 'CurrentRelativeHumidity'); + await this.updateCharacteristic(this.HumiditySensor.Service, this.hap.Characteristic.CurrentRelativeHumidity, this.HumiditySensor.CurrentRelativeHumidity, 'CurrentRelativeHumidity') } // CurrentTemperature if (!this.device.meter?.hide_temperature && this.TemperatureSensor?.Service) { - await this.updateCharacteristic(this.TemperatureSensor.Service, this.hap.Characteristic.CurrentTemperature, - this.TemperatureSensor.CurrentTemperature, 'CurrentTemperature'); + await this.updateCharacteristic(this.TemperatureSensor.Service, this.hap.Characteristic.CurrentTemperature, this.TemperatureSensor.CurrentTemperature, 'CurrentTemperature') } // BatteryLevel - await this.updateCharacteristic(this.Battery.Service, this.hap.Characteristic.BatteryLevel, - this.Battery.BatteryLevel, 'BatteryLevel'); + await this.updateCharacteristic(this.Battery.Service, this.hap.Characteristic.BatteryLevel, this.Battery.BatteryLevel, 'BatteryLevel') // StatusLowBattery - await this.updateCharacteristic(this.Battery.Service, this.hap.Characteristic.StatusLowBattery, - this.Battery.StatusLowBattery, 'StatusLowBattery'); + await this.updateCharacteristic(this.Battery.Service, this.hap.Characteristic.StatusLowBattery, this.Battery.StatusLowBattery, 'StatusLowBattery') } async BLERefreshConnection(switchbot: any): Promise { - await this.errorLog(`wasn't able to establish BLE Connection, node-switchbot: ${switchbot}`); + await this.errorLog(`wasn't able to establish BLE Connection, node-switchbot: ${switchbot}`) if (this.platform.config.credentials?.token && this.device.connectionType === 'BLE/OpenAPI') { - await this.warnLog('Using OpenAPI Connection to Refresh Status'); - await this.openAPIRefreshStatus(); + await this.warnLog('Using OpenAPI Connection to Refresh Status') + await this.openAPIRefreshStatus() } } async offlineOff(): Promise { if (this.device.offline) { if (!this.device.meter?.hide_humidity && this.HumiditySensor?.Service) { - this.HumiditySensor.Service.updateCharacteristic(this.hap.Characteristic.CurrentRelativeHumidity, 50); + this.HumiditySensor.Service.updateCharacteristic(this.hap.Characteristic.CurrentRelativeHumidity, 50) } if (!this.device.meter?.hide_temperature && this.TemperatureSensor?.Service) { - this.TemperatureSensor.Service.updateCharacteristic(this.hap.Characteristic.CurrentTemperature, 30); + this.TemperatureSensor.Service.updateCharacteristic(this.hap.Characteristic.CurrentTemperature, 30) } - this.Battery.Service.updateCharacteristic(this.hap.Characteristic.BatteryLevel, 100); - this.Battery.Service.updateCharacteristic(this.hap.Characteristic.StatusLowBattery, - this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL); + this.Battery.Service.updateCharacteristic(this.hap.Characteristic.BatteryLevel, 100) + this.Battery.Service.updateCharacteristic(this.hap.Characteristic.StatusLowBattery, this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL) } } async apiError(e: any): Promise { if (!this.device.meter?.hide_humidity && this.HumiditySensor?.Service) { - this.HumiditySensor.Service.updateCharacteristic(this.hap.Characteristic.CurrentRelativeHumidity, e); + this.HumiditySensor.Service.updateCharacteristic(this.hap.Characteristic.CurrentRelativeHumidity, e) } if (!this.device.meter?.hide_temperature && this.TemperatureSensor?.Service) { - this.TemperatureSensor.Service.updateCharacteristic(this.hap.Characteristic.CurrentTemperature, e); + this.TemperatureSensor.Service.updateCharacteristic(this.hap.Characteristic.CurrentTemperature, e) } - this.Battery.Service.updateCharacteristic(this.hap.Characteristic.BatteryLevel, e); - this.Battery.Service.updateCharacteristic(this.hap.Characteristic.StatusLowBattery, e); + this.Battery.Service.updateCharacteristic(this.hap.Characteristic.BatteryLevel, e) + this.Battery.Service.updateCharacteristic(this.hap.Characteristic.StatusLowBattery, e) } } diff --git a/src/device/motion.ts b/src/device/motion.ts index 3dada2bf..86e135d1 100644 --- a/src/device/motion.ts +++ b/src/device/motion.ts @@ -1,19 +1,24 @@ - /* Copyright(C) 2021-2024, donavanbecker (https://github.com/donavanbecker). All rights reserved. * * motion.ts: @switchbot/homebridge-switchbot. */ -import { deviceBase } from './device.js'; -import { Subject, interval, skipWhile } from 'rxjs'; -import { SwitchBotBLEModel, SwitchBotBLEModelName } from 'node-switchbot'; - -import type { devicesConfig } from '../settings.js'; -import type { device } from '../types/devicelist.js'; -import type { SwitchBotPlatform } from '../platform.js'; -import type { motionSensorServiceData } from '../types/bledevicestatus.js'; -import type { motionSensorStatus } from '../types/devicestatus.js'; -import type { motionSensorWebhookContext } from '../types/devicewebhookstatus.js'; -import type { CharacteristicValue, PlatformAccessory, Service } from 'homebridge'; +import type { CharacteristicValue, PlatformAccessory, Service } from 'homebridge' + +import type { SwitchBotPlatform } from '../platform.js' +import type { devicesConfig } from '../settings.js' +import type { motionSensorServiceData } from '../types/bledevicestatus.js' +import type { device } from '../types/devicelist.js' +import type { motionSensorStatus } from '../types/devicestatus.js' +import type { motionSensorWebhookContext } from '../types/devicewebhookstatus.js' + +/* +* For Testing Locally: +* import { SwitchBotBLEModel, SwitchBotBLEModelName } from '/Users/Shared/GitHub/OpenWonderLabs/node-switchbot/dist/index.js'; +*/ +import { SwitchBotBLEModel, SwitchBotBLEModelName } from 'node-switchbot' +import { interval, skipWhile, Subject } from 'rxjs' + +import { deviceBase } from './device.js' /** * Platform Accessory @@ -23,211 +28,214 @@ import type { CharacteristicValue, PlatformAccessory, Service } from 'homebridge export class Motion extends deviceBase { // Services private Battery: { - Name: CharacteristicValue; - Service: Service; - BatteryLevel: CharacteristicValue; - StatusLowBattery: CharacteristicValue; - }; + Name: CharacteristicValue + Service: Service + BatteryLevel: CharacteristicValue + StatusLowBattery: CharacteristicValue + } private MotionSensor: { - Name: CharacteristicValue; - Service: Service; - MotionDetected: CharacteristicValue; - }; + Name: CharacteristicValue + Service: Service + MotionDetected: CharacteristicValue + } private LightSensor?: { - Name: CharacteristicValue; - Service: Service; - CurrentAmbientLightLevel: CharacteristicValue; - }; + Name: CharacteristicValue + Service: Service + CurrentAmbientLightLevel: CharacteristicValue + } // OpenAPI - deviceStatus!: motionSensorStatus; + deviceStatus!: motionSensorStatus - //Webhook - webhookContext!: motionSensorWebhookContext; + // Webhook + webhookContext!: motionSensorWebhookContext // BLE - serviceData!: motionSensorServiceData; + serviceData!: motionSensorServiceData // Updates - motionUbpdateInProgress!: boolean; - doMotionUpdate!: Subject; + motionUbpdateInProgress!: boolean + doMotionUpdate!: Subject constructor( readonly platform: SwitchBotPlatform, accessory: PlatformAccessory, device: device & devicesConfig, ) { - super(platform, accessory, device); + super(platform, accessory, device) // Set category - accessory.category = this.hap.Categories.SENSOR; + accessory.category = this.hap.Categories.SENSOR // this is subject we use to track when we need to POST changes to the SwitchBot API - this.doMotionUpdate = new Subject(); - this.motionUbpdateInProgress = false; + this.doMotionUpdate = new Subject() + this.motionUbpdateInProgress = false // Initialize Motion Sensor property - accessory.context.MotionSensor = accessory.context.MotionSensor ?? {}; + accessory.context.MotionSensor = accessory.context.MotionSensor ?? {} this.MotionSensor = { Name: `${accessory.displayName} Motion Sensor`, Service: accessory.getService(this.hap.Service.MotionSensor) ?? accessory.addService(this.hap.Service.MotionSensor) as Service, MotionDetected: accessory.context.MotionDetected ?? false, - }; - accessory.context.MotionSensor = this.MotionSensor as object; + } + accessory.context.MotionSensor = this.MotionSensor as object // Initialize Motion Sensor Characteristics - this.MotionSensor.Service - .setCharacteristic(this.hap.Characteristic.Name, this.MotionSensor.Name) - .setCharacteristic(this.hap.Characteristic.StatusActive, true) - .getCharacteristic(this.hap.Characteristic.MotionDetected) - .onGet(() => { - return this.MotionSensor.MotionDetected; - }); + this.MotionSensor.Service.setCharacteristic(this.hap.Characteristic.Name, this.MotionSensor.Name).setCharacteristic(this.hap.Characteristic.StatusActive, true).getCharacteristic(this.hap.Characteristic.MotionDetected).onGet(() => { + return this.MotionSensor.MotionDetected + }) // Initialize Battery Service - accessory.context.Battery = accessory.context.Battery ?? {}; + accessory.context.Battery = accessory.context.Battery ?? {} this.Battery = { Name: `${accessory.displayName} Battery`, Service: accessory.getService(this.hap.Service.Battery) ?? accessory.addService(this.hap.Service.Battery) as Service, BatteryLevel: accessory.context.BatteryLevel ?? 100, StatusLowBattery: accessory.context.StatusLowBattery ?? this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL, - }; - accessory.context.Battery = this.Battery as object; + } + accessory.context.Battery = this.Battery as object // Initialize Battery Characteristics - this.Battery.Service - .setCharacteristic(this.hap.Characteristic.Name, this.Battery.Name) - .setCharacteristic(this.hap.Characteristic.ChargingState, this.hap.Characteristic.ChargingState.NOT_CHARGEABLE) - .getCharacteristic(this.hap.Characteristic.BatteryLevel) - .onGet(() => { - return this.Battery.BatteryLevel; - }); - - this.Battery.Service - .setCharacteristic(this.hap.Characteristic.StatusLowBattery, this.Battery.StatusLowBattery) - .getCharacteristic(this.hap.Characteristic.StatusLowBattery) - .onGet(() => { - return this.Battery.StatusLowBattery; - }); + this.Battery.Service.setCharacteristic(this.hap.Characteristic.Name, this.Battery.Name).setCharacteristic(this.hap.Characteristic.ChargingState, this.hap.Characteristic.ChargingState.NOT_CHARGEABLE).getCharacteristic(this.hap.Characteristic.BatteryLevel).onGet(() => { + return this.Battery.BatteryLevel + }) + + this.Battery.Service.setCharacteristic(this.hap.Characteristic.StatusLowBattery, this.Battery.StatusLowBattery).getCharacteristic(this.hap.Characteristic.StatusLowBattery).onGet(() => { + return this.Battery.StatusLowBattery + }) // Initialize Light Sensor Service if (device.motion?.hide_lightsensor) { if (this.LightSensor) { - this.debugLog('Removing Light Sensor Service'); - this.LightSensor.Service = this.accessory.getService(this.hap.Service.LightSensor) as Service; - accessory.removeService(this.LightSensor.Service); + this.debugLog('Removing Light Sensor Service') + this.LightSensor.Service = this.accessory.getService(this.hap.Service.LightSensor) as Service + accessory.removeService(this.LightSensor.Service) } } else { - accessory.context.LightSensor = accessory.context.LightSensor ?? {}; + accessory.context.LightSensor = accessory.context.LightSensor ?? {} this.LightSensor = { Name: `${accessory.displayName} Light Sensor`, Service: accessory.getService(this.hap.Service.LightSensor) ?? this.accessory.addService(this.hap.Service.LightSensor) as Service, CurrentAmbientLightLevel: accessory.context.CurrentAmbientLightLevel ?? 0.0001, - }; - accessory.context.LightSensor = this.LightSensor as object; + } + accessory.context.LightSensor = this.LightSensor as object // Initialize LightSensor Characteristics - this.LightSensor.Service - .setCharacteristic(this.hap.Characteristic.Name, this.LightSensor.Name) - .setCharacteristic(this.hap.Characteristic.StatusActive, true) - .getCharacteristic(this.hap.Characteristic.CurrentAmbientLightLevel) - .onGet(() => { - return this.LightSensor!.CurrentAmbientLightLevel; - }); + this.LightSensor.Service.setCharacteristic(this.hap.Characteristic.Name, this.LightSensor.Name).setCharacteristic(this.hap.Characteristic.StatusActive, true).getCharacteristic(this.hap.Characteristic.CurrentAmbientLightLevel).onGet(() => { + return this.LightSensor!.CurrentAmbientLightLevel + }) }; // Retrieve initial values and updateHomekit - this.debugLog('Retrieve initial values and update Homekit'); - this.refreshStatus(); + try { + this.debugLog('Retrieve initial values and update Homekit') + this.refreshStatus() + } catch (e: any) { + this.errorLog(`failed to retrieve initial values and update Homekit, Error: ${e}`) + } + + // regisiter webhook event handler if enabled + try { + this.debugLog('Registering Webhook Event Handler') + this.registerWebhook() + } catch (e: any) { + this.errorLog(`failed to registerWebhook, Error: ${e}`) + } - //regisiter webhook event handler - this.debugLog('Registering Webhook Event Handler'); - this.registerWebhook(); + // regisiter platform BLE event handler if enabled + try { + this.debugLog('Registering Platform BLE Event Handler') + this.registerPlatformBLE() + } catch (e: any) { + this.errorLog(`failed to registerPlatformBLE, Error: ${e}`) + } // Start an update interval interval(this.deviceRefreshRate * 1000) .pipe(skipWhile(() => this.motionUbpdateInProgress)) .subscribe(async () => { - await this.refreshStatus(); - }); + await this.refreshStatus() + }) } async BLEparseStatus(): Promise { - await this.debugLog('BLEparseStatus'); - await this.debugLog(`(moveDetected) = BLE: (${this.serviceData.movement}), current: (${this.MotionSensor.MotionDetected})`); + await this.debugLog('BLEparseStatus') + await this.debugLog(`(moveDetected) = BLE: (${this.serviceData.movement}), current: (${this.MotionSensor.MotionDetected})`) // Movement - this.MotionSensor.MotionDetected = this.serviceData.movement; - await this.debugLog(`MotionDetected: ${this.MotionSensor.MotionDetected}`); + this.MotionSensor.MotionDetected = this.serviceData.movement + await this.debugLog(`MotionDetected: ${this.MotionSensor.MotionDetected}`) // CurrentAmbientLightLevel if (!this.device.motion?.hide_lightsensor && this.LightSensor?.Service) { - const set_minLux = this.device.blindTilt?.set_minLux ?? 1; - const set_maxLux = this.device.blindTilt?.set_maxLux ?? 6001; - const lightLevel = this.serviceData.lightLevel === 'bright' ? set_maxLux : set_minLux; - this.LightSensor.CurrentAmbientLightLevel = await this.getLightLevel(lightLevel, set_minLux, set_maxLux, 2); - await this.debugLog(`LightLevel: ${this.serviceData.lightLevel}, CurrentAmbientLightLevel: ${this.LightSensor.CurrentAmbientLightLevel}`); + const set_minLux = this.device.blindTilt?.set_minLux ?? 1 + const set_maxLux = this.device.blindTilt?.set_maxLux ?? 6001 + const lightLevel = this.serviceData.lightLevel === 'bright' ? set_maxLux : set_minLux + this.LightSensor.CurrentAmbientLightLevel = await this.getLightLevel(lightLevel, set_minLux, set_maxLux, 2) + await this.debugLog(`LightLevel: ${this.serviceData.lightLevel}, CurrentAmbientLightLevel: ${this.LightSensor.CurrentAmbientLightLevel}`) } // BatteryLevel - this.Battery.BatteryLevel = this.serviceData.battery; - await this.debugLog(`BatteryLevel: ${this.Battery.BatteryLevel}`); + this.Battery.BatteryLevel = this.serviceData.battery + await this.debugLog(`BatteryLevel: ${this.Battery.BatteryLevel}`) // StatusLowBattery this.Battery.StatusLowBattery = this.Battery.BatteryLevel < 10 - ? this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW : this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL; - await this.debugLog(`StatusLowBattery: ${this.Battery.StatusLowBattery}`); + ? this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW + : this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL + await this.debugLog(`StatusLowBattery: ${this.Battery.StatusLowBattery}`) } async openAPIparseStatus(): Promise { - await this.debugLog('openAPIparseStatus'); - await this.debugLog(`(moveDetected) = OpenAPI: (${this.deviceStatus.moveDetected}), current: (${this.MotionSensor.MotionDetected})`); + await this.debugLog('openAPIparseStatus') + await this.debugLog(`(moveDetected) = OpenAPI: (${this.deviceStatus.moveDetected}), current: (${this.MotionSensor.MotionDetected})`) // Motion State - this.MotionSensor.MotionDetected = this.deviceStatus.moveDetected; - await this.debugLog(`MotionDetected: ${this.MotionSensor.MotionDetected}`); + this.MotionSensor.MotionDetected = this.deviceStatus.moveDetected + await this.debugLog(`MotionDetected: ${this.MotionSensor.MotionDetected}`) // CurrentAmbientLightLevel if (!this.device.motion?.hide_lightsensor && this.LightSensor?.Service) { - const set_minLux = this.device.blindTilt?.set_minLux ?? 1; - const set_maxLux = this.device.blindTilt?.set_maxLux ?? 6001; - const lightLevel = this.deviceStatus.brightness === 'bright' ? set_maxLux : set_minLux; - this.LightSensor.CurrentAmbientLightLevel = await this.getLightLevel(lightLevel, set_minLux, set_maxLux, 2); - await this.debugLog(`LightLevel: ${this.deviceStatus.brightness}, CurrentAmbientLightLevel: ${this.LightSensor.CurrentAmbientLightLevel}`); + const set_minLux = this.device.blindTilt?.set_minLux ?? 1 + const set_maxLux = this.device.blindTilt?.set_maxLux ?? 6001 + const lightLevel = this.deviceStatus.brightness === 'bright' ? set_maxLux : set_minLux + this.LightSensor.CurrentAmbientLightLevel = await this.getLightLevel(lightLevel, set_minLux, set_maxLux, 2) + await this.debugLog(`LightLevel: ${this.deviceStatus.brightness}, CurrentAmbientLightLevel: ${this.LightSensor.CurrentAmbientLightLevel}`) } // BatteryLevel - this.Battery.BatteryLevel = this.deviceStatus.battery; - await this.debugLog(`BatteryLevel: ${this.Battery.BatteryLevel}`); + this.Battery.BatteryLevel = this.deviceStatus.battery + await this.debugLog(`BatteryLevel: ${this.Battery.BatteryLevel}`) // StatusLowBattery this.Battery.StatusLowBattery = this.Battery.BatteryLevel < 10 - ? this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW : this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL; - await this.debugLog(`StatusLowBattery: ${this.Battery.StatusLowBattery}`); + ? this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW + : this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL + await this.debugLog(`StatusLowBattery: ${this.Battery.StatusLowBattery}`) // FirmwareVersion if (this.deviceStatus.version) { - const version = this.deviceStatus.version.toString(); - await this.debugLog(`Firmware Version: ${version.replace(/^V|-.*$/g, '')}`); - const deviceVersion = version.replace(/^V|-.*$/g, '') ?? '0.0.0'; + const version = this.deviceStatus.version.toString() + await this.debugLog(`Firmware Version: ${version.replace(/^V|-.*$/g, '')}`) + const deviceVersion = version.replace(/^V|-.*$/g, '') ?? '0.0.0' this.accessory .getService(this.hap.Service.AccessoryInformation)! .setCharacteristic(this.hap.Characteristic.HardwareRevision, deviceVersion) .setCharacteristic(this.hap.Characteristic.FirmwareRevision, deviceVersion) .getCharacteristic(this.hap.Characteristic.FirmwareRevision) - .updateValue(deviceVersion); - this.accessory.context.version = deviceVersion; - await this.debugLog(`version: ${this.accessory.context.version}`); + .updateValue(deviceVersion) + this.accessory.context.version = deviceVersion + await this.debugLog(`version: ${this.accessory.context.version}`) } } async parseStatusWebhook(): Promise { - await this.debugLog('parseStatusWebhook'); - await this.debugLog(`(detectionState) = Webhook: (${this.webhookContext.detectionState}), current: (${this.MotionSensor.MotionDetected})`); + await this.debugLog('parseStatusWebhook') + await this.debugLog(`(detectionState) = Webhook: (${this.webhookContext.detectionState}), current: (${this.MotionSensor.MotionDetected})`) // MotionDetected - this.MotionSensor.MotionDetected = this.webhookContext.detectionState === 'DETECTED' ? true : false; - await this.debugLog(`MotionDetected: ${this.MotionSensor.MotionDetected}`); + this.MotionSensor.MotionDetected = this.webhookContext.detectionState === 'DETECTED' + await this.debugLog(`MotionDetected: ${this.MotionSensor.MotionDetected}`) } /** @@ -235,93 +243,97 @@ export class Motion extends deviceBase { */ async refreshStatus(): Promise { if (!this.device.enableCloudService && this.OpenAPI) { - await this.errorLog(`refreshStatus enableCloudService: ${this.device.enableCloudService}`); - } else if (this.BLE || this.config.options?.BLE) { - await this.BLERefreshStatus(); + await this.errorLog(`refreshStatus enableCloudService: ${this.device.enableCloudService}`) + } else if (this.BLE) { + await this.BLERefreshStatus() } else if (this.OpenAPI && this.platform.config.credentials?.token) { - await this.openAPIRefreshStatus(); + await this.openAPIRefreshStatus() } else { - await this.offlineOff(); - await this.debugWarnLog(`Connection Type: ${this.device.connectionType}, refreshStatus will not happen.`); + await this.offlineOff() + await this.debugWarnLog(`Connection Type: ${this.device.connectionType}, refreshStatus will not happen.`) } } async BLERefreshStatus(): Promise { - await this.debugLog('BLERefreshStatus'); + await this.debugLog('BLERefreshStatus') + const switchbot = await this.switchbotBLE() + if (switchbot === undefined) { + await this.BLERefreshConnection(switchbot) + } else { + // Start to monitor advertisement packets + (async () => { + // Start to monitor advertisement packets + const serviceData = await this.monitorAdvertisementPackets(switchbot) as motionSensorServiceData + // Update HomeKit + if (serviceData.model === SwitchBotBLEModel.MotionSensor && serviceData.modelName === SwitchBotBLEModelName.MotionSensor) { + this.serviceData = serviceData + await this.BLEparseStatus() + await this.updateHomeKitCharacteristics() + } else { + await this.errorLog(`failed to get serviceData, serviceData: ${serviceData}`) + await this.BLERefreshConnection(switchbot) + } + })() + } + } + + async registerPlatformBLE(): Promise { + await this.debugLog('registerPlatformBLE') if (this.config.options?.BLE) { - await this.debugLog('is listening to Platform BLE.'); - this.device.bleMac = this.device.deviceId!.match(/.{1,2}/g)!.join(':').toLowerCase(); - await this.debugLog(`bleMac: ${this.device.bleMac}`); + await this.debugLog('is listening to Platform BLE.') + this.device.bleMac = this.device.deviceId!.match(/.{1,2}/g)!.join(':').toLowerCase() + await this.debugLog(`bleMac: ${this.device.bleMac}`) this.platform.bleEventHandler[this.device.bleMac] = async (context: motionSensorServiceData) => { try { - await this.debugLog(`received BLE: ${JSON.stringify(context)}`); - this.serviceData = context; - await this.BLEparseStatus(); - await this.updateHomeKitCharacteristics(); + await this.debugLog(`received BLE: ${JSON.stringify(context)}`) + this.serviceData = context + await this.BLEparseStatus() + await this.updateHomeKitCharacteristics() } catch (e: any) { - await this.errorLog(`failed to handle BLE. Received: ${JSON.stringify(context)} Error: ${e}`); + await this.errorLog(`failed to handle BLE. Received: ${JSON.stringify(context)} Error: ${e}`) } - }; - } else { - await this.debugLog('is using Device BLE Scanning.'); - const switchbot = await this.switchbotBLE(); - if (switchbot === undefined) { - await this.BLERefreshConnection(switchbot); - } else { - // Start to monitor advertisement packets - (async () => { - // Start to monitor advertisement packets - const serviceData = await this.monitorAdvertisementPackets(switchbot) as motionSensorServiceData; - // Update HomeKit - if (serviceData.model === SwitchBotBLEModel.MotionSensor && serviceData.modelName === SwitchBotBLEModelName.MotionSensor) { - this.serviceData = serviceData; - await this.BLEparseStatus(); - await this.updateHomeKitCharacteristics(); - } else { - await this.errorLog(`failed to get serviceData, serviceData: ${serviceData}`); - await this.BLERefreshConnection(switchbot); - } - })(); } + } else { + await this.debugLog('is not listening to Platform BLE') } } async openAPIRefreshStatus(): Promise { - await this.debugLog('openAPIRefreshStatus'); + await this.debugLog('openAPIRefreshStatus') try { - const { body, statusCode } = await this.deviceRefreshStatus(); - const deviceStatus: any = await body.json(); - await this.debugLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`);; + const { body, statusCode } = await this.deviceRefreshStatus() + const deviceStatus: any = await body.json() + await this.debugLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`) if (await this.successfulStatusCodes(statusCode, deviceStatus)) { - await this.debugSuccessLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`); - this.deviceStatus = deviceStatus.body; - await this.openAPIparseStatus(); - await this.updateHomeKitCharacteristics(); + await this.debugSuccessLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`) + this.deviceStatus = deviceStatus.body + await this.openAPIparseStatus() + await this.updateHomeKitCharacteristics() } else { - await this.debugWarnLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`); - await this.debugWarnLog(statusCode, deviceStatus); + await this.debugWarnLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`) + await this.debugWarnLog(statusCode, deviceStatus) } } catch (e: any) { - await this.apiError(e); - await this.errorLog(`failed openAPIRefreshStatus with ${this.device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`); + await this.apiError(e) + await this.errorLog(`failed openAPIRefreshStatus with ${this.device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`) } } async registerWebhook() { if (this.device.webhook) { - await this.debugLog('is listening webhook.'); + await this.debugLog('is listening webhook.') this.platform.webhookEventHandler[this.device.deviceId] = async (context: motionSensorWebhookContext) => { try { - await this.debugLog(`received Webhook: ${JSON.stringify(context)}`); - this.webhookContext = context; - await this.parseStatusWebhook(); - await this.updateHomeKitCharacteristics(); + await this.debugLog(`received Webhook: ${JSON.stringify(context)}`) + this.webhookContext = context + await this.parseStatusWebhook() + await this.updateHomeKitCharacteristics() } catch (e: any) { - await this.errorLog(`failed to handle webhook. Received: ${JSON.stringify(context)} Error: ${e}`); + await this.errorLog(`failed to handle webhook. Received: ${JSON.stringify(context)} Error: ${e}`) } - }; + } } else { - await this.debugLog('is not listening webhook.'); + await this.debugLog('is not listening webhook.') } } @@ -330,41 +342,37 @@ export class Motion extends deviceBase { */ async updateHomeKitCharacteristics(): Promise { // MotionDetected - await this.updateCharacteristic(this.MotionSensor.Service, this.hap.Characteristic.MotionDetected, - this.MotionSensor.MotionDetected, 'MotionDetected'); + await this.updateCharacteristic(this.MotionSensor.Service, this.hap.Characteristic.MotionDetected, this.MotionSensor.MotionDetected, 'MotionDetected') // BatteryLevel - await this.updateCharacteristic(this.Battery.Service, this.hap.Characteristic.BatteryLevel, - this.Battery.BatteryLevel, 'BatteryLevel'); + await this.updateCharacteristic(this.Battery.Service, this.hap.Characteristic.BatteryLevel, this.Battery.BatteryLevel, 'BatteryLevel') // StatusLowBattery - await this.updateCharacteristic(this.Battery.Service, this.hap.Characteristic.StatusLowBattery, - this.Battery.StatusLowBattery, 'StatusLowBattery'); + await this.updateCharacteristic(this.Battery.Service, this.hap.Characteristic.StatusLowBattery, this.Battery.StatusLowBattery, 'StatusLowBattery') // CurrentAmbientLightLevel if (!this.device.motion?.hide_lightsensor && this.LightSensor?.Service) { - await this.updateCharacteristic(this.LightSensor.Service, this.hap.Characteristic.CurrentAmbientLightLevel, - this.LightSensor.CurrentAmbientLightLevel, 'CurrentAmbientLightLevel'); + await this.updateCharacteristic(this.LightSensor.Service, this.hap.Characteristic.CurrentAmbientLightLevel, this.LightSensor.CurrentAmbientLightLevel, 'CurrentAmbientLightLevel') } } async BLERefreshConnection(switchbot: any): Promise { - await this.errorLog(`wasn't able to establish BLE Connection, node-switchbot: ${switchbot}`); + await this.errorLog(`wasn't able to establish BLE Connection, node-switchbot: ${switchbot}`) if (this.platform.config.credentials?.token && this.device.connectionType === 'BLE/OpenAPI') { - await this.warnLog('Using OpenAPI Connection to Refresh Status'); - await this.openAPIRefreshStatus(); + await this.warnLog('Using OpenAPI Connection to Refresh Status') + await this.openAPIRefreshStatus() } } async offlineOff(): Promise { if (this.device.offline) { - this.MotionSensor.Service.updateCharacteristic(this.hap.Characteristic.MotionDetected, false); + this.MotionSensor.Service.updateCharacteristic(this.hap.Characteristic.MotionDetected, false) } } async apiError(e: any): Promise { - this.MotionSensor.Service.updateCharacteristic(this.hap.Characteristic.MotionDetected, e); - this.Battery.Service.updateCharacteristic(this.hap.Characteristic.BatteryLevel, e); - this.Battery.Service.updateCharacteristic(this.hap.Characteristic.StatusLowBattery, e); + this.MotionSensor.Service.updateCharacteristic(this.hap.Characteristic.MotionDetected, e) + this.Battery.Service.updateCharacteristic(this.hap.Characteristic.BatteryLevel, e) + this.Battery.Service.updateCharacteristic(this.hap.Characteristic.StatusLowBattery, e) if (!this.device.motion?.hide_lightsensor && this.LightSensor?.Service) { - this.LightSensor.Service.updateCharacteristic(this.hap.Characteristic.CurrentAmbientLightLevel, e); + this.LightSensor.Service.updateCharacteristic(this.hap.Characteristic.CurrentAmbientLightLevel, e) } } } diff --git a/src/device/plug.ts b/src/device/plug.ts index 6596c9fb..e45406ce 100644 --- a/src/device/plug.ts +++ b/src/device/plug.ts @@ -2,144 +2,163 @@ * * plug.ts: @switchbot/homebridge-switchbot. */ -import { deviceBase } from './device.js'; -import { SwitchBotBLEModel, SwitchBotBLEModelName } from 'node-switchbot'; -import { Subject, debounceTime, interval, skipWhile, take, tap } from 'rxjs'; - -import type { devicesConfig } from '../settings.js'; -import type { device } from '../types/devicelist.js'; -import type { SwitchBotPlatform } from '../platform.js'; -import type { CharacteristicValue, PlatformAccessory, Service } from 'homebridge'; -import type { plugStatus, plugMiniStatus } from '../types/devicestatus.js'; -import type { plugMiniUSServiceData, plugMiniJPServiceData } from '../types/bledevicestatus.js'; -import type { plugMiniJPWebhookContext, plugMiniUSWebhookContext, plugWebhookContext } from '../types/devicewebhookstatus.js'; +import type { CharacteristicValue, PlatformAccessory, Service } from 'homebridge' + +import type { SwitchBotPlatform } from '../platform.js' +import type { devicesConfig } from '../settings.js' +import type { plugMiniJPServiceData, plugMiniUSServiceData } from '../types/bledevicestatus.js' +import type { device } from '../types/devicelist.js' +import type { plugMiniStatus, plugStatus } from '../types/devicestatus.js' +import type { plugMiniJPWebhookContext, plugMiniUSWebhookContext, plugWebhookContext } from '../types/devicewebhookstatus.js' + +/* +* For Testing Locally: +* import { SwitchBotBLEModel, SwitchBotBLEModelName } from '/Users/Shared/GitHub/OpenWonderLabs/node-switchbot/dist/index.js'; +*/ +import { SwitchBotBLEModel, SwitchBotBLEModelName } from 'node-switchbot' +import { debounceTime, interval, skipWhile, Subject, take, tap } from 'rxjs' + +import { deviceBase } from './device.js' + export class Plug extends deviceBase { // Services private Outlet: { - Name: CharacteristicValue; - Service: Service; - On: CharacteristicValue; - }; + Name: CharacteristicValue + Service: Service + On: CharacteristicValue + } // OpenAPI - deviceStatus!: plugStatus | plugMiniStatus; + deviceStatus!: plugStatus | plugMiniStatus - //Webhook - webhookContext!: plugWebhookContext | plugMiniUSWebhookContext | plugMiniJPWebhookContext; + // Webhook + webhookContext!: plugWebhookContext | plugMiniUSWebhookContext | plugMiniJPWebhookContext // BLE - serviceData!: plugMiniUSServiceData | plugMiniJPServiceData; + serviceData!: plugMiniUSServiceData | plugMiniJPServiceData // Updates - plugUpdateInProgress!: boolean; - doPlugUpdate!: Subject; + plugUpdateInProgress!: boolean + doPlugUpdate!: Subject constructor( readonly platform: SwitchBotPlatform, accessory: PlatformAccessory, device: device & devicesConfig, ) { - super(platform, accessory, device); + super(platform, accessory, device) // Set category - accessory.category = this.hap.Categories.OUTLET; + accessory.category = this.hap.Categories.OUTLET // this is subject we use to track when we need to POST changes to the SwitchBot API - this.doPlugUpdate = new Subject(); - this.plugUpdateInProgress = false; + this.doPlugUpdate = new Subject() + this.plugUpdateInProgress = false // Initialize Outlet Service - accessory.context.Outlet = accessory.context.Outlet ?? {}; + accessory.context.Outlet = accessory.context.Outlet ?? {} this.Outlet = { Name: accessory.displayName, Service: accessory.getService(this.hap.Service.Outlet) ?? accessory.addService(this.hap.Service.Outlet) as Service, On: accessory.context.On || false, - }; - accessory.context.Outlet = this.Outlet as object; + } + accessory.context.Outlet = this.Outlet as object // Initialize Outlet Characteristics - this.Outlet.Service - .setCharacteristic(this.hap.Characteristic.Name, this.Outlet.Name) - .getCharacteristic(this.hap.Characteristic.On) - .onGet(() => { - return this.Outlet.On; - }) - .onSet(this.OnSet.bind(this)); + this.Outlet.Service.setCharacteristic(this.hap.Characteristic.Name, this.Outlet.Name).getCharacteristic(this.hap.Characteristic.On).onGet(() => { + return this.Outlet.On + }).onSet(this.OnSet.bind(this)) // Retrieve initial values and updateHomekit - this.debugLog('Retrieve initial values and update Homekit'); - this.refreshStatus(); + try { + this.debugLog('Retrieve initial values and update Homekit') + this.refreshStatus() + } catch (e: any) { + this.errorLog(`failed to retrieve initial values and update Homekit, Error: ${e}`) + } - //regisiter webhook event handler - this.debugLog('Registering Webhook Event Handler'); - this.registerWebhook(); + // regisiter webhook event handler if enabled + try { + this.debugLog('Registering Webhook Event Handler') + this.registerWebhook() + } catch (e: any) { + this.errorLog(`failed to registerWebhook, Error: ${e}`) + } + + // regisiter platform BLE event handler if enabled + try { + this.debugLog('Registering Platform BLE Event Handler') + this.registerPlatformBLE() + } catch (e: any) { + this.errorLog(`failed to registerPlatformBLE, Error: ${e}`) + } // Start an update interval interval(this.deviceRefreshRate * 1000) .pipe(skipWhile(() => this.plugUpdateInProgress)) .subscribe(async () => { - await this.refreshStatus(); - }); + await this.refreshStatus() + }) // Watch for Plug change events // We put in a debounce of 100ms so we don't make duplicate calls this.doPlugUpdate .pipe( tap(() => { - this.plugUpdateInProgress = true; + this.plugUpdateInProgress = true }), debounceTime(this.devicePushRate * 1000), ) .subscribe(async () => { try { - await this.pushChanges(); + await this.pushChanges() } catch (e: any) { - await this.apiError(e); - await this.errorLog(`failed pushChanges with ${device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`); + await this.apiError(e) + await this.errorLog(`failed pushChanges with ${device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`) } - this.plugUpdateInProgress = false; - }); + this.plugUpdateInProgress = false + }) } async BLEparseStatus(): Promise { - await this.debugLog('BLEparseStatus'); - await this.debugLog(`(powerState) = BLE: (${this.serviceData.state}), current:(${this.Outlet.On})`); + await this.debugLog('BLEparseStatus') + await this.debugLog(`(powerState) = BLE: (${this.serviceData.state}), current:(${this.Outlet.On})`) // On - this.Outlet.On = this.serviceData.state === 'on' ? true : false; - await this.debugLog(`On: ${this.Outlet.On}`); + this.Outlet.On = this.serviceData.state === 'on' + await this.debugLog(`On: ${this.Outlet.On}`) } async openAPIparseStatus() { - await this.debugLog('openAPIparseStatus'); - await this.debugLog(`(powerState) = OpenAPI: (${this.deviceStatus.power}), current:(${this.Outlet.On})`); + await this.debugLog('openAPIparseStatus') + await this.debugLog(`(powerState) = OpenAPI: (${this.deviceStatus.power}), current:(${this.Outlet.On})`) // On - this.Outlet.On = this.deviceStatus.power === 'on' ? true : false; - await this.debugLog(`On: ${this.Outlet.On}`); + this.Outlet.On = this.deviceStatus.power === 'on' + await this.debugLog(`On: ${this.Outlet.On}`) // Firmware Version if (this.deviceStatus.version) { - const version = this.deviceStatus.version.toString(); - await this.debugLog(`Firmware Version: ${version.replace(/^V|-.*$/g, '')}`); - const deviceVersion = version.replace(/^V|-.*$/g, '') ?? '0.0.0'; + const version = this.deviceStatus.version.toString() + await this.debugLog(`Firmware Version: ${version.replace(/^V|-.*$/g, '')}`) + const deviceVersion = version.replace(/^V|-.*$/g, '') ?? '0.0.0' this.accessory .getService(this.hap.Service.AccessoryInformation)! .setCharacteristic(this.hap.Characteristic.HardwareRevision, deviceVersion) .setCharacteristic(this.hap.Characteristic.FirmwareRevision, deviceVersion) .getCharacteristic(this.hap.Characteristic.FirmwareRevision) - .updateValue(deviceVersion); - this.accessory.context.version = deviceVersion; - await this.debugSuccessLog(`version: ${this.accessory.context.version}`); + .updateValue(deviceVersion) + this.accessory.context.version = deviceVersion + await this.debugSuccessLog(`version: ${this.accessory.context.version}`) } } async parseStatusWebhook(): Promise { - await this.debugLog('parseStatusWebhook'); - await this.debugLog(`(powerState) = Webhook: (${this.webhookContext.powerState}), current:(${this.Outlet.On})`); + await this.debugLog('parseStatusWebhook') + await this.debugLog(`(powerState) = Webhook: (${this.webhookContext.powerState}), current:(${this.Outlet.On})`) // On - this.Outlet.On = this.webhookContext.powerState === 'ON' ? true : false; - await this.debugLog(`On: ${this.Outlet.On}`); + this.Outlet.On = this.webhookContext.powerState === 'ON' + await this.debugLog(`On: ${this.Outlet.On}`) } /** @@ -147,195 +166,198 @@ export class Plug extends deviceBase { */ async refreshStatus(): Promise { if (!this.device.enableCloudService && this.OpenAPI) { - await this.errorLog(`refreshStatus enableCloudService: ${this.device.enableCloudService}`); - } else if (this.BLE || this.config.options?.BLE) { - await this.BLERefreshStatus(); + await this.errorLog(`refreshStatus enableCloudService: ${this.device.enableCloudService}`) + } else if (this.BLE) { + await this.BLERefreshStatus() } else if (this.OpenAPI && this.platform.config.credentials?.token) { - await this.openAPIRefreshStatus(); + await this.openAPIRefreshStatus() } else { - await this.offlineOff(); - await this.debugWarnLog(`Connection Type: ${this.device.connectionType}, refreshStatus will not happen.`); + await this.offlineOff() + await this.debugWarnLog(`Connection Type: ${this.device.connectionType}, refreshStatus will not happen.`) } } async BLERefreshStatus(): Promise { - await this.debugLog('BLERefreshStatus'); + await this.debugLog('BLERefreshStatus') + const switchbot = await this.switchbotBLE() + if (switchbot === undefined) { + await this.BLERefreshConnection(switchbot) + } else { + // Start to monitor advertisement packets + (async () => { + // Start to monitor advertisement packets + const serviceData = await this.monitorAdvertisementPackets(switchbot) as plugMiniUSServiceData | plugMiniJPServiceData + // Update HomeKit + if ((serviceData.model === SwitchBotBLEModel.PlugMiniUS || SwitchBotBLEModel.PlugMiniJP) + && serviceData.modelName === (SwitchBotBLEModelName.PlugMini || SwitchBotBLEModelName.PlugMini)) { + this.serviceData = serviceData + await this.BLEparseStatus() + await this.updateHomeKitCharacteristics() + } else { + await this.errorLog(`failed to get serviceData, serviceData: ${serviceData}`) + await this.BLERefreshConnection(switchbot) + } + })() + } + } + + async registerPlatformBLE(): Promise { + await this.debugLog('registerPlatformBLE') if (this.config.options?.BLE) { - await this.debugLog('is listening to Platform BLE.'); - this.device.bleMac = this.device.deviceId!.match(/.{1,2}/g)!.join(':').toLowerCase(); - await this.debugLog(`bleMac: ${this.device.bleMac}`); + await this.debugLog('is listening to Platform BLE.') + this.device.bleMac = this.device.deviceId!.match(/.{1,2}/g)!.join(':').toLowerCase() + await this.debugLog(`bleMac: ${this.device.bleMac}`) this.platform.bleEventHandler[this.device.bleMac] = async (context: plugMiniUSServiceData | plugMiniJPServiceData) => { try { - await this.debugLog(`received BLE: ${JSON.stringify(context)}`); - this.serviceData = context; - await this.BLEparseStatus(); - await this.updateHomeKitCharacteristics(); + await this.debugLog(`received BLE: ${JSON.stringify(context)}`) + this.serviceData = context + await this.BLEparseStatus() + await this.updateHomeKitCharacteristics() } catch (e: any) { - await this.errorLog(`failed to handle BLE. Received: ${JSON.stringify(context)} Error: ${e}`); + await this.errorLog(`failed to handle BLE. Received: ${JSON.stringify(context)} Error: ${e}`) } - }; - } else { - await this.debugLog('is using Device BLE Scanning.'); - const switchbot = await this.switchbotBLE(); - if (switchbot === undefined) { - await this.BLERefreshConnection(switchbot); - } else { - // Start to monitor advertisement packets - (async () => { - // Start to monitor advertisement packets - const serviceData = await this.monitorAdvertisementPackets(switchbot) as plugMiniUSServiceData | plugMiniJPServiceData; - // Update HomeKit - if ((serviceData.model === SwitchBotBLEModel.PlugMiniUS || SwitchBotBLEModel.PlugMiniJP) - && serviceData.modelName === (SwitchBotBLEModelName.PlugMini || SwitchBotBLEModelName.PlugMini)) { - this.serviceData = serviceData; - await this.BLEparseStatus(); - await this.updateHomeKitCharacteristics(); - } else { - await this.errorLog(`failed to get serviceData, serviceData: ${serviceData}`); - await this.BLERefreshConnection(switchbot); - } - })(); } + } else { + await this.debugLog('is not listening to Platform BLE') } } async openAPIRefreshStatus(): Promise { - await this.debugLog('openAPIRefreshStatus'); + await this.debugLog('openAPIRefreshStatus') try { - const { body, statusCode } = await this.deviceRefreshStatus(); - const deviceStatus: any = await body.json(); - await this.debugLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`);; + const { body, statusCode } = await this.deviceRefreshStatus() + const deviceStatus: any = await body.json() + await this.debugLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`) if (await this.successfulStatusCodes(statusCode, deviceStatus)) { - await this.debugSuccessLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`); - this.deviceStatus = deviceStatus.body; - await this.openAPIparseStatus(); - await this.updateHomeKitCharacteristics(); + await this.debugSuccessLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`) + this.deviceStatus = deviceStatus.body + await this.openAPIparseStatus() + await this.updateHomeKitCharacteristics() } else { - await this.debugWarnLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`); - await this.debugWarnLog(statusCode, deviceStatus); + await this.debugWarnLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`) + await this.debugWarnLog(statusCode, deviceStatus) } } catch (e: any) { - await this.apiError(e); - await this.errorLog(`failed openAPIRefreshStatus with ${this.device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`); + await this.apiError(e) + await this.errorLog(`failed openAPIRefreshStatus with ${this.device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`) } } async registerWebhook() { if (this.device.webhook) { - await this.debugLog('is listening webhook.'); - this.platform.webhookEventHandler[this.device.deviceId] = async (context: plugWebhookContext | plugMiniUSWebhookContext - | plugMiniJPWebhookContext) => { + await this.debugLog('is listening webhook.') + this.platform.webhookEventHandler[this.device.deviceId] = async (context: plugWebhookContext | plugMiniUSWebhookContext | plugMiniJPWebhookContext) => { try { - await this.debugLog(`received Webhook: ${JSON.stringify(context)}`); - this.webhookContext = context; - await this.parseStatusWebhook(); - await this.updateHomeKitCharacteristics(); + await this.debugLog(`received Webhook: ${JSON.stringify(context)}`) + this.webhookContext = context + await this.parseStatusWebhook() + await this.updateHomeKitCharacteristics() } catch (e: any) { - await this.errorLog(`failed to handle webhook. Received: ${JSON.stringify(context)} Error: ${e}`); + await this.errorLog(`failed to handle webhook. Received: ${JSON.stringify(context)} Error: ${e}`) } - }; + } } else { - await this.debugLog('is not listening webhook.'); + await this.debugLog('is not listening webhook.') } } /** * Pushes the requested changes to the SwitchBot API - * deviceType commandType Command command parameter Description - * Plug - "command" "turnOff" "default" = set to OFF state - * Plug - "command" "turnOn" "default" = set to ON state - * Plug Mini (US/JP) - "command" turnOn default = set to ON state - * Plug Mini (US/JP) - "command" turnOff default = set to OFF state - * Plug Mini (US/JP) - "command" toggle default = toggle state + * deviceType commandType Command command parameter Description + * Plug - "command" "turnOff" "default" = set to OFF state + * Plug - "command" "turnOn" "default" = set to ON state + * Plug Mini (US/JP) - "command" "turnOn" "default" = set to ON state + * Plug Mini (US/JP) - "command" "turnOff" "default" = set to OFF state + * Plug Mini (US/JP) - "command" "toggle" "default" = toggle state */ async pushChanges(): Promise { if (!this.device.enableCloudService && this.OpenAPI) { - await this.errorLog(`pushChanges enableCloudService: ${this.device.enableCloudService}`); + await this.errorLog(`pushChanges enableCloudService: ${this.device.enableCloudService}`) } else if (this.BLE) { - await this.BLEpushChanges(); + await this.BLEpushChanges() } else if (this.OpenAPI && this.platform.config.credentials?.token) { - await this.openAPIpushChanges(); + await this.openAPIpushChanges() } else { - await this.offlineOff(); - await this.debugWarnLog(`Connection Type: ${this.device.connectionType}, pushChanges will not happen.`); + await this.offlineOff() + await this.debugWarnLog(`Connection Type: ${this.device.connectionType}, pushChanges will not happen.`) } // Refresh the status from the API interval(15000) .pipe(skipWhile(() => this.plugUpdateInProgress)) .pipe(take(1)) .subscribe(async () => { - await this.refreshStatus(); - }); + await this.refreshStatus() + }) } async BLEpushChanges(): Promise { - await this.debugLog('BLEpushChanges'); + await this.debugLog('BLEpushChanges') if (this.Outlet.On !== this.accessory.context.On) { - await this.debugLog(`BLEpushChanges On: ${this.Outlet.On}, OnCached: ${this.accessory.context.On}`); - const switchbot = await this.platform.connectBLE(this.accessory, this.device); - await this.convertBLEAddress(); + await this.debugLog(`BLEpushChanges On: ${this.Outlet.On}, OnCached: ${this.accessory.context.On}`) + const switchbot = await this.platform.connectBLE(this.accessory, this.device) + await this.convertBLEAddress() if (switchbot !== false) { switchbot .discover({ model: this.device.bleModel, id: this.device.bleMac }) .then(async (device_list: any) => { - await this.infoLog(`On: ${this.Outlet.On}`); + await this.infoLog(`On: ${this.Outlet.On}`) return await this.retryBLE({ max: await this.maxRetryBLE(), fn: async () => { if (this.Outlet.On) { - return await device_list[0].turnOn({ id: this.device.bleMac }); + return await device_list[0].turnOn({ id: this.device.bleMac }) } else { - return await device_list[0].turnOff({ id: this.device.bleMac }); + return await device_list[0].turnOff({ id: this.device.bleMac }) } }, - }); + }) }) .then(async () => { - await this.successLog(`On: ${this.Outlet.On} sent over SwitchBot BLE, sent successfully`); - await this.updateHomeKitCharacteristics(); + await this.successLog(`On: ${this.Outlet.On} sent over SwitchBot BLE, sent successfully`) + await this.updateHomeKitCharacteristics() }) .catch(async (e: any) => { - await this.apiError(e); - await this.errorLog(`failed BLEpushChanges with ${this.device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`); - await this.BLEPushConnection(); - }); + await this.apiError(e) + await this.errorLog(`failed BLEpushChanges with ${this.device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`) + await this.BLEPushConnection() + }) } else { - await this.errorLog(`wasn't able to establish BLE Connection, node-switchbot: ${switchbot}`); - await this.BLEPushConnection(); + await this.errorLog(`wasn't able to establish BLE Connection, node-switchbot: ${switchbot}`) + await this.BLEPushConnection() } } else { - await this.debugLog(`No changes (BLEpushChanges), On: ${this.Outlet.On}, OnCached: ${this.accessory.context.On}`); + await this.debugLog(`No changes (BLEpushChanges), On: ${this.Outlet.On}, OnCached: ${this.accessory.context.On}`) } } async openAPIpushChanges() { - await this.debugLog('openAPIpushChanges'); + await this.debugLog('openAPIpushChanges') if (this.Outlet.On !== this.accessory.context.On) { - const command = this.Outlet.On ? 'turnOn' : 'turnOff'; + const command = this.Outlet.On ? 'turnOn' : 'turnOff' const bodyChange = JSON.stringify({ command: `${command}`, parameter: 'default', commandType: 'command', - }); - await this.debugLog(`SwitchBot OpenAPI bodyChange: ${JSON.stringify(bodyChange)}`); + }) + await this.debugLog(`SwitchBot OpenAPI bodyChange: ${JSON.stringify(bodyChange)}`) try { - const { body, statusCode } = await this.pushChangeRequest(bodyChange); - const deviceStatus: any = await body.json(); - await this.debugLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`); + const { body, statusCode } = await this.pushChangeRequest(bodyChange) + const deviceStatus: any = await body.json() + await this.debugLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`) if (await this.successfulStatusCodes(statusCode, deviceStatus)) { - await this.debugSuccessLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`); - await this.updateHomeKitCharacteristics(); + await this.debugSuccessLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`) + await this.updateHomeKitCharacteristics() } else { - await this.statusCode(statusCode); - await this.statusCode(deviceStatus.statusCode); + await this.statusCode(statusCode) + await this.statusCode(deviceStatus.statusCode) } } catch (e: any) { - await this.apiError(e); - await this.errorLog(`failed openAPIpushChanges with ${this.device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`); + await this.apiError(e) + await this.errorLog(`failed openAPIpushChanges with ${this.device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`) } } else { - await this.debugLog(`No changes (openAPIpushChanges), On: ${this.Outlet.On}, OnCached: ${this.accessory.context.On}`); + await this.debugLog(`No changes (openAPIpushChanges), On: ${this.Outlet.On}, OnCached: ${this.accessory.context.On}`) } } @@ -344,43 +366,42 @@ export class Plug extends deviceBase { */ async OnSet(value: CharacteristicValue): Promise { if (this.Outlet.On !== this.accessory.context.On) { - await this.infoLog(`Set On: ${value}`); + await this.infoLog(`Set On: ${value}`) } else { - await this.debugLog(`No Changes, On: ${value}`); + await this.debugLog(`No Changes, On: ${value}`) } - this.Outlet.On = value; - this.doPlugUpdate.next(); + this.Outlet.On = value + this.doPlugUpdate.next() } async updateHomeKitCharacteristics(): Promise { // On - await this.updateCharacteristic(this.Outlet.Service, this.hap.Characteristic.On, - this.Outlet.On, 'On'); + await this.updateCharacteristic(this.Outlet.Service, this.hap.Characteristic.On, this.Outlet.On, 'On') } async BLEPushConnection() { if (this.platform.config.credentials?.token && this.device.connectionType === 'BLE/OpenAPI') { - await this.warnLog('Using OpenAPI Connection to Push Changes'); - await this.openAPIpushChanges(); + await this.warnLog('Using OpenAPI Connection to Push Changes') + await this.openAPIpushChanges() } } async BLERefreshConnection(switchbot: any): Promise { - await this.errorLog(`wasn't able to establish BLE Connection, node-switchbot: ${switchbot}`); + await this.errorLog(`wasn't able to establish BLE Connection, node-switchbot: ${switchbot}`) if (this.platform.config.credentials?.token && this.device.connectionType === 'BLE/OpenAPI') { - await this.warnLog('Using OpenAPI Connection to Refresh Status'); - await this.openAPIRefreshStatus(); + await this.warnLog('Using OpenAPI Connection to Refresh Status') + await this.openAPIRefreshStatus() } } async offlineOff(): Promise { if (this.device.offline) { - this.Outlet.Service.updateCharacteristic(this.hap.Characteristic.On, false); + this.Outlet.Service.updateCharacteristic(this.hap.Characteristic.On, false) } } async apiError(e: any): Promise { - this.Outlet.Service.updateCharacteristic(this.hap.Characteristic.On, e); + this.Outlet.Service.updateCharacteristic(this.hap.Characteristic.On, e) } } diff --git a/src/device/robotvacuumcleaner.ts b/src/device/robotvacuumcleaner.ts index f6d6c0b9..ebe6cd98 100644 --- a/src/device/robotvacuumcleaner.ts +++ b/src/device/robotvacuumcleaner.ts @@ -2,245 +2,254 @@ * * robotvacuumcleaner.ts: @switchbot/homebridge-switchbot. */ -import { deviceBase } from './device.js'; -import { SwitchBotBLEModel, SwitchBotBLEModelName } from 'node-switchbot'; -import { Subject, debounceTime, interval, skipWhile, take, tap } from 'rxjs'; - -import type { devicesConfig } from '../settings.js'; -import type { device } from '../types/devicelist.js'; -import type { SwitchBotPlatform } from '../platform.js'; -import type { robotVacuumCleanerServiceData } from '../types/bledevicestatus.js'; -import type { Service, PlatformAccessory, CharacteristicValue } from 'homebridge'; -import type { robotVacuumCleanerS1Status, robotVacuumCleanerS1PlusStatus, floorCleaningRobotS10Status } from '../types/devicestatus.js'; +import type { CharacteristicValue, PlatformAccessory, Service } from 'homebridge' + +import type { SwitchBotPlatform } from '../platform.js' +import type { devicesConfig } from '../settings.js' +import type { robotVacuumCleanerServiceData } from '../types/bledevicestatus.js' +import type { device } from '../types/devicelist.js' +import type { floorCleaningRobotS10Status, robotVacuumCleanerS1PlusStatus, robotVacuumCleanerS1Status } from '../types/devicestatus.js' import type { floorCleaningRobotS10WebhookContext, robotVacuumCleanerS1PlusWebhookContext, robotVacuumCleanerS1WebhookContext, -} from '../types/devicewebhookstatus.js'; +} from '../types/devicewebhookstatus.js' + +/* +* For Testing Locally: +* import { SwitchBotBLEModel, SwitchBotBLEModelName } from '/Users/Shared/GitHub/OpenWonderLabs/node-switchbot/dist/index.js'; +*/ +import { SwitchBotBLEModel, SwitchBotBLEModelName } from 'node-switchbot' +import { debounceTime, interval, skipWhile, Subject, take, tap } from 'rxjs' + +import { deviceBase } from './device.js' export class RobotVacuumCleaner extends deviceBase { // Services private LightBulb: { - Name: CharacteristicValue; - Service: Service; - On: CharacteristicValue; - Brightness: CharacteristicValue; - }; + Name: CharacteristicValue + Service: Service + On: CharacteristicValue + Brightness: CharacteristicValue + } private Battery: { - Name: CharacteristicValue; - Service: Service; - BatteryLevel: CharacteristicValue; - StatusLowBattery: CharacteristicValue; - ChargingState: CharacteristicValue; - }; + Name: CharacteristicValue + Service: Service + BatteryLevel: CharacteristicValue + StatusLowBattery: CharacteristicValue + ChargingState: CharacteristicValue + } // OpenAPI - deviceStatus!: robotVacuumCleanerS1Status | robotVacuumCleanerS1PlusStatus | floorCleaningRobotS10Status; + deviceStatus!: robotVacuumCleanerS1Status | robotVacuumCleanerS1PlusStatus | floorCleaningRobotS10Status - //Webhook - webhookContext!: robotVacuumCleanerS1WebhookContext | robotVacuumCleanerS1PlusWebhookContext | floorCleaningRobotS10WebhookContext; + // Webhook + webhookContext!: robotVacuumCleanerS1WebhookContext | robotVacuumCleanerS1PlusWebhookContext | floorCleaningRobotS10WebhookContext // BLE - serviceData!: robotVacuumCleanerServiceData; + serviceData!: robotVacuumCleanerServiceData // Updates - robotVacuumCleanerUpdateInProgress!: boolean; - doRobotVacuumCleanerUpdate!: Subject; + robotVacuumCleanerUpdateInProgress!: boolean + doRobotVacuumCleanerUpdate!: Subject constructor( readonly platform: SwitchBotPlatform, accessory: PlatformAccessory, device: device & devicesConfig, ) { - super(platform, accessory, device); + super(platform, accessory, device) // Set category - accessory.category = this.hap.Categories.OTHER; + accessory.category = this.hap.Categories.OTHER // this is subject we use to track when we need to POST changes to the SwitchBot API - this.doRobotVacuumCleanerUpdate = new Subject(); - this.robotVacuumCleanerUpdateInProgress = false; + this.doRobotVacuumCleanerUpdate = new Subject() + this.robotVacuumCleanerUpdateInProgress = false // Initialize Lightbulb Service - accessory.context.LightBulb = accessory.context.LightBulb ?? {}; + accessory.context.LightBulb = accessory.context.LightBulb ?? {} this.LightBulb = { Name: accessory.displayName, Service: accessory.getService(this.hap.Service.Lightbulb) ?? accessory.addService(this.hap.Service.Lightbulb) as Service, On: accessory.context.On ?? false, Brightness: accessory.context.Brightness ?? 0, - }; - accessory.context.LightBulb = this.LightBulb as object; + } + accessory.context.LightBulb = this.LightBulb as object // Initialize LightBulb Characteristics - this.LightBulb.Service - .setCharacteristic(this.hap.Characteristic.Name, this.LightBulb.Name) - .getCharacteristic(this.hap.Characteristic.On) - .onGet(() => { - return this.LightBulb.On; - }) - .onSet(this.OnSet.bind(this)); + this.LightBulb.Service.setCharacteristic(this.hap.Characteristic.Name, this.LightBulb.Name).getCharacteristic(this.hap.Characteristic.On).onGet(() => { + return this.LightBulb.On + }).onSet(this.OnSet.bind(this)) // Initialize LightBulb Brightness Characteristic - this.LightBulb.Service - .getCharacteristic(this.hap.Characteristic.Brightness) - .setProps({ - minStep: 25, - minValue: 0, - maxValue: 100, - validValues: [0, 25, 50, 75, 100], - validValueRanges: [0, 100], - }) - .onGet(() => { - return this.LightBulb.Brightness; - }) - .onSet(this.BrightnessSet.bind(this)); + this.LightBulb.Service.getCharacteristic(this.hap.Characteristic.Brightness).setProps({ + minStep: 25, + minValue: 0, + maxValue: 100, + validValues: [0, 25, 50, 75, 100], + validValueRanges: [0, 100], + }).onGet(() => { + return this.LightBulb.Brightness + }).onSet(this.BrightnessSet.bind(this)) // Initialize Battery Service - accessory.context.Battery = accessory.context.Battery ?? {}; + accessory.context.Battery = accessory.context.Battery ?? {} this.Battery = { Name: `${accessory.displayName} Battery`, Service: accessory.getService(this.hap.Service.Battery) ?? accessory.addService(this.hap.Service.Battery) as Service, BatteryLevel: accessory.context.BatteryLevel ?? 100, StatusLowBattery: accessory.context.StatusLowBattery ?? this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL, ChargingState: accessory.context.ChargingState ?? this.hap.Characteristic.ChargingState.NOT_CHARGING, - }; - accessory.context.Battery = this.Battery as object; + } + accessory.context.Battery = this.Battery as object // Initialize Battery Characteristics - this.Battery.Service - .setCharacteristic(this.hap.Characteristic.Name, this.Battery.Name) - .getCharacteristic(this.hap.Characteristic.BatteryLevel) - .onGet(() => { - return this.Battery.BatteryLevel; - }); - - this.Battery.Service - .getCharacteristic(this.hap.Characteristic.StatusLowBattery) - .onGet(() => { - return this.Battery.StatusLowBattery; - }); - - this.Battery.Service - .getCharacteristic(this.hap.Characteristic.ChargingState) - .onGet(() => { - return this.Battery.ChargingState; - }); + this.Battery.Service.setCharacteristic(this.hap.Characteristic.Name, this.Battery.Name).getCharacteristic(this.hap.Characteristic.BatteryLevel).onGet(() => { + return this.Battery.BatteryLevel + }) + + this.Battery.Service.getCharacteristic(this.hap.Characteristic.StatusLowBattery).onGet(() => { + return this.Battery.StatusLowBattery + }) + + this.Battery.Service.getCharacteristic(this.hap.Characteristic.ChargingState).onGet(() => { + return this.Battery.ChargingState + }) // Retrieve initial values and updateHomekit - this.debugLog('Retrieve initial values and update Homekit'); - this.refreshStatus(); + try { + this.debugLog('Retrieve initial values and update Homekit') + this.refreshStatus() + } catch (e: any) { + this.errorLog(`failed to retrieve initial values and update Homekit, Error: ${e}`) + } - //regisiter webhook event handler - this.debugLog('Registering Webhook Event Handler'); - this.registerWebhook(); + // regisiter webhook event handler if enabled + try { + this.debugLog('Registering Webhook Event Handler') + this.registerWebhook() + } catch (e: any) { + this.errorLog(`failed to registerWebhook, Error: ${e}`) + } + + // regisiter platform BLE event handler if enabled + try { + this.debugLog('Registering Platform BLE Event Handler') + this.registerPlatformBLE() + } catch (e: any) { + this.errorLog(`failed to registerPlatformBLE, Error: ${e}`) + } // Start an update interval interval(this.deviceRefreshRate * 1000) .pipe(skipWhile(() => this.robotVacuumCleanerUpdateInProgress)) .subscribe(async () => { - await this.refreshStatus(); - }); + await this.refreshStatus() + }) // Watch for Plug change events // We put in a debounce of 100ms so we don't make duplicate calls this.doRobotVacuumCleanerUpdate .pipe( tap(() => { - this.robotVacuumCleanerUpdateInProgress = true; + this.robotVacuumCleanerUpdateInProgress = true }), debounceTime(this.devicePushRate * 1000), ) .subscribe(async () => { try { - await this.pushChanges(); + await this.pushChanges() } catch (e: any) { - await this.apiError(e); - await this.errorLog(`failed pushChanges with ${device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`); + await this.apiError(e) + await this.errorLog(`failed pushChanges with ${device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`) } - this.robotVacuumCleanerUpdateInProgress = false; - }); + this.robotVacuumCleanerUpdateInProgress = false + }) } async BLEparseStatus(): Promise { - await this.debugLog('BLEparseStatus'); - await this.debugLog(`(state, battery) = BLE: (${this.serviceData.state}, ${this.serviceData.battery}),` - + ` current: (${this.LightBulb.On}, ${this.Battery.BatteryLevel})`); + await this.debugLog('BLEparseStatus') + await this.debugLog(`(state, battery) = BLE: (${this.serviceData.state}, ${this.serviceData.battery}), current: (${this.LightBulb.On}, ${this.Battery.BatteryLevel})`) // On - this.LightBulb.On = this.serviceData.state === 'on' ? true : false; - await this.debugLog(`On: ${this.LightBulb.On}`); + this.LightBulb.On = !!['InDustCollecting', 'Clearing'].includes(this.deviceStatus.workingStatus) + await this.debugLog(`On: ${this.LightBulb.On}`) // BatteryLevel - this.Battery.BatteryLevel = this.serviceData.battery; - await this.debugLog(`BatteryLevel: ${this.Battery.BatteryLevel}`); + this.Battery.BatteryLevel = this.serviceData.battery + await this.debugLog(`BatteryLevel: ${this.Battery.BatteryLevel}`) // StatusLowBattery this.Battery.StatusLowBattery = this.Battery.BatteryLevel < 10 - ? this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW : this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL; - await this.debugLog(`StatusLowBattery: ${this.Battery.StatusLowBattery}`); + ? this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW + : this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL + await this.debugLog(`StatusLowBattery: ${this.Battery.StatusLowBattery}`) } async openAPIparseStatus() { - await this.debugLog('openAPIparseStatus'); - await this.debugLog(`(onlineStatus, battery, workingStatus) = API: (${this.deviceStatus.onlineStatus}, ${this.deviceStatus.battery},` - + ` ${this.deviceStatus.workingStatus}), current: (${this.LightBulb.On}, ${this.Battery.BatteryLevel}, ${this.Battery.ChargingState})`); + await this.debugLog('openAPIparseStatus') + await this.debugLog(`(onlineStatus, battery, workingStatus) = API: (${this.deviceStatus.onlineStatus}, ${this.deviceStatus.battery}, ${this.deviceStatus.workingStatus}), current: (${this.LightBulb.On}, ${this.Battery.BatteryLevel}, ${this.Battery.ChargingState})`) // On - this.LightBulb.On = this.deviceStatus.onlineStatus === 'online' ? true : false; - await this.debugLog(`On: ${this.LightBulb.On}`); + this.LightBulb.On = this.deviceStatus.onlineStatus === 'online' + await this.debugLog(`On: ${this.LightBulb.On}`) // BatteryLevel - this.Battery.BatteryLevel = this.deviceStatus.battery; - await this.debugLog(`BatteryLevel: ${this.Battery.BatteryLevel}`); + this.Battery.BatteryLevel = this.deviceStatus.battery + await this.debugLog(`BatteryLevel: ${this.Battery.BatteryLevel}`) // StatusLowBattery this.Battery.StatusLowBattery = this.Battery.BatteryLevel < 10 - ? this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW : this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL; - await this.debugLog(`StatusLowBattery: ${this.Battery.StatusLowBattery}`); + ? this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW + : this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL + await this.debugLog(`StatusLowBattery: ${this.Battery.StatusLowBattery}`) // ChargingState this.Battery.ChargingState = this.deviceStatus.workingStatus === 'Charging' - ? this.hap.Characteristic.ChargingState.CHARGING : this.hap.Characteristic.ChargingState.NOT_CHARGING; - await this.debugLog(`ChargingState: ${this.Battery.ChargingState}`); + ? this.hap.Characteristic.ChargingState.CHARGING + : this.hap.Characteristic.ChargingState.NOT_CHARGING + await this.debugLog(`ChargingState: ${this.Battery.ChargingState}`) // Firmware Version if (this.deviceStatus.version) { - const version = this.deviceStatus.version.toString(); - await this.debugLog(`Firmware Version: ${version.replace(/^V|-.*$/g, '')}`); - const deviceVersion = version.replace(/^V|-.*$/g, '') ?? '0.0.0'; + const version = this.deviceStatus.version.toString() + await this.debugLog(`Firmware Version: ${version.replace(/^V|-.*$/g, '')}`) + const deviceVersion = version.replace(/^V|-.*$/g, '') ?? '0.0.0' this.accessory .getService(this.hap.Service.AccessoryInformation)! .setCharacteristic(this.hap.Characteristic.HardwareRevision, deviceVersion) .setCharacteristic(this.hap.Characteristic.FirmwareRevision, deviceVersion) .getCharacteristic(this.hap.Characteristic.FirmwareRevision) - .updateValue(deviceVersion); - this.accessory.context.version = deviceVersion; - await this.debugSuccessLog(`version: ${this.accessory.context.version}`); + .updateValue(deviceVersion) + this.accessory.context.version = deviceVersion + await this.debugSuccessLog(`version: ${this.accessory.context.version}`) } } async parseStatusWebhook(): Promise { - await this.debugLog('parseStatusWebhook'); - await this.debugLog(`(onlineStatus, battery, workingStatus) = Webhook: (${this.webhookContext.onlineStatus}, ${this.webhookContext.battery},` - + ` ${this.webhookContext.workingStatus}), current: (${this.LightBulb.On}, ${this.Battery.BatteryLevel}, ${this.Battery.ChargingState})`); + await this.debugLog('parseStatusWebhook') + await this.debugLog(`(onlineStatus, battery, workingStatus) = Webhook: (${this.webhookContext.onlineStatus}, ${this.webhookContext.battery}, ${this.webhookContext.workingStatus}), current: (${this.LightBulb.On}, ${this.Battery.BatteryLevel}, ${this.Battery.ChargingState})`) // On - this.LightBulb.On = this.webhookContext.onlineStatus === 'online' ? true : false; - await this.debugLog(`On: ${this.LightBulb.On}`); + this.LightBulb.On = this.webhookContext.onlineStatus === 'online' + await this.debugLog(`On: ${this.LightBulb.On}`) // BatteryLevel - this.Battery.BatteryLevel = this.webhookContext.battery; - await this.debugLog(`BatteryLevel: ${this.Battery.BatteryLevel}`); + this.Battery.BatteryLevel = this.webhookContext.battery + await this.debugLog(`BatteryLevel: ${this.Battery.BatteryLevel}`) // StatusLowBattery this.Battery.StatusLowBattery = this.Battery.BatteryLevel < 10 - ? this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW : this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL; - await this.debugLog(`StatusLowBattery: ${this.Battery.StatusLowBattery}`); + ? this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW + : this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL + await this.debugLog(`StatusLowBattery: ${this.Battery.StatusLowBattery}`) // ChargingState this.Battery.ChargingState = this.webhookContext.workingStatus === 'Charging' - ? this.hap.Characteristic.ChargingState.CHARGING : this.hap.Characteristic.ChargingState.NOT_CHARGING; - await this.debugLog(`ChargingState: ${this.Battery.ChargingState}`); + ? this.hap.Characteristic.ChargingState.CHARGING + : this.hap.Characteristic.ChargingState.NOT_CHARGING + await this.debugLog(`ChargingState: ${this.Battery.ChargingState}`) } /** @@ -248,224 +257,231 @@ export class RobotVacuumCleaner extends deviceBase { */ async refreshStatus(): Promise { if (!this.device.enableCloudService && this.OpenAPI) { - await this.errorLog(`refreshStatus enableCloudService: ${this.device.enableCloudService}`); - } else if (this.BLE || this.config.options?.BLE) { - await this.BLERefreshStatus(); + await this.errorLog(`refreshStatus enableCloudService: ${this.device.enableCloudService}`) + } else if (this.BLE) { + await this.BLERefreshStatus() } else if (this.OpenAPI && this.platform.config.credentials?.token) { - await this.openAPIRefreshStatus(); + await this.openAPIRefreshStatus() } else { - await this.offlineOff(); - await this.debugWarnLog(`Connection Type: ${this.device.connectionType}, refreshStatus will not happen.`); + await this.offlineOff() + await this.debugWarnLog(`Connection Type: ${this.device.connectionType}, refreshStatus will not happen.`) } } async BLERefreshStatus(): Promise { - await this.debugLog('BLERefreshStatus'); + await this.debugLog('BLERefreshStatus') + const switchbot = await this.switchbotBLE() + if (switchbot === undefined) { + await this.BLERefreshConnection(switchbot) + } else { + // Start to monitor advertisement packets + (async () => { + // Start to monitor advertisement packets + const serviceData = await this.monitorAdvertisementPackets(switchbot) as unknown as robotVacuumCleanerServiceData + // Update HomeKit + if (serviceData.model === SwitchBotBLEModel.Unknown && serviceData.modelName === SwitchBotBLEModelName.Unknown) { + this.serviceData = serviceData + await this.BLEparseStatus() + await this.updateHomeKitCharacteristics() + } else { + await this.errorLog(`failed to get serviceData, serviceData: ${serviceData}`) + await this.BLERefreshConnection(switchbot) + } + })() + } + } + + async registerPlatformBLE(): Promise { + await this.debugLog('registerPlatformBLE') if (this.config.options?.BLE) { - await this.debugLog('is listening to Platform BLE.'); - this.device.bleMac = this.device.deviceId!.match(/.{1,2}/g)!.join(':').toLowerCase(); - await this.debugLog(`bleMac: ${this.device.bleMac}`); + await this.debugLog('is listening to Platform BLE.') + this.device.bleMac = this.device.deviceId!.match(/.{1,2}/g)!.join(':').toLowerCase() + await this.debugLog(`bleMac: ${this.device.bleMac}`) this.platform.bleEventHandler[this.device.bleMac] = async (context: robotVacuumCleanerServiceData) => { try { - await this.debugLog(`received BLE: ${JSON.stringify(context)}`); - this.serviceData = context; - await this.BLEparseStatus(); - await this.updateHomeKitCharacteristics(); + await this.debugLog(`received BLE: ${JSON.stringify(context)}`) + this.serviceData = context + await this.BLEparseStatus() + await this.updateHomeKitCharacteristics() } catch (e: any) { - await this.errorLog(`failed to handle BLE. Received: ${JSON.stringify(context)} Error: ${e}`); + await this.errorLog(`failed to handle BLE. Received: ${JSON.stringify(context)} Error: ${e}`) } - }; - } else { - await this.debugLog('is using Device BLE Scanning.'); - const switchbot = await this.switchbotBLE(); - if (switchbot === undefined) { - await this.BLERefreshConnection(switchbot); - } else { - // Start to monitor advertisement packets - (async () => { - // Start to monitor advertisement packets - const serviceData = await this.monitorAdvertisementPackets(switchbot) as unknown as robotVacuumCleanerServiceData; - // Update HomeKit - if (serviceData.model === SwitchBotBLEModel.Unknown && serviceData.modelName === SwitchBotBLEModelName.Unknown) { - this.serviceData = serviceData; - await this.BLEparseStatus(); - await this.updateHomeKitCharacteristics(); - } else { - await this.errorLog(`failed to get serviceData, serviceData: ${serviceData}`); - await this.BLERefreshConnection(switchbot); - } - })(); } + } else { + await this.debugLog('is not listening to Platform BLE') } } async openAPIRefreshStatus(): Promise { - await this.debugLog('openAPIRefreshStatus'); + await this.debugLog('openAPIRefreshStatus') try { - const { body, statusCode } = await this.deviceRefreshStatus(); - const deviceStatus: any = await body.json(); - await this.debugLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`);; + const { body, statusCode } = await this.deviceRefreshStatus() + const deviceStatus: any = await body.json() + await this.debugLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`) if (await this.successfulStatusCodes(statusCode, deviceStatus)) { - await this.debugSuccessLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`); - this.deviceStatus = deviceStatus.body; - await this.openAPIparseStatus(); - await this.updateHomeKitCharacteristics(); + await this.debugSuccessLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`) + this.deviceStatus = deviceStatus.body + await this.openAPIparseStatus() + await this.updateHomeKitCharacteristics() } else { - await this.debugWarnLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`); - await this.debugWarnLog(statusCode, deviceStatus); + await this.debugWarnLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`) + await this.debugWarnLog(statusCode, deviceStatus) } } catch (e: any) { - await this.apiError(e); - await this.errorLog(`failed openAPIRefreshStatus with ${this.device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`); + await this.apiError(e) + await this.errorLog(`failed openAPIRefreshStatus with ${this.device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`) } } async registerWebhook() { if (this.device.webhook) { - await this.debugLog('is listening webhook.'); - this.platform.webhookEventHandler[this.device.deviceId] = async (context: robotVacuumCleanerS1WebhookContext - | robotVacuumCleanerS1PlusWebhookContext | floorCleaningRobotS10WebhookContext) => { + await this.debugLog('is listening webhook.') + this.platform.webhookEventHandler[this.device.deviceId] = async (context: robotVacuumCleanerS1WebhookContext | robotVacuumCleanerS1PlusWebhookContext | floorCleaningRobotS10WebhookContext) => { try { - await this.debugLog(`received Webhook: ${JSON.stringify(context)}`); - this.webhookContext = context; - await this.parseStatusWebhook(); - await this.updateHomeKitCharacteristics(); + await this.debugLog(`received Webhook: ${JSON.stringify(context)}`) + this.webhookContext = context + await this.parseStatusWebhook() + await this.updateHomeKitCharacteristics() } catch (e: any) { - await this.errorLog(`failed to handle webhook. Received: ${JSON.stringify(context)} Error: ${e}`); + await this.errorLog(`failed to handle webhook. Received: ${JSON.stringify(context)} Error: ${e}`) } - }; + } } else { - await this.debugLog('is not listening webhook.'); + await this.debugLog('is not listening webhook.') } } /** * Pushes the requested changes to the SwitchBot API - * deviceType commandType Command parameter Description - * Robot Vacuum Cleaner S1 "command" "start" "default" = start vacuuming - * Robot Vacuum Cleaner S1 "command" "stop" "default" = stop vacuuming - * Robot Vacuum Cleaner S1 "command" "dock" "default" = return to charging dock - * Robot Vacuum Cleaner S1 "command" "PowLevel" "{0-3}" = set suction power level: 0 (Quiet), 1 (Standard), 2 (Strong), 3 (MAX) + * deviceType commandType Command parameter Description + * Robot Vacuum Cleaner S1 "command" "start" "default" = start vacuuming + * Robot Vacuum Cleaner S1 "command" "stop" "default" = stop vacuuming + * Robot Vacuum Cleaner S1 "command" "dock" "default" = return to charging dock + * Robot Vacuum Cleaner S1 "command" "PowLevel" "{0-3}" = set suction power level: 0 (Quiet), 1 (Standard), 2 (Strong), 3 (MAX) */ async pushChanges(): Promise { if (!this.device.enableCloudService && this.OpenAPI) { - await this.errorLog(`pushChanges enableCloudService: ${this.device.enableCloudService}`); + await this.errorLog(`pushChanges enableCloudService: ${this.device.enableCloudService}`) } else if (this.BLE) { - await this.BLEpushChanges(); + await this.BLEpushChanges() } else if (this.OpenAPI && this.platform.config.credentials?.token) { - await this.openAPIpushChanges(); + await this.openAPIpushChanges() } else { - await this.offlineOff(); - await this.debugWarnLog(`Connection Type: ${this.device.connectionType}, pushChanges will not happen.`); + await this.offlineOff() + await this.debugWarnLog(`Connection Type: ${this.device.connectionType}, pushChanges will not happen.`) } // Refresh the status from the API interval(15000) .pipe(skipWhile(() => this.robotVacuumCleanerUpdateInProgress)) .pipe(take(1)) .subscribe(async () => { - await this.refreshStatus(); - }); + await this.refreshStatus() + }) } async BLEpushChanges(): Promise { - await this.debugLog('BLEpushChanges'); + await this.debugLog('BLEpushChanges') if (this.LightBulb.On !== this.accessory.context.On) { - const switchbot = await this.platform.connectBLE(this.accessory, this.device); - await this.convertBLEAddress(); + const switchbot = await this.platform.connectBLE(this.accessory, this.device) + await this.convertBLEAddress() if (switchbot !== false) { switchbot .discover({ model: this.device.bleModel, id: this.device.bleMac }) .then(async (device_list: any) => { - await this.infoLog(`On: ${this.LightBulb.On}`); + await this.infoLog(`On: ${this.LightBulb.On}`) return await this.retryBLE({ max: await this.maxRetryBLE(), fn: async () => { if (this.LightBulb.On) { - return await device_list[0].turnOn({ id: this.device.bleMac }); + return await device_list[0].turnOn({ id: this.device.bleMac }) } else { - return await device_list[0].turnOff({ id: this.device.bleMac }); + return await device_list[0].turnOff({ id: this.device.bleMac }) } }, - }); + }) }) .then(async () => { - await this.successLog(`On: ${this.LightBulb.On} sent over SwitchBot BLE, sent successfully`); - await this.updateHomeKitCharacteristics(); + await this.successLog(`On: ${this.LightBulb.On} sent over SwitchBot BLE, sent successfully`) + await this.updateHomeKitCharacteristics() }) .catch(async (e: any) => { - await this.apiError(e); - await this.errorLog(`failed BLEpushChanges with ${this.device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`); - await this.BLEPushConnection(); - }); + await this.apiError(e) + await this.errorLog(`failed BLEpushChanges with ${this.device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`) + await this.BLEPushConnection() + }) } else { - await this.errorLog(`wasn't able to establish BLE Connection, node-switchbot: ${switchbot}`); - await this.BLEPushConnection(); + await this.errorLog(`wasn't able to establish BLE Connection, node-switchbot: ${switchbot}`) + await this.BLEPushConnection() } } else { - await this.debugLog(`No changes (BLEpushChanges), On: ${this.LightBulb.On}, OnCached: ${this.accessory.context.On}`); + await this.debugLog(`No changes (BLEpushChanges), On: ${this.LightBulb.On}, OnCached: ${this.accessory.context.On}`) } } async openAPIpushChanges() { - await this.debugLog('openAPIpushChanges'); + await this.debugLog('openAPIpushChanges') if (this.LightBulb.On !== this.accessory.context.On) { - const command = this.LightBulb.On ? 'start' : 'dock'; + const command = this.LightBulb.On ? 'start' : 'dock' const bodyChange = JSON.stringify({ command: `${command}`, parameter: 'default', commandType: 'command', - }); - await this.debugLog(`SwitchBot OpenAPI bodyChange: ${JSON.stringify(bodyChange)}`); + }) + await this.debugLog(`SwitchBot OpenAPI bodyChange: ${JSON.stringify(bodyChange)}`) try { - const { body, statusCode } = await this.pushChangeRequest(bodyChange); - const deviceStatus: any = await body.json(); - await this.debugLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`); + const { body, statusCode } = await this.pushChangeRequest(bodyChange) + const deviceStatus: any = await body.json() + await this.debugLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`) if (await this.successfulStatusCodes(statusCode, deviceStatus)) { - await this.debugSuccessLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`); - await this.updateHomeKitCharacteristics(); + await this.debugSuccessLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`) + await this.updateHomeKitCharacteristics() } else { - await this.statusCode(statusCode); - await this.statusCode(deviceStatus.statusCode); + await this.statusCode(statusCode) + await this.statusCode(deviceStatus.statusCode) } } catch (e: any) { - await this.apiError(e); - await this.errorLog(`failed openAPIpushChanges with ${this.device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`); + await this.apiError(e) + await this.errorLog(`failed openAPIpushChanges with ${this.device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`) } } else { - await this.debugLog(`No changes (openAPIpushChanges), On: ${this.LightBulb.On}, OnCached: ${this.accessory.context.On}`); + await this.debugLog(`No changes (openAPIpushChanges), On: ${this.LightBulb.On}, OnCached: ${this.accessory.context.On}`) } } async openAPIpushBrightnessChanges() { - await this.debugLog('openAPIpushBrightnessChanges'); + await this.debugLog('openAPIpushBrightnessChanges') if (this.LightBulb.Brightness !== this.accessory.context.Brightness) { - const command = this.LightBulb.Brightness === 0 ? 'dock' : 'PowLevel'; - const parameter = this.LightBulb.Brightness === 25 ? '0' : this.LightBulb.Brightness === 50 ? '1' : this.LightBulb.Brightness === 75 - ? '2' : this.LightBulb.Brightness === 100 ? '3' : 'default'; + const command = this.LightBulb.Brightness === 0 ? 'dock' : 'PowLevel' + const parameter = this.LightBulb.Brightness === 25 + ? '0' + : this.LightBulb.Brightness === 50 + ? '1' + : this.LightBulb.Brightness === 75 + ? '2' + : this.LightBulb.Brightness === 100 ? '3' : 'default' const bodyChange = JSON.stringify({ command: `${command}`, parameter: `${parameter}`, commandType: 'command', - }); - await this.debugLog(`SwitchBot OpenAPI bodyChange: ${JSON.stringify(bodyChange)}`); + }) + await this.debugLog(`SwitchBot OpenAPI bodyChange: ${JSON.stringify(bodyChange)}`) try { - const { body, statusCode } = await this.pushChangeRequest(bodyChange); - const deviceStatus: any = await body.json(); - await this.debugLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`); + const { body, statusCode } = await this.pushChangeRequest(bodyChange) + const deviceStatus: any = await body.json() + await this.debugLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`) if (await this.successfulStatusCodes(statusCode, deviceStatus)) { - await this.debugSuccessLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`); - await this.updateHomeKitCharacteristics(); + await this.debugSuccessLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`) + await this.updateHomeKitCharacteristics() } else { - await this.statusCode(statusCode); - await this.statusCode(deviceStatus.statusCode); + await this.statusCode(statusCode) + await this.statusCode(deviceStatus.statusCode) } } catch (e: any) { - await this.apiError(e); - await this.errorLog(`failed openAPIpushChanges with ${this.device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`); + await this.apiError(e) + await this.errorLog(`failed openAPIpushChanges with ${this.device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`) } } else { - await this.debugLog(`No changes (openAPIpushBrightnessChanges), Brightness: ${this.LightBulb.Brightness},` - + ` BrightnessCached: ${this.accessory.context.Brightness}`); + await this.debugLog(`No changes (openAPIpushBrightnessChanges), Brightness: ${this.LightBulb.Brightness}, BrightnessCached: ${this.accessory.context.Brightness}`) } } @@ -474,13 +490,13 @@ export class RobotVacuumCleaner extends deviceBase { */ async OnSet(value: CharacteristicValue): Promise { if (this.LightBulb.On !== this.accessory.context.On) { - this.infoLog(`Set On: ${value}`); + this.infoLog(`Set On: ${value}`) } else { - this.debugLog(`No Changes, On: ${value}`); + this.debugLog(`No Changes, On: ${value}`) } - this.LightBulb.On = value; - this.doRobotVacuumCleanerUpdate.next(); + this.LightBulb.On = value + this.doRobotVacuumCleanerUpdate.next() } /** @@ -488,59 +504,54 @@ export class RobotVacuumCleaner extends deviceBase { */ async BrightnessSet(value: CharacteristicValue): Promise { if (this.LightBulb.On && (this.LightBulb.Brightness !== this.accessory.context.Brightness)) { - await this.infoLog(`Set Brightness: ${value}`); + await this.infoLog(`Set Brightness: ${value}`) } else { if (this.LightBulb.On) { - this.debugLog(`No Changes, Brightness: ${value}`); + this.debugLog(`No Changes, Brightness: ${value}`) } else { - this.debugLog(`Brightness: ${value}, On: ${this.LightBulb.On}`); + this.debugLog(`Brightness: ${value}, On: ${this.LightBulb.On}`) } } - this.LightBulb.Brightness = value; - this.doRobotVacuumCleanerUpdate.next(); + this.LightBulb.Brightness = value + this.doRobotVacuumCleanerUpdate.next() } async updateHomeKitCharacteristics(): Promise { // On - await this.updateCharacteristic(this.LightBulb.Service, this.hap.Characteristic.On, - this.LightBulb.On, 'On'); + await this.updateCharacteristic(this.LightBulb.Service, this.hap.Characteristic.On, this.LightBulb.On, 'On') // Brightness - await this.updateCharacteristic(this.LightBulb.Service, this.hap.Characteristic.Brightness, - this.LightBulb.Brightness, 'Brightness'); + await this.updateCharacteristic(this.LightBulb.Service, this.hap.Characteristic.Brightness, this.LightBulb.Brightness, 'Brightness') // BatteryLevel - await this.updateCharacteristic(this.Battery.Service, this.hap.Characteristic.BatteryLevel, - this.Battery.BatteryLevel, 'BatteryLevel'); + await this.updateCharacteristic(this.Battery.Service, this.hap.Characteristic.BatteryLevel, this.Battery.BatteryLevel, 'BatteryLevel') // StatusLowBattery - await this.updateCharacteristic(this.Battery.Service, this.hap.Characteristic.StatusLowBattery, - this.Battery.StatusLowBattery, 'StatusLowBattery'); + await this.updateCharacteristic(this.Battery.Service, this.hap.Characteristic.StatusLowBattery, this.Battery.StatusLowBattery, 'StatusLowBattery') // ChargingState - await this.updateCharacteristic(this.Battery.Service, this.hap.Characteristic.ChargingState, - this.Battery.ChargingState, 'ChargingState'); + await this.updateCharacteristic(this.Battery.Service, this.hap.Characteristic.ChargingState, this.Battery.ChargingState, 'ChargingState') } async BLEPushConnection() { if (this.platform.config.credentials?.token && this.device.connectionType === 'BLE/OpenAPI') { - await this.warnLog('Using OpenAPI Connection to Push Changes'); - await this.openAPIpushChanges(); + await this.warnLog('Using OpenAPI Connection to Push Changes') + await this.openAPIpushChanges() } } async BLERefreshConnection(switchbot: any): Promise { - await this.errorLog(`wasn't able to establish BLE Connection, node-switchbot: ${switchbot}`); + await this.errorLog(`wasn't able to establish BLE Connection, node-switchbot: ${switchbot}`) if (this.platform.config.credentials?.token && this.device.connectionType === 'BLE/OpenAPI') { - await this.warnLog('Using OpenAPI Connection to Refresh Status'); - await this.openAPIRefreshStatus(); + await this.warnLog('Using OpenAPI Connection to Refresh Status') + await this.openAPIRefreshStatus() } } async offlineOff(): Promise { if (this.device.offline) { - this.LightBulb.Service.updateCharacteristic(this.hap.Characteristic.On, false); + this.LightBulb.Service.updateCharacteristic(this.hap.Characteristic.On, false) } } async apiError(e: any): Promise { - this.LightBulb.Service.updateCharacteristic(this.hap.Characteristic.On, e); + this.LightBulb.Service.updateCharacteristic(this.hap.Characteristic.On, e) } } diff --git a/src/device/waterdetector.ts b/src/device/waterdetector.ts index 39b92df7..2715fa48 100644 --- a/src/device/waterdetector.ts +++ b/src/device/waterdetector.ts @@ -2,17 +2,23 @@ * * waterdetector.ts: @switchbot/homebridge-switchbot. */ -import { deviceBase } from './device.js'; -import { Subject, interval, skipWhile } from 'rxjs'; -import { SwitchBotBLEModel, SwitchBotBLEModelName } from 'node-switchbot'; - -import type { devicesConfig } from '../settings.js'; -import type { device } from '../types/devicelist.js'; -import type { SwitchBotPlatform } from '../platform.js'; -import type { waterLeakDetectorServiceData } from '../types/bledevicestatus.js'; -import type { Service, PlatformAccessory, CharacteristicValue } from 'homebridge'; -import type { waterLeakDetectorStatus } from '../types/devicestatus.js'; -import type { waterLeakDetectorWebhookContext } from '../types/devicewebhookstatus.js'; +import type { CharacteristicValue, PlatformAccessory, Service } from 'homebridge' + +import type { SwitchBotPlatform } from '../platform.js' +import type { devicesConfig } from '../settings.js' +import type { waterLeakDetectorServiceData } from '../types/bledevicestatus.js' +import type { device } from '../types/devicelist.js' +import type { waterLeakDetectorStatus } from '../types/devicestatus.js' +import type { waterLeakDetectorWebhookContext } from '../types/devicewebhookstatus.js' + +/* +* For Testing Locally: +* import { SwitchBotBLEModel, SwitchBotBLEModelName } from '/Users/Shared/GitHub/OpenWonderLabs/node-switchbot/dist/index.js'; +*/ +import { SwitchBotBLEModel, SwitchBotBLEModelName } from 'node-switchbot' +import { interval, skipWhile, Subject } from 'rxjs' + +import { deviceBase } from './device.js' /** * Platform Accessory @@ -23,209 +29,227 @@ export class WaterDetector extends deviceBase { // Services private Battery: { Name: CharacteristicValue - Service: Service; - BatteryLevel: CharacteristicValue; - StatusLowBattery: CharacteristicValue; - ChargingState: CharacteristicValue; - }; + Service: Service + BatteryLevel: CharacteristicValue + StatusLowBattery: CharacteristicValue + ChargingState: CharacteristicValue + } private LeakSensor?: { - Name: CharacteristicValue; - Service: Service; - StatusActive: CharacteristicValue; - LeakDetected: CharacteristicValue; - }; + Name: CharacteristicValue + Service: Service + StatusActive: CharacteristicValue + LeakDetected: CharacteristicValue + } // OpenAPI - deviceStatus!: waterLeakDetectorStatus; + deviceStatus!: waterLeakDetectorStatus - //Webhook - webhookContext!: waterLeakDetectorWebhookContext; + // Webhook + webhookContext!: waterLeakDetectorWebhookContext // BLE - serviceData!: waterLeakDetectorServiceData; + serviceData!: waterLeakDetectorServiceData // Updates - WaterDetectorUpdateInProgress!: boolean; - doWaterDetectorUpdate: Subject; + WaterDetectorUpdateInProgress!: boolean + doWaterDetectorUpdate: Subject constructor( readonly platform: SwitchBotPlatform, accessory: PlatformAccessory, device: device & devicesConfig, ) { - super(platform, accessory, device); + super(platform, accessory, device) // Set category - accessory.category = this.hap.Categories.SENSOR; + accessory.category = this.hap.Categories.SENSOR // this is subject we use to track when we need to POST changes to the SwitchBot API - this.doWaterDetectorUpdate = new Subject(); - this.WaterDetectorUpdateInProgress = false; + this.doWaterDetectorUpdate = new Subject() + this.WaterDetectorUpdateInProgress = false // Initialize Battery Service - accessory.context.Battery = accessory.context.Battery ?? {}; + accessory.context.Battery = accessory.context.Battery ?? {} this.Battery = { Name: `${accessory.displayName} Battery`, Service: accessory.getService(this.hap.Service.Battery) ?? accessory.addService(this.hap.Service.Battery) as Service, BatteryLevel: accessory.context.BatteryLevel ?? 100, StatusLowBattery: accessory.context.StatusLowBattery ?? this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL, ChargingState: accessory.context.ChargingState ?? this.hap.Characteristic.ChargingState.NOT_CHARGEABLE, - }; - accessory.context.Battery = this.Battery as object; + } + accessory.context.Battery = this.Battery as object // Initialize Battery Characteristic - this.Battery.Service - .setCharacteristic(this.hap.Characteristic.Name, this.Battery.Name) - .setCharacteristic(this.hap.Characteristic.ChargingState, this.hap.Characteristic.ChargingState.NOT_CHARGEABLE) - .getCharacteristic(this.hap.Characteristic.BatteryLevel) - .onGet(() => { - return this.Battery.StatusLowBattery; - }); - - this.Battery.Service - .getCharacteristic(this.hap.Characteristic.StatusLowBattery) - .onGet(() => { - return this.Battery.StatusLowBattery; - }); + this.Battery.Service.setCharacteristic(this.hap.Characteristic.Name, this.Battery.Name).setCharacteristic(this.hap.Characteristic.ChargingState, this.hap.Characteristic.ChargingState.NOT_CHARGEABLE).getCharacteristic(this.hap.Characteristic.BatteryLevel).onGet(() => { + return this.Battery.StatusLowBattery + }) + + this.Battery.Service.getCharacteristic(this.hap.Characteristic.StatusLowBattery).onGet(() => { + return this.Battery.StatusLowBattery + }) // Initialize Leak Sensor Service if (device.waterdetector?.hide_leak) { if (this.LeakSensor) { - this.debugLog('Removing Leak Sensor Service'); - this.LeakSensor.Service = this.accessory.getService(this.hap.Service.LeakSensor) as Service; - accessory.removeService(this.LeakSensor.Service); + this.debugLog('Removing Leak Sensor Service') + this.LeakSensor.Service = this.accessory.getService(this.hap.Service.LeakSensor) as Service + accessory.removeService(this.LeakSensor.Service) } else { - this.debugLog('Leak Sensor Service Not Found'); + this.debugLog('Leak Sensor Service Not Found') } } else { - accessory.context.LeakSensor = accessory.context.LeakSensor ?? {}; + accessory.context.LeakSensor = accessory.context.LeakSensor ?? {} this.LeakSensor = { Name: `${accessory.displayName} Leak Sensor`, Service: accessory.getService(this.hap.Service.LeakSensor) ?? this.accessory.addService(this.hap.Service.LeakSensor) as Service, StatusActive: accessory.context.StatusActive ?? false, LeakDetected: accessory.context.LeakDetected ?? this.hap.Characteristic.LeakDetected.LEAK_NOT_DETECTED, - }; - accessory.context.LeakSensor = this.LeakSensor as object; + } + accessory.context.LeakSensor = this.LeakSensor as object // Initialize LeakSensor Characteristic - this.LeakSensor!.Service - .setCharacteristic(this.hap.Characteristic.Name, this.LeakSensor.Name) - .setCharacteristic(this.hap.Characteristic.StatusActive, true) - .getCharacteristic(this.hap.Characteristic.LeakDetected) - .onGet(() => { - return this.LeakSensor!.LeakDetected; - }); + this.LeakSensor!.Service.setCharacteristic(this.hap.Characteristic.Name, this.LeakSensor.Name).setCharacteristic(this.hap.Characteristic.StatusActive, true).getCharacteristic(this.hap.Characteristic.LeakDetected).onGet(() => { + return this.LeakSensor!.LeakDetected + }) } // Retrieve initial values and updateHomekit - this.debugLog('Retrieve initial values and update Homekit'); - this.refreshStatus(); + try { + this.debugLog('Retrieve initial values and update Homekit') + this.refreshStatus() + } catch (e: any) { + this.errorLog(`failed to retrieve initial values and update Homekit, Error: ${e}`) + } - //regisiter webhook event handler - this.debugLog('Registering Webhook Event Handler'); - this.registerWebhook(); + // regisiter webhook event handler if enabled + try { + this.debugLog('Registering Webhook Event Handler') + this.registerWebhook() + } catch (e: any) { + this.errorLog(`failed to registerWebhook, Error: ${e}`) + } + + // regisiter platform BLE event handler if enabled + try { + this.debugLog('Registering Platform BLE Event Handler') + this.registerPlatformBLE() + } catch (e: any) { + this.errorLog(`failed to registerPlatformBLE, Error: ${e}`) + } // Start an update interval interval(this.deviceRefreshRate * 1000) .pipe(skipWhile(() => this.WaterDetectorUpdateInProgress)) .subscribe(async () => { - await this.debugLog(`update interval: ${this.deviceRefreshRate * 1000} seconds`); - await this.refreshStatus(); - }); + await this.debugLog(`update interval: ${this.deviceRefreshRate * 1000} seconds`) + await this.refreshStatus() + }) } async BLEparseStatus(): Promise { - await this.debugLog('BLEparseStatus'); - await this.debugLog(`(state, status, battery) = BLE: (${this.serviceData.state}, ${this.serviceData.status}, ${this.serviceData.battery}),` - + ` current:(${this.LeakSensor?.LeakDetected}, ${this.Battery.BatteryLevel})`); - - //LeakSensor - if (this.device.waterdetector?.hide_leak && this.LeakSensor?.Service) { + await this.debugLog('BLEparseStatus') + await this.debugLog(`(state, status, battery) = BLE: (${this.serviceData.state}, ${this.serviceData.status}, ${this.serviceData.battery}), current:(${this.LeakSensor?.LeakDetected}, ${this.Battery.BatteryLevel})`) + // LeakSensor + if (!this.device.waterdetector?.hide_leak && this.LeakSensor?.Service) { // StatusActive - this.LeakSensor.StatusActive = this.serviceData.state; - await this.debugLog(`StatusActive: ${this.LeakSensor.StatusActive}`); + this.LeakSensor.StatusActive = this.serviceData.state + await this.debugLog(`StatusActive: ${this.LeakSensor.StatusActive}`) // LeakDetected - this.LeakSensor.LeakDetected = this.serviceData.status; - this.debugLog(`LeakDetected: ${this.LeakSensor.LeakDetected}`); + if (this.device.waterdetector?.dry) { + this.LeakSensor.LeakDetected = this.serviceData.status === 0 ? 1 : 0 + this.debugLog(`LeakDetected: ${this.LeakSensor.LeakDetected}`) + } else { + this.LeakSensor.LeakDetected = this.serviceData.status + this.debugLog(`LeakDetected: ${this.LeakSensor.LeakDetected}`) + } } // BatteryLevel - this.Battery.BatteryLevel = this.serviceData.battery; - await this.debugLog(`BatteryLevel: ${this.Battery.BatteryLevel}`); + this.Battery.BatteryLevel = this.serviceData.battery + await this.debugLog(`BatteryLevel: ${this.Battery.BatteryLevel}`) // StatusLowBattery this.Battery.StatusLowBattery = this.Battery.BatteryLevel < 10 - ? this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW : this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL; - await this.debugLog(`StatusLowBattery: ${this.Battery.StatusLowBattery}`); + ? this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW + : this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL + await this.debugLog(`StatusLowBattery: ${this.Battery.StatusLowBattery}`) } async openAPIparseStatus(): Promise { - await this.debugLog('openAPIparseStatus'); - await this.debugLog(`(status, battery) = OpenAPI: (${this.deviceStatus.status}, ${this.deviceStatus.battery}),` - + ` current:(${this.LeakSensor?.LeakDetected}, ${this.Battery.BatteryLevel})`); + await this.debugLog('openAPIparseStatus') + await this.debugLog(`(status, battery) = OpenAPI: (${this.deviceStatus.status}, ${this.deviceStatus.battery}), current:(${this.LeakSensor?.LeakDetected}, ${this.Battery.BatteryLevel})`) - //LeakSensor + // LeakSensor if (!this.device.waterdetector?.hide_leak && this.LeakSensor?.Service) { - // StatusActive - this.LeakSensor.StatusActive = this.deviceStatus.battery === 0 ? false : true; - await this.debugLog(`StatusActive: ${this.LeakSensor.StatusActive}`); + this.LeakSensor.StatusActive = this.deviceStatus.battery !== 0 + await this.debugLog(`StatusActive: ${this.LeakSensor.StatusActive}`) // LeakDetected - this.LeakSensor.LeakDetected = this.deviceStatus.status; - this.debugLog(`LeakDetected: ${this.LeakSensor.LeakDetected}`); + if (this.device.waterdetector?.dry) { + this.LeakSensor.LeakDetected = this.deviceStatus.status === 0 ? 1 : 0 + this.debugLog(`LeakDetected: ${this.LeakSensor.LeakDetected}`) + } else { + this.LeakSensor.LeakDetected = this.deviceStatus.status + this.debugLog(`LeakDetected: ${this.LeakSensor.LeakDetected}`) + } } // BatteryLevel - this.Battery.BatteryLevel = this.deviceStatus.battery; - await this.debugLog(`BatteryLevel: ${this.Battery.BatteryLevel}`); + this.Battery.BatteryLevel = this.deviceStatus.battery + await this.debugLog(`BatteryLevel: ${this.Battery.BatteryLevel}`) // StatusLowBattery this.Battery.StatusLowBattery = this.Battery.BatteryLevel < 10 - ? this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW : this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL; - await this.debugLog(`StatusLowBattery: ${this.Battery.StatusLowBattery}`); + ? this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW + : this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL + await this.debugLog(`StatusLowBattery: ${this.Battery.StatusLowBattery}`) // FirmwareVersion if (this.deviceStatus.version) { - const version = this.deviceStatus.version.toString(); - await this.debugLog(`FirmwareVersion: ${version.replace(/^V|-.*$/g, '')}`); - const deviceVersion = version.replace(/^V|-.*$/g, '') ?? '0.0.0'; + const version = this.deviceStatus.version.toString() + await this.debugLog(`FirmwareVersion: ${version.replace(/^V|-.*$/g, '')}`) + const deviceVersion = version.replace(/^V|-.*$/g, '') ?? '0.0.0' this.accessory .getService(this.hap.Service.AccessoryInformation)! .setCharacteristic(this.hap.Characteristic.HardwareRevision, deviceVersion) .setCharacteristic(this.hap.Characteristic.FirmwareRevision, deviceVersion) .getCharacteristic(this.hap.Characteristic.FirmwareRevision) - .updateValue(deviceVersion); - this.accessory.context.version = deviceVersion; - await this.debugSuccessLog(`version: ${this.accessory.context.version}`); + .updateValue(deviceVersion) + this.accessory.context.version = deviceVersion + await this.debugSuccessLog(`version: ${this.accessory.context.version}`) } } async parseStatusWebhook(): Promise { - await this.debugLog('parseStatusWebhook'); - await this.debugLog(`(detectionState, battery) = Webhook: (${this.webhookContext.detectionState}, ${this.webhookContext.battery}),` - + ` current:(${this.LeakSensor?.LeakDetected}, ${this.Battery.BatteryLevel})`); + await this.debugLog('parseStatusWebhook') + await this.debugLog(`(detectionState, battery) = Webhook: (${this.webhookContext.detectionState}, ${this.webhookContext.battery}), current:(${this.LeakSensor?.LeakDetected}, ${this.Battery.BatteryLevel})`) - //LeakSensor + // LeakSensor if (!this.device.waterdetector?.hide_leak && this.LeakSensor?.Service) { - // StatusActive - this.LeakSensor.StatusActive = this.webhookContext.detectionState ? true : false; - await this.debugLog(`StatusActive: ${this.LeakSensor.StatusActive}`); + this.LeakSensor.StatusActive = !!this.webhookContext.detectionState + await this.debugLog(`StatusActive: ${this.LeakSensor.StatusActive}`) // LeakDetected - this.LeakSensor.LeakDetected = this.webhookContext.detectionState; - await this.debugLog(`LeakDetected: ${this.LeakSensor.LeakDetected}`); + if (this.device.waterdetector?.dry) { + this.LeakSensor.LeakDetected = this.webhookContext.detectionState === 0 ? 1 : 0 + this.debugLog(`LeakDetected: ${this.LeakSensor.LeakDetected}`) + } else { + this.LeakSensor.LeakDetected = this.webhookContext.detectionState + await this.debugLog(`LeakDetected: ${this.LeakSensor.LeakDetected}`) + } } // BatteryLevel - this.Battery.BatteryLevel = this.webhookContext.battery; - await this.debugLog(`BatteryLevel: ${this.Battery.BatteryLevel}`); + this.Battery.BatteryLevel = this.webhookContext.battery + await this.debugLog(`BatteryLevel: ${this.Battery.BatteryLevel}`) // StatusLowBattery this.Battery.StatusLowBattery = this.Battery.BatteryLevel < 10 - ? this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW : this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL; - await this.debugLog(`StatusLowBattery: ${this.Battery.StatusLowBattery}`); + ? this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW + : this.hap.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL + await this.debugLog(`StatusLowBattery: ${this.Battery.StatusLowBattery}`) } /** @@ -233,93 +257,97 @@ export class WaterDetector extends deviceBase { */ async refreshStatus(): Promise { if (!this.device.enableCloudService && this.OpenAPI) { - await this.errorLog(`refreshStatus enableCloudService: ${this.device.enableCloudService}`); - } else if (this.BLE || this.config.options?.BLE) { - await this.BLERefreshStatus(); + await this.errorLog(`refreshStatus enableCloudService: ${this.device.enableCloudService}`) + } else if (this.BLE) { + await this.BLERefreshStatus() } else if (this.OpenAPI && this.platform.config.credentials?.token) { - await this.openAPIRefreshStatus(); + await this.openAPIRefreshStatus() } else { - await this.offlineOff(); - await this.debugWarnLog(`Connection Type: ${this.device.connectionType}, refreshStatus will not happen.`); + await this.offlineOff() + await this.debugWarnLog(`Connection Type: ${this.device.connectionType}, refreshStatus will not happen.`) } } async BLERefreshStatus(): Promise { - await this.debugLog('BLERefreshStatus'); + await this.debugLog('BLERefreshStatus') + const switchbot = await this.switchbotBLE() + if (switchbot === undefined) { + await this.BLERefreshConnection(switchbot) + } else { + // Start to monitor advertisement packets + (async () => { + // Start to monitor advertisement packets + const serviceData = await this.monitorAdvertisementPackets(switchbot) as waterLeakDetectorServiceData + // Update HomeKit + if (serviceData.model === SwitchBotBLEModel.Unknown && serviceData.modelName === SwitchBotBLEModelName.Unknown) { + this.serviceData = serviceData + await this.BLEparseStatus() + await this.updateHomeKitCharacteristics() + } else { + await this.errorLog(`failed to get serviceData, serviceData: ${serviceData}`) + await this.BLERefreshConnection(switchbot) + } + })() + } + } + + async registerPlatformBLE(): Promise { + await this.debugLog('registerPlatformBLE') if (this.config.options?.BLE) { - await this.debugLog('is listening to Platform BLE.'); - this.device.bleMac = this.device.deviceId!.match(/.{1,2}/g)!.join(':').toLowerCase(); - await this.debugLog(`bleMac: ${this.device.bleMac}`); + await this.debugLog('is listening to Platform BLE.') + this.device.bleMac = this.device.deviceId!.match(/.{1,2}/g)!.join(':').toLowerCase() + await this.debugLog(`bleMac: ${this.device.bleMac}`) this.platform.bleEventHandler[this.device.bleMac] = async (context: waterLeakDetectorServiceData) => { try { - await this.debugLog(`received BLE: ${JSON.stringify(context)}`); - this.serviceData = context; - await this.BLEparseStatus(); - await this.updateHomeKitCharacteristics(); + await this.debugLog(`received BLE: ${JSON.stringify(context)}`) + this.serviceData = context + await this.BLEparseStatus() + await this.updateHomeKitCharacteristics() } catch (e: any) { - await this.errorLog(`failed to handle BLE. Received: ${JSON.stringify(context)} Error: ${e}`); + await this.errorLog(`failed to handle BLE. Received: ${JSON.stringify(context)} Error: ${e}`) } - }; - } else { - await this.debugLog('is using Device BLE Scanning.'); - const switchbot = await this.switchbotBLE(); - if (switchbot === undefined) { - await this.BLERefreshConnection(switchbot); - } else { - // Start to monitor advertisement packets - (async () => { - // Start to monitor advertisement packets - const serviceData = await this.monitorAdvertisementPackets(switchbot) as waterLeakDetectorServiceData; - // Update HomeKit - if (serviceData.model === SwitchBotBLEModel.Unknown && serviceData.modelName === SwitchBotBLEModelName.Unknown) { - this.serviceData = serviceData; - await this.BLEparseStatus(); - await this.updateHomeKitCharacteristics(); - } else { - await this.errorLog(`failed to get serviceData, serviceData: ${serviceData}`); - await this.BLERefreshConnection(switchbot); - } - })(); } + } else { + await this.debugLog('is not listening to Platform BLE') } } async openAPIRefreshStatus(): Promise { - await this.debugLog('openAPIRefreshStatus'); + await this.debugLog('openAPIRefreshStatus') try { - const { body, statusCode } = await this.deviceRefreshStatus(); - const deviceStatus: any = await body.json(); - await this.debugLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`);; + const { body, statusCode } = await this.deviceRefreshStatus() + const deviceStatus: any = await body.json() + await this.debugLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`) if (await this.successfulStatusCodes(statusCode, deviceStatus)) { - await this.debugSuccessLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`); - this.deviceStatus = deviceStatus.body; - await this.openAPIparseStatus(); - await this.updateHomeKitCharacteristics(); + await this.debugSuccessLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`) + this.deviceStatus = deviceStatus.body + await this.openAPIparseStatus() + await this.updateHomeKitCharacteristics() } else { - await this.debugWarnLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`); - await this.debugWarnLog(statusCode, deviceStatus); + await this.debugWarnLog(`statusCode: ${statusCode}, deviceStatus: ${JSON.stringify(deviceStatus)}`) + await this.debugWarnLog(statusCode, deviceStatus) } } catch (e: any) { - await this.apiError(e); - await this.errorLog(`failed openAPIRefreshStatus with ${this.device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`); + await this.apiError(e) + await this.errorLog(`failed openAPIRefreshStatus with ${this.device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`) } } async registerWebhook() { if (this.device.webhook) { - await this.debugLog('is listening webhook.'); + await this.debugLog('is listening webhook.') this.platform.webhookEventHandler[this.device.deviceId] = async (context: waterLeakDetectorWebhookContext) => { try { - await this.debugLog(`received Webhook: ${JSON.stringify(context)}`); - this.webhookContext = context; - await this.parseStatusWebhook(); - await this.updateHomeKitCharacteristics(); + await this.debugLog(`received Webhook: ${JSON.stringify(context)}`) + this.webhookContext = context + await this.parseStatusWebhook() + await this.updateHomeKitCharacteristics() } catch (e: any) { - await this.errorLog(`failed to handle webhook. Received: ${JSON.stringify(context)} Error: ${e}`); + await this.errorLog(`failed to handle webhook. Received: ${JSON.stringify(context)} Error: ${e}`) } - }; + } } else { - await this.debugLog('is not listening webhook.'); + await this.debugLog('is not listening webhook.') } } @@ -329,43 +357,39 @@ export class WaterDetector extends deviceBase { async updateHomeKitCharacteristics(): Promise { if (!this.device.waterdetector?.hide_leak && this.LeakSensor?.Service) { // StatusActive - await this.updateCharacteristic(this.LeakSensor.Service, this.hap.Characteristic.StatusActive, - this.LeakSensor.StatusActive, 'StatusActive'); + await this.updateCharacteristic(this.LeakSensor.Service, this.hap.Characteristic.StatusActive, this.LeakSensor.StatusActive, 'StatusActive') // LeakDetected - await this.updateCharacteristic(this.LeakSensor.Service, this.hap.Characteristic.LeakDetected, - this.LeakSensor.LeakDetected, 'LeakDetected'); + await this.updateCharacteristic(this.LeakSensor.Service, this.hap.Characteristic.LeakDetected, this.LeakSensor.LeakDetected, 'LeakDetected') } // BatteryLevel - await this.updateCharacteristic(this.Battery.Service, this.hap.Characteristic.BatteryLevel, - this.Battery.BatteryLevel, 'BatteryLevel'); + await this.updateCharacteristic(this.Battery.Service, this.hap.Characteristic.BatteryLevel, this.Battery.BatteryLevel, 'BatteryLevel') // StatusLowBattery - await this.updateCharacteristic(this.Battery.Service, this.hap.Characteristic.StatusLowBattery, - this.Battery.StatusLowBattery, 'StatusLowBattery'); + await this.updateCharacteristic(this.Battery.Service, this.hap.Characteristic.StatusLowBattery, this.Battery.StatusLowBattery, 'StatusLowBattery') } async BLERefreshConnection(switchbot: any): Promise { - await this.errorLog(`wasn't able to establish BLE Connection, node-switchbot: ${switchbot}`); + await this.errorLog(`wasn't able to establish BLE Connection, node-switchbot: ${switchbot}`) if (this.platform.config.credentials?.token && this.device.connectionType === 'BLE/OpenAPI') { - await this.warnLog('Using OpenAPI Connection to Refresh Status'); - await this.openAPIRefreshStatus(); + await this.warnLog('Using OpenAPI Connection to Refresh Status') + await this.openAPIRefreshStatus() } } async offlineOff(): Promise { if (this.device.offline) { if (!this.device.waterdetector?.hide_leak && this.LeakSensor?.Service) { - this.LeakSensor.Service.updateCharacteristic(this.hap.Characteristic.StatusActive, false); - this.LeakSensor.Service.updateCharacteristic(this.hap.Characteristic.LeakDetected, this.hap.Characteristic.LeakDetected.LEAK_NOT_DETECTED); + this.LeakSensor.Service.updateCharacteristic(this.hap.Characteristic.StatusActive, false) + this.LeakSensor.Service.updateCharacteristic(this.hap.Characteristic.LeakDetected, this.hap.Characteristic.LeakDetected.LEAK_NOT_DETECTED) } } } - async apiError(e: any): Promise < void> { - if(!this.device.waterdetector?.hide_leak && this.LeakSensor?.Service) { - this.LeakSensor.Service.updateCharacteristic(this.hap.Characteristic.StatusActive, e); - this.LeakSensor.Service.updateCharacteristic(this.hap.Characteristic.LeakDetected, e); + async apiError(e: any): Promise { + if (!this.device.waterdetector?.hide_leak && this.LeakSensor?.Service) { + this.LeakSensor.Service.updateCharacteristic(this.hap.Characteristic.StatusActive, e) + this.LeakSensor.Service.updateCharacteristic(this.hap.Characteristic.LeakDetected, e) } - this.Battery.Service.updateCharacteristic(this.hap.Characteristic.BatteryLevel, e); - this.Battery.Service.updateCharacteristic(this.hap.Characteristic.StatusLowBattery, e); + this.Battery.Service.updateCharacteristic(this.hap.Characteristic.BatteryLevel, e) + this.Battery.Service.updateCharacteristic(this.hap.Characteristic.StatusLowBattery, e) } } diff --git a/src/homebridge-ui/server.ts b/src/homebridge-ui/server.ts index 65e33b82..b08fc11e 100644 --- a/src/homebridge-ui/server.ts +++ b/src/homebridge-ui/server.ts @@ -1,47 +1,48 @@ -import { HomebridgePluginUiServer } from '@homebridge/plugin-ui-utils'; -import fs from 'fs'; +import fs from 'node:fs' + +import { HomebridgePluginUiServer } from '@homebridge/plugin-ui-utils' class PluginUiServer extends HomebridgePluginUiServer { constructor() { - super(); + super() /* 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-switchbot'; - const devicesToReturn = []; + const plugin = 'homebridge-switchbot' + const devicesToReturn = [] // The path and file of the cached accessories - const accFile = this.homebridgeStoragePath + '/accessories/cachedAccessories'; + 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')); + 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); + devicesToReturn.push(accessory.accessory as never) } - }); + }) } // Return the array - return devicesToReturn; + return devicesToReturn } catch { // Just return an empty accessory list in case of any errors - return []; + return [] } - }); - this.ready(); + }) + this.ready() } } function startPluginUiServer(): PluginUiServer { - return new PluginUiServer(); + return new PluginUiServer() } -startPluginUiServer(); +startPluginUiServer() diff --git a/src/index.ts b/src/index.ts index f1989218..dbe2f784 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,13 +2,12 @@ * * index.ts: @switchbot/homebridge-switchbot plugin registration. */ -import { SwitchBotPlatform } from './platform.js'; -import { PLATFORM_NAME, PLUGIN_NAME } from './settings.js'; +import type { API } from 'homebridge' -import type { API } from 'homebridge'; +import { SwitchBotPlatform } from './platform.js' +import { PLATFORM_NAME, PLUGIN_NAME } from './settings.js' // Register our platform with homebridge. export default (api: API): void => { - - api.registerPlatform(PLUGIN_NAME, PLATFORM_NAME, SwitchBotPlatform); -}; + api.registerPlatform(PLUGIN_NAME, PLATFORM_NAME, SwitchBotPlatform) +} diff --git a/src/irdevice/airconditioner.ts b/src/irdevice/airconditioner.ts index a1a514bb..bb379a51 100644 --- a/src/irdevice/airconditioner.ts +++ b/src/irdevice/airconditioner.ts @@ -2,12 +2,13 @@ * * airconditioners.ts: @switchbot/homebridge-switchbot. */ -import { irdeviceBase } from './irdevice.js'; +import type { CharacteristicValue, PlatformAccessory, Service } from 'homebridge' -import type { SwitchBotPlatform } from '../platform.js'; -import type { irDevicesConfig } from '../settings.js'; -import type { irdevice } from '../types/irdevicelist.js'; -import type { CharacteristicValue, PlatformAccessory, Service } from 'homebridge'; +import type { SwitchBotPlatform } from '../platform.js' +import type { irDevicesConfig } from '../settings.js' +import type { irdevice } from '../types/irdevicelist.js' + +import { irdeviceBase } from './irdevice.js' /** * Platform Accessory @@ -17,55 +18,55 @@ import type { CharacteristicValue, PlatformAccessory, Service } from 'homebridge export class AirConditioner extends irdeviceBase { // Services private HeaterCooler: { - Name: CharacteristicValue; - Service: Service; - Active: CharacteristicValue; - CurrentHeaterCoolerState: CharacteristicValue; - TargetHeaterCoolerState: CharacteristicValue; - CurrentTemperature: CharacteristicValue; - ThresholdTemperature: CharacteristicValue; - RotationSpeed: CharacteristicValue; - }; - - meter?: PlatformAccessory; + Name: CharacteristicValue + Service: Service + Active: CharacteristicValue + CurrentHeaterCoolerState: CharacteristicValue + TargetHeaterCoolerState: CharacteristicValue + CurrentTemperature: CharacteristicValue + ThresholdTemperature: CharacteristicValue + RotationSpeed: CharacteristicValue + } + + meter?: PlatformAccessory private HumiditySensor?: { - Name: CharacteristicValue; - Service: Service; - CurrentRelativeHumidity: CharacteristicValue; - }; + Name: CharacteristicValue + Service: Service + CurrentRelativeHumidity: CharacteristicValue + } // Others - state!: string; - Busy: any; - Timeout: any = null; - CurrentMode!: number; - ValidValues: number[]; - CurrentFanSpeed!: number; + state!: string + Busy: any + Timeout: any = null + CurrentMode!: number + ValidValues: number[] + CurrentFanSpeed!: number // Config - hide_automode?: boolean; - set_max_heat?: number; - set_min_heat?: number; - set_max_cool?: number; - set_min_cool?: number; + hide_automode?: boolean + set_max_heat?: number + set_min_heat?: number + set_max_cool?: number + set_min_cool?: number constructor( readonly platform: SwitchBotPlatform, accessory: PlatformAccessory, device: irdevice & irDevicesConfig, ) { - super(platform, accessory, device); + super(platform, accessory, device) // Set category - accessory.category = this.hap.Categories.AIR_CONDITIONER; + accessory.category = this.hap.Categories.AIR_CONDITIONER // default placeholders - this.getAirConditionerConfigSettings(accessory, device); + this.getAirConditionerConfigSettings(accessory, device) - this.ValidValues = this.hide_automode ? [1, 2] : [0, 1, 2]; + this.ValidValues = this.hide_automode ? [1, 2] : [0, 1, 2] // Initialize HeaterCooler Service - accessory.context.HeaterCooler = accessory.context.HeaterCooler ?? {}; + accessory.context.HeaterCooler = accessory.context.HeaterCooler ?? {} this.HeaterCooler = { Name: accessory.displayName, Service: accessory.getService(this.hap.Service.HeaterCooler) ?? accessory.addService(this.hap.Service.HeaterCooler) as Service, @@ -75,511 +76,461 @@ export class AirConditioner extends irdeviceBase { CurrentTemperature: accessory.context.CurrentTemperature ?? 24, ThresholdTemperature: accessory.context.ThresholdTemperature ?? 24, RotationSpeed: accessory.context.RotationSpeed ?? 4, - }; - accessory.context.HeaterCooler = this.HeaterCooler as object; - - this.HeaterCooler.Service - .setCharacteristic(this.hap.Characteristic.Name, this.HeaterCooler.Name) - .getCharacteristic(this.hap.Characteristic.Active) - .onGet(() => { - return this.HeaterCooler.Active; - }) - .onSet(this.ActiveSet.bind(this)); - - this.HeaterCooler.Service - .getCharacteristic(this.hap.Characteristic.CurrentTemperature) - .onGet(async () => { - return await this.CurrentTemperatureGet(); - }); - - this.HeaterCooler.Service - .getCharacteristic(this.hap.Characteristic.TargetHeaterCoolerState) - .setProps({ - validValues: this.ValidValues, - }) - .onGet(async () => { - return await this.TargetHeaterCoolerStateGet(); - }) - .onSet(this.TargetHeaterCoolerStateSet.bind(this)); - - this.HeaterCooler.Service - .getCharacteristic(this.hap.Characteristic.CurrentHeaterCoolerState) - .onGet(async () => { - return await this.CurrentHeaterCoolerStateGet(); - }); - - this.HeaterCooler.Service - .getCharacteristic(this.hap.Characteristic.HeatingThresholdTemperature) - .setProps({ - minValue: this.set_min_heat, - maxValue: this.set_max_heat, - minStep: 0.5, - }) - .onGet(async () => { - return await this.ThresholdTemperatureGet(); - }) - .onSet(this.ThresholdTemperatureSet.bind(this)); - - this.HeaterCooler.Service - .getCharacteristic(this.hap.Characteristic.CoolingThresholdTemperature) - .setProps({ - minValue: this.set_min_cool, - maxValue: this.set_max_cool, - minStep: 0.5, - }) - .onGet(async () => { - return await this.ThresholdTemperatureGet(); - }) - .onSet(this.ThresholdTemperatureSet.bind(this)); - - this.HeaterCooler.Service - .getCharacteristic(this.hap.Characteristic.RotationSpeed) - .setProps({ - format: 'int', - minStep: 1, - minValue: 1, - maxValue: 4, - }) - .onGet(async () => { - return await this.RotationSpeedGet(); - }) - .onSet(this.RotationSpeedSet.bind(this)); + } + accessory.context.HeaterCooler = this.HeaterCooler as object + + this.HeaterCooler.Service.setCharacteristic(this.hap.Characteristic.Name, this.HeaterCooler.Name).getCharacteristic(this.hap.Characteristic.Active).onGet(() => { + return this.HeaterCooler.Active + }).onSet(this.ActiveSet.bind(this)) + + this.HeaterCooler.Service.getCharacteristic(this.hap.Characteristic.CurrentTemperature).onGet(async () => { + return await this.CurrentTemperatureGet() + }) + + this.HeaterCooler.Service.getCharacteristic(this.hap.Characteristic.TargetHeaterCoolerState).setProps({ + validValues: this.ValidValues, + }).onGet(async () => { + return await this.TargetHeaterCoolerStateGet() + }).onSet(this.TargetHeaterCoolerStateSet.bind(this)) + + this.HeaterCooler.Service.getCharacteristic(this.hap.Characteristic.CurrentHeaterCoolerState).onGet(async () => { + return await this.CurrentHeaterCoolerStateGet() + }) + + this.HeaterCooler.Service.getCharacteristic(this.hap.Characteristic.HeatingThresholdTemperature).setProps({ + minValue: this.set_min_heat, + maxValue: this.set_max_heat, + minStep: 0.5, + }).onGet(async () => { + return await this.ThresholdTemperatureGet() + }).onSet(this.ThresholdTemperatureSet.bind(this)) + + this.HeaterCooler.Service.getCharacteristic(this.hap.Characteristic.CoolingThresholdTemperature).setProps({ + minValue: this.set_min_cool, + maxValue: this.set_max_cool, + minStep: 0.5, + }).onGet(async () => { + return await this.ThresholdTemperatureGet() + }).onSet(this.ThresholdTemperatureSet.bind(this)) + + this.HeaterCooler.Service.getCharacteristic(this.hap.Characteristic.RotationSpeed).setProps({ + format: 'int', + minStep: 1, + minValue: 1, + maxValue: 4, + }).onGet(async () => { + return await this.RotationSpeedGet() + }).onSet(this.RotationSpeedSet.bind(this)) // Initialize HumiditySensor property if (this.device.irair?.meterType && this.device.irair?.meterId) { - const meterUuid = this.platform.api.hap.uuid.generate(`${this.device.irair.meterId}-${this.device.irair.meterType}`); - this.meter = this.platform.accessories.find((accessory) => accessory.UUID === meterUuid); - accessory.context.HumiditySensor = accessory.context.HumiditySensor ?? {}; + const meterUuid = this.platform.api.hap.uuid.generate(`${this.device.irair.meterId}-${this.device.irair.meterType}`) + this.meter = this.platform.accessories.find(accessory => accessory.UUID === meterUuid) + accessory.context.HumiditySensor = accessory.context.HumiditySensor ?? {} this.HumiditySensor = { Name: this.meter!.displayName, Service: this.meter!.getService(this.hap.Service.HumiditySensor) ?? this.meter!.addService(this.hap.Service.HumiditySensor) as Service, CurrentRelativeHumidity: this.meter!.context.CurrentRelativeHumidity || 0, - }; - accessory.context.HumiditySensor = this.HumiditySensor as object; + } + accessory.context.HumiditySensor = this.HumiditySensor as object } if (this.device.irair?.meterType && this.device.irair?.meterId) { - const meterUuid = this.platform.api.hap.uuid.generate(`${this.device.irair.meterId}-${this.device.irair.meterType}`); - this.meter = this.platform.accessories.find((accessory) => accessory.UUID === meterUuid); + const meterUuid = this.platform.api.hap.uuid.generate(`${this.device.irair.meterId}-${this.device.irair.meterType}`) + this.meter = this.platform.accessories.find(accessory => accessory.UUID === meterUuid) } if (this.meter && this.HumiditySensor) { - this.HumiditySensor.Service - .setCharacteristic(this.hap.Characteristic.Name, this.HumiditySensor.Name) - .getCharacteristic(this.hap.Characteristic.CurrentRelativeHumidity) - .onGet(async () => { - return await this.CurrentRelativeHumidityGet(); - }); + this.HumiditySensor.Service.setCharacteristic(this.hap.Characteristic.Name, this.HumiditySensor.Name).getCharacteristic(this.hap.Characteristic.CurrentRelativeHumidity).onGet(async () => { + return await this.CurrentRelativeHumidityGet() + }) } } /** * Pushes the requested changes to the SwitchBot API - * deviceType commandType Command command parameter Description - * AirConditioner: "command" "swing" "default" = swing - * AirConditioner: "command" "timer" "default" = timer - * AirConditioner: "command" "lowSpeed" "default" = fan speed to low - * AirConditioner: "command" "middleSpeed" "default" = fan speed to medium - * AirConditioner: "command" "highSpeed" "default" = fan speed to high + * deviceType commandType Command command parameter Description + * AirConditioner: "command" "swing" "default" = swing + * AirConditioner: "command" "timer" "default" = timer + * AirConditioner: "command" "lowSpeed" "default" = fan speed to low + * AirConditioner: "command" "middleSpeed" "default" = fan speed to medium + * AirConditioner: "command" "highSpeed" "default" = fan speed to high */ async pushAirConditionerOnChanges(): Promise { - await this.debugLog(`pushAirConditionerOnChanges Active: ${this.HeaterCooler.Active},` - + ` disablePushOn: ${this.disablePushOn}`); + await this.debugLog(`pushAirConditionerOnChanges Active: ${this.HeaterCooler.Active}, disablePushOn: ${this.disablePushOn}`) if (this.HeaterCooler.Active === this.hap.Characteristic.Active.ACTIVE && !this.disablePushOn) { - const commandType: string = await this.commandType(); - const command: string = await this.commandOn(); + const commandType: string = await this.commandType() + const command: string = await this.commandOn() const bodyChange = JSON.stringify({ - command: command, + command, parameter: 'default', - commandType: commandType, - }); - await this.pushChanges(bodyChange); + commandType, + }) + await this.pushChanges(bodyChange) } } async pushAirConditionerOffChanges(): Promise { - await this.debugLog(`pushAirConditionerOffChanges Active: ${this.HeaterCooler.Active},` - + ` disablePushOff: ${this.disablePushOff}`); + await this.debugLog(`pushAirConditionerOffChanges Active: ${this.HeaterCooler.Active}, disablePushOff: ${this.disablePushOff}`) if (this.HeaterCooler.Active === this.hap.Characteristic.Active.INACTIVE && !this.disablePushOff) { - const commandType: string = await this.commandType(); - const command: string = await this.commandOff(); + const commandType: string = await this.commandType() + const command: string = await this.commandOff() const bodyChange = JSON.stringify({ - command: command, + command, parameter: 'default', - commandType: commandType, - }); - await this.pushChanges(bodyChange); + commandType, + }) + await this.pushChanges(bodyChange) } } async pushAirConditionerStatusChanges(): Promise { - await this.debugLog(`pushAirConditionerStatusChanges Active: ${this.HeaterCooler.Active},` - + ` disablePushOff: ${this.disablePushOff}, disablePushOn: ${this.disablePushOn}`); + await this.debugLog(`pushAirConditionerStatusChanges Active: ${this.HeaterCooler.Active}, disablePushOff: ${this.disablePushOff}, disablePushOn: ${this.disablePushOn}`) if (!this.Busy) { - this.Busy = true; - this.HeaterCooler.CurrentHeaterCoolerState = this.hap.Characteristic.CurrentHeaterCoolerState.IDLE; + this.Busy = true + this.HeaterCooler.CurrentHeaterCoolerState = this.hap.Characteristic.CurrentHeaterCoolerState.IDLE } - clearTimeout(this.Timeout); + clearTimeout(this.Timeout) // Make a new Timeout set to go off in 1000ms (1 second) - this.Timeout = setTimeout(this.pushAirConditionerDetailsChanges.bind(this), 1500); + this.Timeout = setTimeout(this.pushAirConditionerDetailsChanges.bind(this), 1500) } async pushAirConditionerDetailsChanges(): Promise { - await this.debugLog(`pushAirConditionerDetailsChanges Active: ${this.HeaterCooler.Active},` - + ` disablePushOff: ${this.disablePushOff}, disablePushOn: ${this.disablePushOn}`); - //await this.deviceContext(); + await this.debugLog(`pushAirConditionerDetailsChanges Active: ${this.HeaterCooler.Active}, disablePushOff: ${this.disablePushOff}, disablePushOn: ${this.disablePushOn}`) + // await this.deviceContext(); if (this.CurrentMode === undefined) { - this.CurrentMode = 1; + this.CurrentMode = 1 } if (this.CurrentFanSpeed === undefined) { - this.CurrentFanSpeed = 1; + this.CurrentFanSpeed = 1 } if (this.HeaterCooler.Active === this.hap.Characteristic.Active.ACTIVE) { - this.state = 'on'; + this.state = 'on' } else { - this.state = 'off'; + this.state = 'off' } if (this.CurrentMode === 1) { // Remove or make configurable? - this.HeaterCooler.ThresholdTemperature = 25; - await this.debugLog(`CurrentMode: ${this.CurrentMode},` - + ` ThresholdTemperature: ${this.HeaterCooler.ThresholdTemperature}`); + this.HeaterCooler.ThresholdTemperature = 25 + await this.debugLog(`CurrentMode: ${this.CurrentMode}, ThresholdTemperature: ${this.HeaterCooler.ThresholdTemperature}`) } - const parameter = `${this.HeaterCooler.ThresholdTemperature},${this.CurrentMode},${this.CurrentFanSpeed},${this.state}`; + const parameter = `${this.HeaterCooler.ThresholdTemperature},${this.CurrentMode},${this.CurrentFanSpeed},${this.state}` - await this.UpdateCurrentHeaterCoolerState(); + await this.UpdateCurrentHeaterCoolerState() const bodyChange = JSON.stringify({ command: 'setAll', parameter: `${parameter}`, commandType: 'command', - }); + }) - await this.pushChanges(bodyChange); + await this.pushChanges(bodyChange) } private async UpdateCurrentHeaterCoolerState() { if (this.HeaterCooler.Active === this.hap.Characteristic.Active.ACTIVE) { - if (this.HeaterCooler.Active === undefined) { - this.HeaterCooler.Active = this.hap.Characteristic.Active.INACTIVE; + this.HeaterCooler.Active = this.hap.Characteristic.Active.INACTIVE } else { - this.HeaterCooler.Active = this.HeaterCooler.Active ? this.HeaterCooler.Active : this.accessory.context.Active; + this.HeaterCooler.Active = this.HeaterCooler.Active ? this.HeaterCooler.Active : this.accessory.context.Active } if (this.HeaterCooler.CurrentTemperature === undefined && this.accessory.context.CurrentTemperature === undefined) { - this.HeaterCooler.CurrentTemperature = 24; + this.HeaterCooler.CurrentTemperature = 24 } else { - this.HeaterCooler.CurrentTemperature = this.HeaterCooler.CurrentTemperature || this.accessory.context.CurrentTemperature; + this.HeaterCooler.CurrentTemperature = this.HeaterCooler.CurrentTemperature || this.accessory.context.CurrentTemperature } if (this.HeaterCooler.ThresholdTemperature === undefined && this.accessory.context.ThresholdTemperature === undefined) { - this.HeaterCooler.ThresholdTemperature = 24; + this.HeaterCooler.ThresholdTemperature = 24 } else { - this.HeaterCooler.ThresholdTemperature = this.HeaterCooler.ThresholdTemperature || this.accessory.context.ThresholdTemperature; + this.HeaterCooler.ThresholdTemperature = this.HeaterCooler.ThresholdTemperature || this.accessory.context.ThresholdTemperature } if (this.HeaterCooler.RotationSpeed === undefined && this.accessory.context.RotationSpeed === undefined) { - this.HeaterCooler.RotationSpeed = 4; + this.HeaterCooler.RotationSpeed = 4 } else { - this.HeaterCooler.RotationSpeed = this.HeaterCooler.RotationSpeed || this.accessory.context.RotationSpeed; + this.HeaterCooler.RotationSpeed = this.HeaterCooler.RotationSpeed || this.accessory.context.RotationSpeed } if (this.device.irair?.hide_automode) { - this.hide_automode = this.device.irair?.hide_automode; - this.accessory.context.hide_automode = this.hide_automode; + this.hide_automode = this.device.irair?.hide_automode + this.accessory.context.hide_automode = this.hide_automode } else { - this.hide_automode = this.device.irair?.hide_automode; - this.accessory.context.hide_automode = this.hide_automode; + this.hide_automode = this.device.irair?.hide_automode + this.accessory.context.hide_automode = this.hide_automode } if (this.device.irair?.set_max_heat) { - this.set_max_heat = this.device.irair?.set_max_heat; - this.accessory.context.set_max_heat = this.set_max_heat; + this.set_max_heat = this.device.irair?.set_max_heat + this.accessory.context.set_max_heat = this.set_max_heat } else { - this.set_max_heat = 35; - this.accessory.context.set_max_heat = this.set_max_heat; + this.set_max_heat = 35 + this.accessory.context.set_max_heat = this.set_max_heat } if (this.device.irair?.set_min_heat) { - this.set_min_heat = this.device.irair?.set_min_heat; - this.accessory.context.set_min_heat = this.set_min_heat; + this.set_min_heat = this.device.irair?.set_min_heat + this.accessory.context.set_min_heat = this.set_min_heat } else { - this.set_min_heat = 0; - this.accessory.context.set_min_heat = this.set_min_heat; + this.set_min_heat = 0 + this.accessory.context.set_min_heat = this.set_min_heat } if (this.device.irair?.set_max_cool) { - this.set_max_cool = this.device.irair?.set_max_cool; - this.accessory.context.set_max_cool = this.set_max_cool; + this.set_max_cool = this.device.irair?.set_max_cool + this.accessory.context.set_max_cool = this.set_max_cool } else { - this.set_max_cool = 35; - this.accessory.context.set_max_cool = this.set_max_cool; + this.set_max_cool = 35 + this.accessory.context.set_max_cool = this.set_max_cool } if (this.device.irair?.set_min_cool) { - this.set_min_cool = this.device.irair?.set_min_cool; - this.accessory.context.set_min_cool = this.set_min_cool; + this.set_min_cool = this.device.irair?.set_min_cool + this.accessory.context.set_min_cool = this.set_min_cool } else { - this.set_min_cool = 0; - this.accessory.context.set_min_cool = this.set_min_cool; + this.set_min_cool = 0 + this.accessory.context.set_min_cool = this.set_min_cool } if (this.meter) { if (this.HumiditySensor!.CurrentRelativeHumidity === undefined && this.accessory.context.CurrentRelativeHumidity === undefined) { - this.HumiditySensor!.CurrentRelativeHumidity = 0; + this.HumiditySensor!.CurrentRelativeHumidity = 0 } else { - this.HumiditySensor!.CurrentRelativeHumidity = this.HumiditySensor!.CurrentRelativeHumidity - || this.accessory.context.CurrentRelativeHumidity; + this.HumiditySensor!.CurrentRelativeHumidity = this.HumiditySensor!.CurrentRelativeHumidity ?? this.accessory.context.CurrentRelativeHumidity } } - if (this.HeaterCooler.ThresholdTemperature < this.HeaterCooler.CurrentTemperature && - this.HeaterCooler.TargetHeaterCoolerState !== this.hap.Characteristic.TargetHeaterCoolerState.HEAT) { - this.HeaterCooler.CurrentHeaterCoolerState = this.hap.Characteristic.CurrentHeaterCoolerState.COOLING; - } else if (this.HeaterCooler.ThresholdTemperature > this.HeaterCooler.CurrentTemperature && - this.HeaterCooler.TargetHeaterCoolerState !== this.hap.Characteristic.TargetHeaterCoolerState.COOL) { - this.HeaterCooler.CurrentHeaterCoolerState = this.hap.Characteristic.CurrentHeaterCoolerState.HEATING; + if (this.HeaterCooler.ThresholdTemperature < this.HeaterCooler.CurrentTemperature + && this.HeaterCooler.TargetHeaterCoolerState !== this.hap.Characteristic.TargetHeaterCoolerState.HEAT) { + this.HeaterCooler.CurrentHeaterCoolerState = this.hap.Characteristic.CurrentHeaterCoolerState.COOLING + } else if (this.HeaterCooler.ThresholdTemperature > this.HeaterCooler.CurrentTemperature && this.HeaterCooler.TargetHeaterCoolerState !== this.hap.Characteristic.TargetHeaterCoolerState.COOL) { + this.HeaterCooler.CurrentHeaterCoolerState = this.hap.Characteristic.CurrentHeaterCoolerState.HEATING } else { - this.HeaterCooler.CurrentHeaterCoolerState = this.hap.Characteristic.CurrentHeaterCoolerState.IDLE; + this.HeaterCooler.CurrentHeaterCoolerState = this.hap.Characteristic.CurrentHeaterCoolerState.IDLE } } else { - this.HeaterCooler.CurrentHeaterCoolerState = this.hap.Characteristic.CurrentHeaterCoolerState.INACTIVE; + this.HeaterCooler.CurrentHeaterCoolerState = this.hap.Characteristic.CurrentHeaterCoolerState.INACTIVE } } async pushChanges(bodyChange: any): Promise { - await this.debugLog('pushChanges'); + await this.debugLog('pushChanges') if (this.device.connectionType === 'OpenAPI' && !this.disablePushDetail) { - await this.infoLog(`Sending request to SwitchBot API, body: ${bodyChange},`); + await this.infoLog(`Sending request to SwitchBot API, body: ${bodyChange},`) try { - const { body, statusCode } = await this.pushChangeRequest(bodyChange); - const deviceStatus: any = await body.json(); - await this.pushStatusCodes(statusCode, deviceStatus); + const { body, statusCode } = await this.pushChangeRequest(bodyChange) + const deviceStatus: any = await body.json() + await this.pushStatusCodes(statusCode, deviceStatus) if (await this.successfulStatusCodes(statusCode, deviceStatus)) { - await this.successfulPushChange(statusCode, deviceStatus, bodyChange); - await this.updateHomeKitCharacteristics(); + await this.successfulPushChange(statusCode, deviceStatus, bodyChange) + await this.updateHomeKitCharacteristics() } else { - await this.statusCode(statusCode); - await this.statusCode(deviceStatus.statusCode); + await this.statusCode(statusCode) + await this.statusCode(deviceStatus.statusCode) } } catch (e: any) { - await this.apiError(e); - await this.pushChangeError(e); + await this.apiError(e) + await this.pushChangeError(e) } } else { - await this.warnLog(`Connection Type: ${this.device.connectionType}, commands will not be sent to OpenAPI`); - await this.debugLog(`Connection Type: ${this.device.connectionType}, disablePushDetails: ${this.disablePushDetail}`); - await this.updateHomeKitCharacteristics(); + await this.warnLog(`Connection Type: ${this.device.connectionType}, commands will not be sent to OpenAPI`) + await this.debugLog(`Connection Type: ${this.device.connectionType}, disablePushDetails: ${this.disablePushDetail}`) + await this.updateHomeKitCharacteristics() } } async CurrentTemperatureGet(): Promise { if (this.meter?.context?.CurrentTemperature) { - this.accessory.context.CurrentTemperature = this.meter.context.CurrentTemperature; - await this.debugLog('' - + `Using CurrentTemperature from ${this.meter.context.deviceType} (${this.meter.context.deviceId})`); + this.accessory.context.CurrentTemperature = this.meter.context.CurrentTemperature + await this.debugLog(`Using CurrentTemperature from ${this.meter.context.deviceType} (${this.meter.context.deviceId})`) } - this.HeaterCooler.CurrentTemperature = this.accessory.context.CurrentTemperature || 24; - await this.debugLog(`Get CurrentTemperature: ${this.HeaterCooler.CurrentTemperature}`); - return this.HeaterCooler.CurrentTemperature; + this.HeaterCooler.CurrentTemperature = this.accessory.context.CurrentTemperature || 24 + await this.debugLog(`Get CurrentTemperature: ${this.HeaterCooler.CurrentTemperature}`) + return this.HeaterCooler.CurrentTemperature } async CurrentRelativeHumidityGet(): Promise { if (this.meter?.context?.CurrentRelativeHumidity) { - this.accessory.context.CurrentRelativeHumidity = this.meter.context.CurrentRelativeHumidity; - await this.debugLog('' - + `Using CurrentRelativeHumidity from ${this.meter.context.deviceType} (${this.meter.context.deviceId})`); + this.accessory.context.CurrentRelativeHumidity = this.meter.context.CurrentRelativeHumidity + await this.debugLog(`Using CurrentRelativeHumidity from ${this.meter.context.deviceType} (${this.meter.context.deviceId})`) } - this.HumiditySensor!.CurrentRelativeHumidity = this.accessory.context.CurrentRelativeHumidity || 0; - await this.debugLog('Get' - + ` CurrentRelativeHumidity: ${this.HumiditySensor!.CurrentRelativeHumidity}`); - return this.HumiditySensor!.CurrentRelativeHumidity as CharacteristicValue; + this.HumiditySensor!.CurrentRelativeHumidity = this.accessory.context.CurrentRelativeHumidity || 0 + await this.debugLog(`Get CurrentRelativeHumidity: ${this.HumiditySensor!.CurrentRelativeHumidity}`) + return this.HumiditySensor!.CurrentRelativeHumidity as CharacteristicValue } async RotationSpeedGet(): Promise { if (!this.CurrentFanSpeed || this.CurrentFanSpeed === 1) { - this.HeaterCooler.RotationSpeed = 4; + this.HeaterCooler.RotationSpeed = 4 } else { - this.HeaterCooler.RotationSpeed = this.CurrentFanSpeed - 1; + this.HeaterCooler.RotationSpeed = this.CurrentFanSpeed - 1 } - await this.debugLog(`Get RotationSpeed: ${this.HeaterCooler.RotationSpeed}`); - return this.HeaterCooler.RotationSpeed; + await this.debugLog(`Get RotationSpeed: ${this.HeaterCooler.RotationSpeed}`) + return this.HeaterCooler.RotationSpeed } async RotationSpeedSet(value: CharacteristicValue): Promise { if (value === 4) { - this.CurrentFanSpeed = 1; + this.CurrentFanSpeed = 1 } else { - this.CurrentFanSpeed = Number(value) + 1; + this.CurrentFanSpeed = Number(value) + 1 } - this.HeaterCooler.RotationSpeed = value; - await this.debugLog(`Set RotationSpeed: ${this.HeaterCooler.RotationSpeed}, CurrentFanSpeed: ${this.CurrentFanSpeed}`); - this.pushAirConditionerStatusChanges(); + this.HeaterCooler.RotationSpeed = value + await this.debugLog(`Set RotationSpeed: ${this.HeaterCooler.RotationSpeed}, CurrentFanSpeed: ${this.CurrentFanSpeed}`) + this.pushAirConditionerStatusChanges() } async ActiveSet(value: CharacteristicValue): Promise { - await this.debugLog(`Set Active: ${value}`); + await this.debugLog(`Set Active: ${value}`) - this.HeaterCooler.Active = value; + this.HeaterCooler.Active = value if (this.HeaterCooler.Active === this.hap.Characteristic.Active.ACTIVE) { - await this.debugLog(`pushAirConditionerOnChanges, Active: ${this.HeaterCooler.Active}`); + await this.debugLog(`pushAirConditionerOnChanges, Active: ${this.HeaterCooler.Active}`) if (this.disablePushOn) { - this.pushAirConditionerStatusChanges(); + this.pushAirConditionerStatusChanges() } else { - this.pushAirConditionerOnChanges(); + this.pushAirConditionerOnChanges() } } else { - await this.debugLog(`pushAirConditionerOffChanges, Active: ${this.HeaterCooler.Active}`); - this.pushAirConditionerOffChanges(); + await this.debugLog(`pushAirConditionerOffChanges, Active: ${this.HeaterCooler.Active}`) + this.pushAirConditionerOffChanges() } } async TargetHeaterCoolerStateGet(): Promise { - const targetState = this.HeaterCooler.TargetHeaterCoolerState || this.accessory.context.TargetHeaterCoolerState; - this.HeaterCooler.TargetHeaterCoolerState = this.ValidValues.includes(targetState) ? targetState : this.ValidValues[0]; - await this.debugLog(`Get (${this.getTargetHeaterCoolerStateName()}) TargetHeaterCoolerState:` - + ` ${this.HeaterCooler.TargetHeaterCoolerState}, ValidValues: ${this.ValidValues}, hide_automode: ${this.hide_automode}`); - return this.HeaterCooler.TargetHeaterCoolerState; + const targetState = this.HeaterCooler.TargetHeaterCoolerState || this.accessory.context.TargetHeaterCoolerState + this.HeaterCooler.TargetHeaterCoolerState = this.ValidValues.includes(targetState) ? targetState : this.ValidValues[0] + await this.debugLog(`Get (${this.getTargetHeaterCoolerStateName()}) TargetHeaterCoolerState: ${this.HeaterCooler.TargetHeaterCoolerState}, ValidValues: ${this.ValidValues}, hide_automode: ${this.hide_automode}`) + return this.HeaterCooler.TargetHeaterCoolerState } async TargetHeaterCoolerStateSet(value: CharacteristicValue): Promise { if (!this.hide_automode && value === this.hap.Characteristic.TargetHeaterCoolerState.AUTO) { - this.TargetHeaterCoolerStateAUTO(); + this.TargetHeaterCoolerStateAUTO() } else if (value === this.hap.Characteristic.TargetHeaterCoolerState.HEAT) { - this.TargetHeaterCoolerStateHEAT(); + this.TargetHeaterCoolerStateHEAT() } else if (value === this.hap.Characteristic.TargetHeaterCoolerState.COOL) { - this.TargetHeaterCoolerStateCOOL(); + this.TargetHeaterCoolerStateCOOL() } else { - this.errorLog('Set TargetHeaterCoolerState: ' - + `${this.HeaterCooler.TargetHeaterCoolerState}, hide_automode: ${this.hide_automode} `); + this.errorLog(`Set TargetHeaterCoolerState: ${this.HeaterCooler.TargetHeaterCoolerState}, hide_automode: ${this.hide_automode} `) } - this.pushAirConditionerStatusChanges(); + this.pushAirConditionerStatusChanges() } async TargetHeaterCoolerStateAUTO(): Promise { - this.HeaterCooler.TargetHeaterCoolerState = this.hap.Characteristic.TargetHeaterCoolerState.AUTO; - this.CurrentMode = 1; - await this.debugLog('Set (AUTO)' - + ` TargetHeaterCoolerState: ${this.HeaterCooler.TargetHeaterCoolerState}`); - await this.debugLog(`Switchbot CurrentMode: ${this.CurrentMode}`); + this.HeaterCooler.TargetHeaterCoolerState = this.hap.Characteristic.TargetHeaterCoolerState.AUTO + this.CurrentMode = 1 + await this.debugLog(`Set (AUTO) TargetHeaterCoolerState: ${this.HeaterCooler.TargetHeaterCoolerState}`) + await this.debugLog(`Switchbot CurrentMode: ${this.CurrentMode}`) } async TargetHeaterCoolerStateCOOL(): Promise { - this.HeaterCooler.TargetHeaterCoolerState = this.hap.Characteristic.TargetHeaterCoolerState.COOL; - this.CurrentMode = 2; - await this.debugLog('Set (COOL)' - + ` TargetHeaterCoolerState: ${this.HeaterCooler.TargetHeaterCoolerState}`); - await this.debugLog(`Switchbot CurrentMode: ${this.CurrentMode}`); + this.HeaterCooler.TargetHeaterCoolerState = this.hap.Characteristic.TargetHeaterCoolerState.COOL + this.CurrentMode = 2 + await this.debugLog(`Set (COOL) TargetHeaterCoolerState: ${this.HeaterCooler.TargetHeaterCoolerState}`) + await this.debugLog(`Switchbot CurrentMode: ${this.CurrentMode}`) } async TargetHeaterCoolerStateHEAT(): Promise { - this.HeaterCooler.TargetHeaterCoolerState = this.hap.Characteristic.TargetHeaterCoolerState.HEAT; - this.CurrentMode = 5; - await this.debugLog('Set (HEAT)' - + ` TargetHeaterCoolerState: ${this.HeaterCooler.TargetHeaterCoolerState}`); - await this.debugLog(`Switchbot CurrentMode: ${this.CurrentMode}`); + this.HeaterCooler.TargetHeaterCoolerState = this.hap.Characteristic.TargetHeaterCoolerState.HEAT + this.CurrentMode = 5 + await this.debugLog(`Set (HEAT) TargetHeaterCoolerState: ${this.HeaterCooler.TargetHeaterCoolerState}`) + await this.debugLog(`Switchbot CurrentMode: ${this.CurrentMode}`) } async CurrentHeaterCoolerStateGet(): Promise { - await this.UpdateCurrentHeaterCoolerState(); - await this.debugLog(`Get (${this.getTargetHeaterCoolerStateName()}) CurrentHeaterCoolerState: ${this.HeaterCooler.CurrentHeaterCoolerState}`); + await this.UpdateCurrentHeaterCoolerState() + await this.debugLog(`Get (${this.getTargetHeaterCoolerStateName()}) CurrentHeaterCoolerState: ${this.HeaterCooler.CurrentHeaterCoolerState}`) - return this.HeaterCooler.CurrentHeaterCoolerState; + return this.HeaterCooler.CurrentHeaterCoolerState } - private getTargetHeaterCoolerStateName(): string { switch (this.HeaterCooler.TargetHeaterCoolerState) { case this.hap.Characteristic.TargetHeaterCoolerState.AUTO: - return 'AUTO'; + return 'AUTO' case this.hap.Characteristic.TargetHeaterCoolerState.HEAT: - return 'HEAT'; + return 'HEAT' case this.hap.Characteristic.TargetHeaterCoolerState.COOL: - return 'COOL'; + return 'COOL' default: - return this.HeaterCooler.TargetHeaterCoolerState.toString(); + return this.HeaterCooler.TargetHeaterCoolerState.toString() } } async ThresholdTemperatureGet(): Promise { - this.HeaterCooler.Active = this.HeaterCooler.Active === undefined ? this.hap.Characteristic.Active.INACTIVE - : this.HeaterCooler.Active ?? this.accessory.context.Active; + this.HeaterCooler.Active = this.HeaterCooler.Active === undefined + ? this.hap.Characteristic.Active.INACTIVE + : this.HeaterCooler.Active ?? this.accessory.context.Active - this.HeaterCooler.CurrentTemperature = (this.HeaterCooler.CurrentTemperature === undefined - && this.accessory.context.CurrentTemperature === undefined) ? 24 : this.HeaterCooler.CurrentTemperature - ?? this.accessory.context.CurrentTemperature; + this.HeaterCooler.CurrentTemperature = (this.HeaterCooler.CurrentTemperature === undefined && this.accessory.context.CurrentTemperature === undefined) + ? 24 + : this.HeaterCooler.CurrentTemperature ?? this.accessory.context.CurrentTemperature - this.HeaterCooler.ThresholdTemperature = this.HeaterCooler.ThresholdTemperature === undefined ? 24 : this.HeaterCooler.ThresholdTemperature - ?? this.accessory.context.ThresholdTemperature; + this.HeaterCooler.ThresholdTemperature = this.HeaterCooler.ThresholdTemperature === undefined + ? 24 + : this.HeaterCooler.ThresholdTemperature ?? this.accessory.context.ThresholdTemperature - this.HeaterCooler.RotationSpeed = (this.HeaterCooler.RotationSpeed === undefined && this.accessory.context.RotationSpeed === undefined) ? 4 - : this.HeaterCooler.RotationSpeed ?? this.accessory.context.RotationSpeed; + this.HeaterCooler.RotationSpeed = (this.HeaterCooler.RotationSpeed === undefined && this.accessory.context.RotationSpeed === undefined) + ? 4 + : this.HeaterCooler.RotationSpeed ?? this.accessory.context.RotationSpeed - await this.getAirConditionerConfigSettings(this.accessory, this.device); + await this.getAirConditionerConfigSettings(this.accessory, this.device) if (this.meter && this.HumiditySensor?.Service) { - this.HumiditySensor.CurrentRelativeHumidity = (this.HumiditySensor.CurrentRelativeHumidity === undefined - && this.accessory.context.CurrentRelativeHumidity === undefined) ? 0 : this.HumiditySensor.CurrentRelativeHumidity - ?? this.accessory.context.CurrentRelativeHumidity; + this.HumiditySensor.CurrentRelativeHumidity = (this.HumiditySensor.CurrentRelativeHumidity === undefined && this.accessory.context.CurrentRelativeHumidity === undefined) + ? 0 + : this.HumiditySensor.CurrentRelativeHumidity ?? this.accessory.context.CurrentRelativeHumidity } - await this.debugLog(`Get ThresholdTemperature: ${this.HeaterCooler.ThresholdTemperature}`); - return this.HeaterCooler.ThresholdTemperature; + await this.debugLog(`Get ThresholdTemperature: ${this.HeaterCooler.ThresholdTemperature}`) + return this.HeaterCooler.ThresholdTemperature } async ThresholdTemperatureSet(value: CharacteristicValue): Promise { - this.HeaterCooler.ThresholdTemperature = value; - await this.debugLog(`Set ThresholdTemperature: ${this.HeaterCooler.ThresholdTemperature},` - + ` ThresholdTemperatureCached: ${this.accessory.context.ThresholdTemperature}`); - this.pushAirConditionerStatusChanges(); + this.HeaterCooler.ThresholdTemperature = value + await this.debugLog(`Set ThresholdTemperature: ${this.HeaterCooler.ThresholdTemperature}, ThresholdTemperatureCached: ${this.accessory.context.ThresholdTemperature}`) + this.pushAirConditionerStatusChanges() } async updateHomeKitCharacteristics(): Promise { - await this.debugLog('updateHomeKitCharacteristics'); + await this.debugLog('updateHomeKitCharacteristics') // Active - await this.updateCharacteristic(this.HeaterCooler.Service, this.hap.Characteristic.Active, - this.HeaterCooler.Active, 'Active'); + await this.updateCharacteristic(this.HeaterCooler.Service, this.hap.Characteristic.Active, this.HeaterCooler.Active, 'Active') // RotationSpeed - await this.updateCharacteristic(this.HeaterCooler.Service, this.hap.Characteristic.RotationSpeed, - this.HeaterCooler.RotationSpeed, 'RotationSpeed'); + await this.updateCharacteristic(this.HeaterCooler.Service, this.hap.Characteristic.RotationSpeed, this.HeaterCooler.RotationSpeed, 'RotationSpeed') // CurrentTemperature - await this.updateCharacteristic(this.HeaterCooler.Service, this.hap.Characteristic.CurrentTemperature, - this.HeaterCooler.CurrentTemperature, 'CurrentTemperature'); + await this.updateCharacteristic(this.HeaterCooler.Service, this.hap.Characteristic.CurrentTemperature, this.HeaterCooler.CurrentTemperature, 'CurrentTemperature') // TargetHeaterCoolerState - await this.updateCharacteristic(this.HeaterCooler.Service, this.hap.Characteristic.TargetHeaterCoolerState, - this.HeaterCooler.TargetHeaterCoolerState, 'TargetHeaterCoolerState'); + await this.updateCharacteristic(this.HeaterCooler.Service, this.hap.Characteristic.TargetHeaterCoolerState, this.HeaterCooler.TargetHeaterCoolerState, 'TargetHeaterCoolerState') // CurrentHeaterCoolerState - await this.updateCharacteristic(this.HeaterCooler.Service, this.hap.Characteristic.CurrentHeaterCoolerState, - this.HeaterCooler.CurrentHeaterCoolerState, 'CurrentHeaterCoolerState'); + await this.updateCharacteristic(this.HeaterCooler.Service, this.hap.Characteristic.CurrentHeaterCoolerState, this.HeaterCooler.CurrentHeaterCoolerState, 'CurrentHeaterCoolerState') // HeatingThresholdTemperature - await this.updateCharacteristic(this.HeaterCooler.Service, this.hap.Characteristic.HeatingThresholdTemperature, - this.HeaterCooler.ThresholdTemperature, 'ThresholdTemperature'); + await this.updateCharacteristic(this.HeaterCooler.Service, this.hap.Characteristic.HeatingThresholdTemperature, this.HeaterCooler.ThresholdTemperature, 'ThresholdTemperature') // CoolingThresholdTemperature - await this.updateCharacteristic(this.HeaterCooler.Service, this.hap.Characteristic.CoolingThresholdTemperature, - this.HeaterCooler.ThresholdTemperature, 'ThresholdTemperature'); + await this.updateCharacteristic(this.HeaterCooler.Service, this.hap.Characteristic.CoolingThresholdTemperature, this.HeaterCooler.ThresholdTemperature, 'ThresholdTemperature') if (this.meter && this.HumiditySensor?.Service) { // CurrentRelativeHumidity - await this.updateCharacteristic(this.HumiditySensor.Service, this.hap.Characteristic.CurrentRelativeHumidity, - this.HumiditySensor.CurrentRelativeHumidity, 'CurrentRelativeHumidity'); + await this.updateCharacteristic(this.HumiditySensor.Service, this.hap.Characteristic.CurrentRelativeHumidity, this.HumiditySensor.CurrentRelativeHumidity, 'CurrentRelativeHumidity') } } async apiError(e: any): Promise { - this.HeaterCooler.Service.updateCharacteristic(this.hap.Characteristic.Active, e); - this.HeaterCooler.Service.updateCharacteristic(this.hap.Characteristic.RotationSpeed, e); - this.HeaterCooler.Service.updateCharacteristic(this.hap.Characteristic.CurrentTemperature, e); - this.HeaterCooler.Service.updateCharacteristic(this.hap.Characteristic.CurrentRelativeHumidity, e); - this.HeaterCooler.Service.updateCharacteristic(this.hap.Characteristic.TargetHeaterCoolerState, e); - this.HeaterCooler.Service.updateCharacteristic(this.hap.Characteristic.CurrentHeaterCoolerState, e); - this.HeaterCooler.Service.updateCharacteristic(this.hap.Characteristic.HeatingThresholdTemperature, e); - this.HeaterCooler.Service.updateCharacteristic(this.hap.Characteristic.CoolingThresholdTemperature, e); + this.HeaterCooler.Service.updateCharacteristic(this.hap.Characteristic.Active, e) + this.HeaterCooler.Service.updateCharacteristic(this.hap.Characteristic.RotationSpeed, e) + this.HeaterCooler.Service.updateCharacteristic(this.hap.Characteristic.CurrentTemperature, e) + this.HeaterCooler.Service.updateCharacteristic(this.hap.Characteristic.CurrentRelativeHumidity, e) + this.HeaterCooler.Service.updateCharacteristic(this.hap.Characteristic.TargetHeaterCoolerState, e) + this.HeaterCooler.Service.updateCharacteristic(this.hap.Characteristic.CurrentHeaterCoolerState, e) + this.HeaterCooler.Service.updateCharacteristic(this.hap.Characteristic.HeatingThresholdTemperature, e) + this.HeaterCooler.Service.updateCharacteristic(this.hap.Characteristic.CoolingThresholdTemperature, e) } async getAirConditionerConfigSettings(accessory: PlatformAccessory, device: irdevice & irDevicesConfig): Promise { - accessory.context.hide_automode = this.hide_automode = device.irair?.hide_automode; - accessory.context.set_max_heat = this.set_max_heat = device.irair?.set_max_heat ?? 35; - accessory.context.set_min_heat = this.set_min_heat = device.irair?.set_min_heat ?? 0; - accessory.context.set_max_cool = this.set_max_cool = device.irair?.set_max_cool ?? 35; - accessory.context.set_min_cool = this.set_min_cool = device.irair?.set_min_cool ?? 0; + accessory.context.hide_automode = this.hide_automode = device.irair?.hide_automode + accessory.context.set_max_heat = this.set_max_heat = device.irair?.set_max_heat ?? 35 + accessory.context.set_min_heat = this.set_min_heat = device.irair?.set_min_heat ?? 0 + accessory.context.set_max_cool = this.set_max_cool = device.irair?.set_max_cool ?? 35 + accessory.context.set_min_cool = this.set_min_cool = device.irair?.set_min_cool ?? 0 } } diff --git a/src/irdevice/airpurifier.ts b/src/irdevice/airpurifier.ts index 21f7edef..9c7187d8 100644 --- a/src/irdevice/airpurifier.ts +++ b/src/irdevice/airpurifier.ts @@ -2,12 +2,13 @@ * * airpurifier.ts: @switchbot/homebridge-switchbot. */ -import { irdeviceBase } from './irdevice.js'; +import type { CharacteristicValue, PlatformAccessory, Service } from 'homebridge' -import type { SwitchBotPlatform } from '../platform.js'; -import type { irDevicesConfig } from '../settings.js'; -import type { irdevice } from '../types/irdevicelist.js'; -import type { CharacteristicValue, PlatformAccessory, Service } from 'homebridge'; +import type { SwitchBotPlatform } from '../platform.js' +import type { irDevicesConfig } from '../settings.js' +import type { irdevice } from '../types/irdevicelist.js' + +import { irdeviceBase } from './irdevice.js' /** * Platform Accessory @@ -17,48 +18,48 @@ import type { CharacteristicValue, PlatformAccessory, Service } from 'homebridge export class AirPurifier extends irdeviceBase { // Services private AirPurifier: { - Name: CharacteristicValue; - Service: Service; - Active: CharacteristicValue; - RotationSpeed: CharacteristicValue; - CurrentAirPurifierState: CharacteristicValue; - TargetAirPurifierState: CharacteristicValue; - CurrentHeaterCoolerState: CharacteristicValue; - }; + Name: CharacteristicValue + Service: Service + Active: CharacteristicValue + RotationSpeed: CharacteristicValue + CurrentAirPurifierState: CharacteristicValue + TargetAirPurifierState: CharacteristicValue + CurrentHeaterCoolerState: CharacteristicValue + } private TemperatureSensor: { - Name: CharacteristicValue; - Service: Service; - CurrentTemperature: CharacteristicValue; - }; + Name: CharacteristicValue + Service: Service + CurrentTemperature: CharacteristicValue + } // Characteristic Values - APActive!: CharacteristicValue; - CurrentAPTemp!: CharacteristicValue; - CurrentAPMode!: CharacteristicValue; - CurrentAPFanSpeed!: CharacteristicValue; + APActive!: CharacteristicValue + CurrentAPTemp!: CharacteristicValue + CurrentAPMode!: CharacteristicValue + CurrentAPFanSpeed!: CharacteristicValue // Others - Busy: any; - Timeout: any = null; - static IDLE: number; - CurrentMode!: number; - static INACTIVE: number; - LastTemperature!: number; - CurrentFanSpeed!: number; - static PURIFYING_AIR: number; + Busy: any + Timeout: any = null + static IDLE: number + CurrentMode!: number + static INACTIVE: number + LastTemperature!: number + CurrentFanSpeed!: number + static PURIFYING_AIR: number constructor( readonly platform: SwitchBotPlatform, accessory: PlatformAccessory, device: irdevice & irDevicesConfig, ) { - super(platform, accessory, device); + super(platform, accessory, device) // Set category - accessory.category = this.hap.Categories.AIR_PURIFIER; + accessory.category = this.hap.Categories.AIR_PURIFIER // Initialize AirPurifier Service - accessory.context.AirPurifier = accessory.context.AirPurifier ?? {}; + accessory.context.AirPurifier = accessory.context.AirPurifier ?? {} this.AirPurifier = { Name: accessory.displayName, Service: accessory.getService(this.hap.Service.AirPurifier) ?? accessory.addService(this.hap.Service.AirPurifier) as Service, @@ -67,207 +68,187 @@ export class AirPurifier extends irdeviceBase { CurrentAirPurifierState: accessory.context.CurrentAirPurifierState ?? this.hap.Characteristic.CurrentAirPurifierState.INACTIVE, TargetAirPurifierState: accessory.context.TargetAirPurifierState ?? this.hap.Characteristic.TargetAirPurifierState.AUTO, CurrentHeaterCoolerState: accessory.context.CurrentHeaterCoolerState ?? this.hap.Characteristic.CurrentHeaterCoolerState.INACTIVE, - }; - accessory.context.AirPurifier = this.AirPurifier as object; + } + accessory.context.AirPurifier = this.AirPurifier as object - this.AirPurifier.Service - .setCharacteristic(this.hap.Characteristic.Name, this.AirPurifier.Name) - .getCharacteristic(this.hap.Characteristic.Active) - .onGet(() => { - return this.AirPurifier.Active; - }) - .onSet(this.ActiveSet.bind(this)); + this.AirPurifier.Service.setCharacteristic(this.hap.Characteristic.Name, this.AirPurifier.Name).getCharacteristic(this.hap.Characteristic.Active).onGet(() => { + return this.AirPurifier.Active + }).onSet(this.ActiveSet.bind(this)) - this.AirPurifier.Service - .getCharacteristic(this.hap.Characteristic.CurrentAirPurifierState) - .onGet(() => { - return this.CurrentAirPurifierStateGet(); - }); + this.AirPurifier.Service.getCharacteristic(this.hap.Characteristic.CurrentAirPurifierState).onGet(() => { + return this.CurrentAirPurifierStateGet() + }) - this.AirPurifier.Service - .getCharacteristic(this.hap.Characteristic.TargetAirPurifierState) - .onGet(() => { - return this.AirPurifier.TargetAirPurifierState; - }) - .onSet(this.TargetAirPurifierStateSet.bind(this)); + this.AirPurifier.Service.getCharacteristic(this.hap.Characteristic.TargetAirPurifierState).onGet(() => { + return this.AirPurifier.TargetAirPurifierState + }).onSet(this.TargetAirPurifierStateSet.bind(this)) // Initialize TemperatureSensor Service - accessory.context.TemperatureSensor = accessory.context.TemperatureSensor ?? {}; + accessory.context.TemperatureSensor = accessory.context.TemperatureSensor ?? {} this.TemperatureSensor = { Name: `${accessory.displayName} Temperature Sensor`, Service: accessory.getService(this.hap.Service.TemperatureSensor) ?? accessory.addService(this.hap.Service.TemperatureSensor) as Service, CurrentTemperature: accessory.context.CurrentTemperature || 24, - }; - accessory.context.TemperatureSensor = this.TemperatureSensor as object; + } + accessory.context.TemperatureSensor = this.TemperatureSensor as object - this.TemperatureSensor.Service - .setCharacteristic(this.hap.Characteristic.Name, this.TemperatureSensor.Name) - .getCharacteristic(this.hap.Characteristic.CurrentTemperature) - .onGet(() => { - return this.TemperatureSensor.CurrentTemperature; - }); + this.TemperatureSensor.Service.setCharacteristic(this.hap.Characteristic.Name, this.TemperatureSensor.Name).getCharacteristic(this.hap.Characteristic.CurrentTemperature).onGet(() => { + return this.TemperatureSensor.CurrentTemperature + }) } async ActiveSet(value: CharacteristicValue): Promise { - await this.debugLog(`Set Active: ${value}`); + await this.debugLog(`Set Active: ${value}`) - this.AirPurifier.Active = value; + this.AirPurifier.Active = value if (this.AirPurifier.Active === this.hap.Characteristic.Active.ACTIVE) { - this.pushAirPurifierOnChanges(); + this.pushAirPurifierOnChanges() } else { - this.pushAirPurifierOffChanges(); + this.pushAirPurifierOffChanges() } } async TargetAirPurifierStateSet(value: CharacteristicValue): Promise { switch (value) { case this.hap.Characteristic.CurrentAirPurifierState.PURIFYING_AIR: - this.CurrentMode = AirPurifier.PURIFYING_AIR; - break; + this.CurrentMode = AirPurifier.PURIFYING_AIR + break case this.hap.Characteristic.CurrentAirPurifierState.IDLE: - this.CurrentMode = AirPurifier.IDLE; - break; + this.CurrentMode = AirPurifier.IDLE + break case this.hap.Characteristic.CurrentAirPurifierState.INACTIVE: - this.CurrentMode = AirPurifier.INACTIVE; - break; + this.CurrentMode = AirPurifier.INACTIVE + break default: - break; + break } } async CurrentAirPurifierStateGet(): Promise { if (this.AirPurifier.Active === 1) { - this.AirPurifier.CurrentAirPurifierState = this.hap.Characteristic.CurrentAirPurifierState.PURIFYING_AIR; + this.AirPurifier.CurrentAirPurifierState = this.hap.Characteristic.CurrentAirPurifierState.PURIFYING_AIR } else { - this.AirPurifier.CurrentAirPurifierState = this.hap.Characteristic.CurrentAirPurifierState.INACTIVE; + this.AirPurifier.CurrentAirPurifierState = this.hap.Characteristic.CurrentAirPurifierState.INACTIVE } - return this.AirPurifier.CurrentAirPurifierState; + return this.AirPurifier.CurrentAirPurifierState } /** * Pushes the requested changes to the SwitchBot API - * deviceType commandType Command command parameter Description - * AirPurifier: "command" "turnOn" "default" = every home appliance can be turned on by default - * AirPurifier: "command" "turnOff" "default" = every home appliance can be turned off by default - * AirPurifier: "command" "swing" "default" = swing - * AirPurifier: "command" "timer" "default" = timer - * AirPurifier: "command" "lowSpeed" "default" = fan speed to low - * AirPurifier: "command" "middleSpeed" "default" = fan speed to medium - * AirPurifier: "command" "highSpeed" "default" = fan speed to high + * deviceType commandType Command command parameter Description + * AirPurifier: "command" "turnOn" "default" = every home appliance can be turned on by default + * AirPurifier: "command" "turnOff" "default" = every home appliance can be turned off by default + * AirPurifier: "command" "swing" "default" = swing + * AirPurifier: "command" "timer" "default" = timer + * AirPurifier: "command" "lowSpeed" "default" = fan speed to low + * AirPurifier: "command" "middleSpeed" "default" = fan speed to medium + * AirPurifier: "command" "highSpeed" "default" = fan speed to high */ async pushAirPurifierOnChanges(): Promise { - await this.debugLog(`pushAirPurifierOnChanges Active: ${this.AirPurifier.Active},` - + ` disablePushOn: ${this.disablePushOn}`); + await this.debugLog(`pushAirPurifierOnChanges Active: ${this.AirPurifier.Active}, disablePushOn: ${this.disablePushOn}`) if (this.AirPurifier.Active === this.hap.Characteristic.Active.ACTIVE && !this.disablePushOn) { - const commandType: string = await this.commandType(); - const command: string = await this.commandOn(); + const commandType: string = await this.commandType() + const command: string = await this.commandOn() const bodyChange = JSON.stringify({ - command: command, + command, parameter: 'default', - commandType: commandType, - }); - await this.pushChanges(bodyChange); + commandType, + }) + await this.pushChanges(bodyChange) } } async pushAirPurifierOffChanges(): Promise { - await this.debugLog(`pushAirPurifierOffChanges Active: ${this.AirPurifier.Active},` - + ` disablePushOff: ${this.disablePushOff}`); + await this.debugLog(`pushAirPurifierOffChanges Active: ${this.AirPurifier.Active}, disablePushOff: ${this.disablePushOff}`) if (this.AirPurifier.Active === this.hap.Characteristic.Active.INACTIVE && !this.disablePushOn) { - const commandType: string = await this.commandType(); - const command: string = await this.commandOff(); + const commandType: string = await this.commandType() + const command: string = await this.commandOff() const bodyChange = JSON.stringify({ - command: command, + command, parameter: 'default', - commandType: commandType, - }); - await this.pushChanges(bodyChange); + commandType, + }) + await this.pushChanges(bodyChange) } } async pushAirPurifierStatusChanges(): Promise { - await this.debugLog(`pushAirPurifierStatusChanges Active: ${this.AirPurifier.Active},` - + ` disablePushOff: ${this.disablePushOff}, disablePushOn: ${this.disablePushOn}`); + await this.debugLog(`pushAirPurifierStatusChanges Active: ${this.AirPurifier.Active}, disablePushOff: ${this.disablePushOff}, disablePushOn: ${this.disablePushOn}`) if (!this.Busy) { - this.Busy = true; - this.AirPurifier.CurrentHeaterCoolerState = this.hap.Characteristic.CurrentHeaterCoolerState.IDLE; + this.Busy = true + this.AirPurifier.CurrentHeaterCoolerState = this.hap.Characteristic.CurrentHeaterCoolerState.IDLE } - clearTimeout(this.Timeout); + clearTimeout(this.Timeout) // Make a new Timeout set to go off in 1000ms (1 second) - this.Timeout = setTimeout(this.pushAirPurifierDetailsChanges.bind(this), 1500); + this.Timeout = setTimeout(this.pushAirPurifierDetailsChanges.bind(this), 1500) } async pushAirPurifierDetailsChanges(): Promise { - await this.debugLog(`pushAirPurifierDetailsChanges Active: ${this.AirPurifier.Active},` - + ` disablePushOff: ${this.disablePushOff}, disablePushOn: ${this.disablePushOn}`); - this.CurrentAPTemp = this.TemperatureSensor!.CurrentTemperature ?? 24; - this.CurrentAPMode = this.CurrentMode ?? 1; - this.CurrentAPFanSpeed = this.CurrentFanSpeed ?? 1; - this.APActive = this.AirPurifier.Active === 1 ? 'on' : 'off'; - const parameter = `${this.CurrentAPTemp},${this.CurrentAPMode},${this.CurrentAPFanSpeed},${this.APActive}`; + await this.debugLog(`pushAirPurifierDetailsChanges Active: ${this.AirPurifier.Active}, disablePushOff: ${this.disablePushOff}, disablePushOn: ${this.disablePushOn}`) + this.CurrentAPTemp = this.TemperatureSensor!.CurrentTemperature ?? 24 + this.CurrentAPMode = this.CurrentMode ?? 1 + this.CurrentAPFanSpeed = this.CurrentFanSpeed ?? 1 + this.APActive = this.AirPurifier.Active === 1 ? 'on' : 'off' + const parameter = `${this.CurrentAPTemp},${this.CurrentAPMode},${this.CurrentAPFanSpeed},${this.APActive}` const bodyChange = JSON.stringify({ command: 'setAll', parameter: `${parameter}`, commandType: 'command', - }); + }) if (this.AirPurifier.Active === 1) { if ((Number(this.TemperatureSensor!.CurrentTemperature) || 24) < (this.LastTemperature || 30)) { - this.AirPurifier.CurrentHeaterCoolerState = this.hap.Characteristic.CurrentHeaterCoolerState.COOLING; + this.AirPurifier.CurrentHeaterCoolerState = this.hap.Characteristic.CurrentHeaterCoolerState.COOLING } else { - this.AirPurifier.CurrentHeaterCoolerState = this.hap.Characteristic.CurrentHeaterCoolerState.HEATING; + this.AirPurifier.CurrentHeaterCoolerState = this.hap.Characteristic.CurrentHeaterCoolerState.HEATING } } else { - this.AirPurifier.CurrentHeaterCoolerState = this.hap.Characteristic.CurrentHeaterCoolerState.INACTIVE; + this.AirPurifier.CurrentHeaterCoolerState = this.hap.Characteristic.CurrentHeaterCoolerState.INACTIVE } - await this.pushChanges(bodyChange); + await this.pushChanges(bodyChange) } async pushChanges(bodyChange: any): Promise { - await this.debugLog('pushChanges'); + await this.debugLog('pushChanges') if (this.device.connectionType === 'OpenAPI') { - this.infoLog(`Sending request to SwitchBot API, body: ${bodyChange},`); + this.infoLog(`Sending request to SwitchBot API, body: ${bodyChange},`) try { - const { body, statusCode } = await this.pushChangeRequest(bodyChange); - const deviceStatus: any = await body.json(); - await this.pushStatusCodes(statusCode, deviceStatus); + const { body, statusCode } = await this.pushChangeRequest(bodyChange) + const deviceStatus: any = await body.json() + await this.pushStatusCodes(statusCode, deviceStatus) if (await this.successfulStatusCodes(statusCode, deviceStatus)) { - await this.successfulPushChange(statusCode, deviceStatus, bodyChange); - await this.updateHomeKitCharacteristics(); + await this.successfulPushChange(statusCode, deviceStatus, bodyChange) + await this.updateHomeKitCharacteristics() } else { - await this.statusCode(statusCode); - await this.statusCode(deviceStatus.statusCode); + await this.statusCode(statusCode) + await this.statusCode(deviceStatus.statusCode) } } catch (e: any) { - await this.apiError(e); - await this.pushChangeError(e); + await this.apiError(e) + await this.pushChangeError(e) } } else { - this.warnLog(`Connection Type: ${this.device.connectionType}, commands will not be sent to OpenAPI`); + this.warnLog(`Connection Type: ${this.device.connectionType}, commands will not be sent to OpenAPI`) } } async updateHomeKitCharacteristics(): Promise { - await this.debugLog('updateHomeKitCharacteristics'); + await this.debugLog('updateHomeKitCharacteristics') // Active - await this.updateCharacteristic(this.AirPurifier.Service, this.hap.Characteristic.Active, - this.AirPurifier.Active, 'Active'); + await this.updateCharacteristic(this.AirPurifier.Service, this.hap.Characteristic.Active, this.AirPurifier.Active, 'Active') // CurrentAirPurifierState - await this.updateCharacteristic(this.AirPurifier.Service, this.hap.Characteristic.CurrentAirPurifierState, - this.AirPurifier.CurrentAirPurifierState, 'CurrentAirPurifierState'); + await this.updateCharacteristic(this.AirPurifier.Service, this.hap.Characteristic.CurrentAirPurifierState, this.AirPurifier.CurrentAirPurifierState, 'CurrentAirPurifierState') // CurrentHeaterCoolerState - await this.updateCharacteristic(this.AirPurifier.Service, this.hap.Characteristic.CurrentHeaterCoolerState, - this.AirPurifier.CurrentHeaterCoolerState, 'CurrentHeaterCoolerState'); + await this.updateCharacteristic(this.AirPurifier.Service, this.hap.Characteristic.CurrentHeaterCoolerState, this.AirPurifier.CurrentHeaterCoolerState, 'CurrentHeaterCoolerState') // CurrentTemperature - await this.updateCharacteristic(this.TemperatureSensor.Service, this.hap.Characteristic.CurrentTemperature, - this.TemperatureSensor.CurrentTemperature, 'CurrentTemperature'); + await this.updateCharacteristic(this.TemperatureSensor.Service, this.hap.Characteristic.CurrentTemperature, this.TemperatureSensor.CurrentTemperature, 'CurrentTemperature') } async apiError(e: any): Promise { - this.AirPurifier.Service.updateCharacteristic(this.hap.Characteristic.Active, e); - this.AirPurifier.Service.updateCharacteristic(this.hap.Characteristic.TargetAirPurifierState, e); - this.AirPurifier.Service.updateCharacteristic(this.hap.Characteristic.CurrentAirPurifierState, e); - this.AirPurifier.Service.updateCharacteristic(this.hap.Characteristic.CurrentHeaterCoolerState, e); - this.TemperatureSensor.Service.updateCharacteristic(this.hap.Characteristic.CurrentTemperature, e); + this.AirPurifier.Service.updateCharacteristic(this.hap.Characteristic.Active, e) + this.AirPurifier.Service.updateCharacteristic(this.hap.Characteristic.TargetAirPurifierState, e) + this.AirPurifier.Service.updateCharacteristic(this.hap.Characteristic.CurrentAirPurifierState, e) + this.AirPurifier.Service.updateCharacteristic(this.hap.Characteristic.CurrentHeaterCoolerState, e) + this.TemperatureSensor.Service.updateCharacteristic(this.hap.Characteristic.CurrentTemperature, e) } } diff --git a/src/irdevice/camera.ts b/src/irdevice/camera.ts index dccbc0e5..69dfa1e1 100644 --- a/src/irdevice/camera.ts +++ b/src/irdevice/camera.ts @@ -2,12 +2,13 @@ * * camera.ts: @switchbot/homebridge-switchbot. */ -import { irdeviceBase } from './irdevice.js'; +import type { CharacteristicValue, PlatformAccessory, Service } from 'homebridge' -import type { SwitchBotPlatform } from '../platform.js'; -import type { irDevicesConfig } from '../settings.js'; -import type { irdevice } from '../types/irdevicelist.js'; -import type { CharacteristicValue, PlatformAccessory, Service } from 'homebridge'; +import type { SwitchBotPlatform } from '../platform.js' +import type { irDevicesConfig } from '../settings.js' +import type { irdevice } from '../types/irdevicelist.js' + +import { irdeviceBase } from './irdevice.js' /** * Platform Accessory @@ -17,121 +18,114 @@ import type { CharacteristicValue, PlatformAccessory, Service } from 'homebridge export class Camera extends irdeviceBase { // Services private Switch: { - Name: CharacteristicValue; - Service: Service; - On: CharacteristicValue; - }; + Name: CharacteristicValue + Service: Service + On: CharacteristicValue + } constructor( readonly platform: SwitchBotPlatform, accessory: PlatformAccessory, device: irdevice & irDevicesConfig, ) { - super(platform, accessory, device); + super(platform, accessory, device) // Set category - accessory.category = this.hap.Categories.CAMERA; + accessory.category = this.hap.Categories.CAMERA // Initialize Switch Service - accessory.context.Switch = accessory.context.Switch ?? {}; + accessory.context.Switch = accessory.context.Switch ?? {} this.Switch = { Name: accessory.displayName, Service: accessory.getService(this.hap.Service.Switch) ?? accessory.addService(this.hap.Service.Switch) as Service, On: accessory.context.On ?? false, - }; - accessory.context.Switch = this.Switch as object; + } + accessory.context.Switch = this.Switch as object - this.Switch.Service - .setCharacteristic(this.hap.Characteristic.Name, this.Switch.Name) - .getCharacteristic(this.hap.Characteristic.On) - .onGet(() => { - return this.Switch.On; - }) - .onSet(this.OnSet.bind(this)); + this.Switch.Service.setCharacteristic(this.hap.Characteristic.Name, this.Switch.Name).getCharacteristic(this.hap.Characteristic.On).onGet(() => { + return this.Switch.On + }).onSet(this.OnSet.bind(this)) } async OnSet(value: CharacteristicValue): Promise { - await this.debugLog(`On: ${value}`); + await this.debugLog(`On: ${value}`) - this.Switch.On = value; + this.Switch.On = value if (this.Switch.On) { - this.pushOnChanges(); + this.pushOnChanges() } else { - this.pushOffChanges(); + this.pushOffChanges() } } /** * Pushes the requested changes to the SwitchBot API - * deviceType commandType Command command parameter Description - * Camera - "command" "turnOff" "default" = set to OFF state - * Camera - "command" "turnOn" "default" = set to ON state - * Camera - "command" "volumeAdd" "default" = volume up - * Camera - "command" "volumeSub" "default" = volume down - * Camera - "command" "channelAdd" "default" = next channel - * Camera - "command" "channelSub" "default" = previous channel + * deviceType commandType Command command parameter Description + * Camera - "command" "turnOff" "default" = set to OFF state + * Camera - "command" "turnOn" "default" = set to ON state + * Camera - "command" "volumeAdd" "default" = volume up + * Camera - "command" "volumeSub" "default" = volume down + * Camera - "command" "channelAdd" "default" = next channel + * Camera - "command" "channelSub" "default" = previous channel */ async pushOnChanges(): Promise { - await this.debugLog(`pushOnChanges On: ${this.Switch.On},` - + ` disablePushOn: ${this.disablePushOn}`); + await this.debugLog(`pushOnChanges On: ${this.Switch.On}, disablePushOn: ${this.disablePushOn}`) if (this.Switch.On && !this.disablePushOn) { - const commandType: string = await this.commandType(); - const command: string = await this.commandOn(); + const commandType: string = await this.commandType() + const command: string = await this.commandOn() const bodyChange = JSON.stringify({ - command: command, + command, parameter: 'default', - commandType: commandType, - }); - await this.pushChanges(bodyChange); + commandType, + }) + await this.pushChanges(bodyChange) } } async pushOffChanges(): Promise { - await this.debugLog(`pushOffChanges On: ${this.Switch.On},` - + ` disablePushOff: ${this.disablePushOff}`); + await this.debugLog(`pushOffChanges On: ${this.Switch.On}, disablePushOff: ${this.disablePushOff}`) if (!this.Switch.On && !this.disablePushOff) { - const commandType: string = await this.commandType(); - const command: string = await this.commandOff(); + const commandType: string = await this.commandType() + const command: string = await this.commandOff() const bodyChange = JSON.stringify({ - command: command, + command, parameter: 'default', - commandType: commandType, - }); - await this.pushChanges(bodyChange); + commandType, + }) + await this.pushChanges(bodyChange) } } async pushChanges(bodyChange: any): Promise { - await this.debugLog('pushChanges'); + await this.debugLog('pushChanges') if (this.device.connectionType === 'OpenAPI') { - this.infoLog(`Sending request to SwitchBot API, body: ${bodyChange},`); + this.infoLog(`Sending request to SwitchBot API, body: ${bodyChange},`) try { - const { body, statusCode } = await this.pushChangeRequest(bodyChange); - const deviceStatus: any = await body.json(); - await this.pushStatusCodes(statusCode, deviceStatus); + const { body, statusCode } = await this.pushChangeRequest(bodyChange) + const deviceStatus: any = await body.json() + await this.pushStatusCodes(statusCode, deviceStatus) if (await this.successfulStatusCodes(statusCode, deviceStatus)) { - await this.successfulPushChange(statusCode, deviceStatus, bodyChange); - await this.updateHomeKitCharacteristics(); + await this.successfulPushChange(statusCode, deviceStatus, bodyChange) + await this.updateHomeKitCharacteristics() } else { - await this.statusCode(statusCode); - await this.statusCode(deviceStatus.statusCode); + await this.statusCode(statusCode) + await this.statusCode(deviceStatus.statusCode) } } catch (e: any) { - await this.apiError(e); - await this.pushChangeError(e); + await this.apiError(e) + await this.pushChangeError(e) } } else { - await this.warnLog(`Connection Type: ${this.device.connectionType}, commands will not be sent to OpenAPI`); + await this.warnLog(`Connection Type: ${this.device.connectionType}, commands will not be sent to OpenAPI`) } } async updateHomeKitCharacteristics(): Promise { - await this.debugLog('updateHomeKitCharacteristics'); + await this.debugLog('updateHomeKitCharacteristics') // Active - await this.updateCharacteristic(this.Switch.Service, this.hap.Characteristic.On, - this.Switch.On, 'On'); + await this.updateCharacteristic(this.Switch.Service, this.hap.Characteristic.On, this.Switch.On, 'On') } async apiError(e: any): Promise { - this.Switch.Service.updateCharacteristic(this.hap.Characteristic.On, e); + this.Switch.Service.updateCharacteristic(this.hap.Characteristic.On, e) } } diff --git a/src/irdevice/fan.ts b/src/irdevice/fan.ts index b7f2c664..f6e8d2af 100644 --- a/src/irdevice/fan.ts +++ b/src/irdevice/fan.ts @@ -2,12 +2,13 @@ * * fan.ts: @switchbot/homebridge-switchbot. */ -import { irdeviceBase } from './irdevice.js'; +import type { CharacteristicValue, PlatformAccessory, Service } from 'homebridge' -import type { SwitchBotPlatform } from '../platform.js'; -import type { irDevicesConfig } from '../settings.js'; -import type { irdevice } from '../types/irdevicelist.js'; -import type { CharacteristicValue, PlatformAccessory, Service } from 'homebridge'; +import type { SwitchBotPlatform } from '../platform.js' +import type { irDevicesConfig } from '../settings.js' +import type { irdevice } from '../types/irdevicelist.js' + +import { irdeviceBase } from './irdevice.js' /** * Platform Accessory @@ -17,25 +18,25 @@ import type { CharacteristicValue, PlatformAccessory, Service } from 'homebridge export class IRFan extends irdeviceBase { // Services private Fan: { - Name: CharacteristicValue; - Service: Service; - Active: CharacteristicValue; - SwingMode: CharacteristicValue; - RotationSpeed: CharacteristicValue; - RotationDirection: CharacteristicValue; - }; + Name: CharacteristicValue + Service: Service + Active: CharacteristicValue + SwingMode: CharacteristicValue + RotationSpeed: CharacteristicValue + RotationDirection: CharacteristicValue + } constructor( readonly platform: SwitchBotPlatform, accessory: PlatformAccessory, device: irdevice & irDevicesConfig, ) { - super(platform, accessory, device); + super(platform, accessory, device) // Set category - accessory.category = this.hap.Categories.FAN; + accessory.category = this.hap.Categories.FAN // Initialize Switch Service - accessory.context.Fan = accessory.context.Fan ?? {}; + accessory.context.Fan = accessory.context.Fan ?? {} this.Fan = { Name: accessory.displayName, Service: accessory.getService(this.hap.Service.Fanv2) ?? accessory.addService(this.hap.Service.Fanv2) as Service, @@ -43,133 +44,118 @@ export class IRFan extends irdeviceBase { SwingMode: accessory.context.SwingMode ?? this.hap.Characteristic.SwingMode.SWING_DISABLED, RotationSpeed: accessory.context.RotationSpeed ?? 0, RotationDirection: accessory.context.RotationDirection ?? this.hap.Characteristic.RotationDirection.CLOCKWISE, - }; - accessory.context.Fan = this.Fan as object; - - this.Fan.Service - .setCharacteristic(this.hap.Characteristic.Name, this.Fan.Name) - .getCharacteristic(this.hap.Characteristic.Active) - .onGet(() => { - return this.Fan.Active; - }) - .onSet(this.ActiveSet.bind(this)); + } + accessory.context.Fan = this.Fan as object + + this.Fan.Service.setCharacteristic(this.hap.Characteristic.Name, this.Fan.Name).getCharacteristic(this.hap.Characteristic.Active).onGet(() => { + return this.Fan.Active + }).onSet(this.ActiveSet.bind(this)) if (device.irfan?.rotation_speed) { // handle Rotation Speed events using the RotationSpeed characteristic - this.Fan.Service - .getCharacteristic(this.hap.Characteristic.RotationSpeed) - .setProps({ - minStep: device.irfan?.set_minStep ?? 1, - minValue: device.irfan?.set_min ?? 1, - maxValue: device.irfan?.set_max ?? 100, - }) - .onGet(() => { - return this.Fan.RotationSpeed; - }) - .onSet(this.RotationSpeedSet.bind(this)); + this.Fan.Service.getCharacteristic(this.hap.Characteristic.RotationSpeed).setProps({ + minStep: device.irfan?.set_minStep ?? 1, + minValue: device.irfan?.set_min ?? 1, + maxValue: device.irfan?.set_max ?? 100, + }).onGet(() => { + return this.Fan.RotationSpeed + }).onSet(this.RotationSpeedSet.bind(this)) } else if (this.Fan.Service.testCharacteristic(this.hap.Characteristic.RotationSpeed) && !device.irfan?.swing_mode) { - const characteristic = this.Fan.Service.getCharacteristic(this.hap.Characteristic.RotationSpeed); - this.Fan.Service.removeCharacteristic(characteristic); - this.debugLog('Rotation Speed Characteristic was removed.'); + const characteristic = this.Fan.Service.getCharacteristic(this.hap.Characteristic.RotationSpeed) + this.Fan.Service.removeCharacteristic(characteristic) + this.debugLog('Rotation Speed Characteristic was removed.') } else { - this.debugLog('RotationSpeed Characteristic was not removed/added, ' - + `Clear Cache on ${this.accessory.displayName} to remove Chracteristic`); + this.debugLog(`RotationSpeed Characteristic was not removed/added, Clear Cache on ${this.accessory.displayName} to remove Chracteristic`) } if (device.irfan?.swing_mode) { // handle Osolcation events using the SwingMode characteristic - this.Fan.Service - .getCharacteristic(this.hap.Characteristic.SwingMode) - .onGet(() => { - return this.Fan.SwingMode; - }) - .onSet(this.SwingModeSet.bind(this)); + this.Fan.Service.getCharacteristic(this.hap.Characteristic.SwingMode).onGet(() => { + return this.Fan.SwingMode + }).onSet(this.SwingModeSet.bind(this)) } else if (this.Fan.Service.testCharacteristic(this.hap.Characteristic.SwingMode) && !device.irfan?.swing_mode) { - const characteristic = this.Fan.Service.getCharacteristic(this.hap.Characteristic.SwingMode); - this.Fan.Service.removeCharacteristic(characteristic); - this.debugLog('Swing Mode Characteristic was removed.'); + const characteristic = this.Fan.Service.getCharacteristic(this.hap.Characteristic.SwingMode) + this.Fan.Service.removeCharacteristic(characteristic) + this.debugLog('Swing Mode Characteristic was removed.') } else { - this.debugLog('Swing Mode Characteristic was not removed/added, ' - + `Clear Cache on ${this.accessory.displayName} To Remove Chracteristic`); + this.debugLog(`Swing Mode Characteristic was not removed/added, Clear Cache on ${this.accessory.displayName} To Remove Chracteristic`) } } async SwingModeSet(value: CharacteristicValue): Promise { - await this.debugLog(`SwingMode: ${value}`); + await this.debugLog(`SwingMode: ${value}`) if (value > this.Fan.SwingMode) { - this.Fan.SwingMode = 1; - await this.pushFanOnChanges(); - await this.pushFanSwingChanges(); + this.Fan.SwingMode = 1 + await this.pushFanOnChanges() + await this.pushFanSwingChanges() } else { - this.Fan.SwingMode = 0; - await this.pushFanOnChanges(); - await this.pushFanSwingChanges(); + this.Fan.SwingMode = 0 + await this.pushFanOnChanges() + await this.pushFanSwingChanges() } - this.Fan.SwingMode = value; - this.accessory.context.SwingMode = this.Fan.SwingMode; + this.Fan.SwingMode = value + this.accessory.context.SwingMode = this.Fan.SwingMode } async RotationSpeedSet(value: CharacteristicValue): Promise { - await this.debugLog(`RotationSpeed: ${value}`); + await this.debugLog(`RotationSpeed: ${value}`) if (value > this.Fan.RotationSpeed) { - this.Fan.RotationSpeed = 1; - this.pushFanSpeedUpChanges(); - this.pushFanOnChanges(); + this.Fan.RotationSpeed = 1 + this.pushFanSpeedUpChanges() + this.pushFanOnChanges() } else { - this.Fan.RotationSpeed = 0; - this.pushFanSpeedDownChanges(); + this.Fan.RotationSpeed = 0 + this.pushFanSpeedDownChanges() } - this.Fan.RotationSpeed = value; - this.accessory.context.RotationSpeed = this.Fan.RotationSpeed; + this.Fan.RotationSpeed = value + this.accessory.context.RotationSpeed = this.Fan.RotationSpeed } async ActiveSet(value: CharacteristicValue): Promise { - await this.debugLog(`Active: ${value}`); + await this.debugLog(`Active: ${value}`) - this.Fan.Active = value; + this.Fan.Active = value if (this.Fan.Active === this.hap.Characteristic.Active.ACTIVE) { - this.pushFanOnChanges(); + this.pushFanOnChanges() } else { - this.pushFanOffChanges(); + this.pushFanOffChanges() } } /** * Pushes the requested changes to the SwitchBot API - * deviceType commandType Command command parameter Description - * Fan - "command" "swing" "default" = swing - * Fan - "command" "timer" "default" = timer - * Fan - "command" "lowSpeed" "default" = fan speed to low - * Fan - "command" "middleSpeed" "default" = fan speed to medium - * Fan - "command" "highSpeed" "default" = fan speed to high + * deviceType commandType Command command parameter Description + * Fan - "command" "swing" "default" = swing + * Fan - "command" "timer" "default" = timer + * Fan - "command" "lowSpeed" "default" = fan speed to low + * Fan - "command" "middleSpeed" "default" = fan speed to medium + * Fan - "command" "highSpeed" "default" = fan speed to high */ async pushFanOnChanges(): Promise { - await this.debugLog(`pushFanOnChanges Active: ${this.Fan.Active},` - + ` disablePushOn: ${this.disablePushOn}`); + await this.debugLog(`pushFanOnChanges Active: ${this.Fan.Active}, disablePushOn: ${this.disablePushOn}`) if (this.Fan.Active === this.hap.Characteristic.Active.ACTIVE && !this.disablePushOn) { - const commandType: string = await this.commandType(); - const command: string = await this.commandOn(); + const commandType: string = await this.commandType() + const command: string = await this.commandOn() const bodyChange = JSON.stringify({ - command: command, + command, parameter: 'default', - commandType: commandType, - }); - await this.pushChanges(bodyChange); + commandType, + }) + await this.pushChanges(bodyChange) } } async pushFanOffChanges(): Promise { - await this.debugLog(`pushLightOffChanges Active: ${this.Fan.Active},` - + ` disablePushOff: ${this.disablePushOff}`); + await this.debugLog(`pushLightOffChanges Active: ${this.Fan.Active}, disablePushOff: ${this.disablePushOff}`) if (this.Fan.Active === this.hap.Characteristic.Active.INACTIVE && !this.disablePushOff) { - const commandType: string = await this.commandType(); - const command: string = await this.commandOff(); + const commandType: string = await this.commandType() + const command: string = await this.commandOff() const bodyChange = JSON.stringify({ - command: command, + command, parameter: 'default', - commandType: commandType, - }); - await this.pushChanges(bodyChange); + commandType, + }) + await this.pushChanges(bodyChange) } } @@ -178,8 +164,8 @@ export class IRFan extends irdeviceBase { command: 'highSpeed', parameter: 'default', commandType: 'command', - }); - await this.pushChanges(bodyChange); + }) + await this.pushChanges(bodyChange) } async pushFanSpeedDownChanges(): Promise { @@ -187,8 +173,8 @@ export class IRFan extends irdeviceBase { command: 'lowSpeed', parameter: 'default', commandType: 'command', - }); - await this.pushChanges(bodyChange); + }) + await this.pushChanges(bodyChange) } async pushFanSwingChanges(): Promise { @@ -196,50 +182,47 @@ export class IRFan extends irdeviceBase { command: 'swing', parameter: 'default', commandType: 'command', - }); - await this.pushChanges(bodyChange); + }) + await this.pushChanges(bodyChange) } async pushChanges(bodyChange: any): Promise { - await this.debugLog('pushChanges'); + await this.debugLog('pushChanges') if (this.device.connectionType === 'OpenAPI') { - this.infoLog(`Sending request to SwitchBot API, body: ${bodyChange},`); + this.infoLog(`Sending request to SwitchBot API, body: ${bodyChange},`) try { - const { body, statusCode } = await this.pushChangeRequest(bodyChange); - const deviceStatus: any = await body.json(); - await this.pushStatusCodes(statusCode, deviceStatus); + const { body, statusCode } = await this.pushChangeRequest(bodyChange) + const deviceStatus: any = await body.json() + await this.pushStatusCodes(statusCode, deviceStatus) if (await this.successfulStatusCodes(statusCode, deviceStatus)) { - await this.successfulPushChange(statusCode, deviceStatus, bodyChange); - await this.updateHomeKitCharacteristics(); + await this.successfulPushChange(statusCode, deviceStatus, bodyChange) + await this.updateHomeKitCharacteristics() } else { - await this.statusCode(statusCode); - await this.statusCode(deviceStatus.statusCode); + await this.statusCode(statusCode) + await this.statusCode(deviceStatus.statusCode) } } catch (e: any) { - await this.apiError(e); - await this.pushChangeError(e); + await this.apiError(e) + await this.pushChangeError(e) } } else { - await this.warnLog(`Connection Type: ${this.device.connectionType}, commands will not be sent to OpenAPI`); + await this.warnLog(`Connection Type: ${this.device.connectionType}, commands will not be sent to OpenAPI`) } } async updateHomeKitCharacteristics(): Promise { - await this.debugLog('updateHomeKitCharacteristics'); + await this.debugLog('updateHomeKitCharacteristics') // Active - await this.updateCharacteristic(this.Fan.Service, this.hap.Characteristic.Active, - this.Fan.Active, 'Active'); + await this.updateCharacteristic(this.Fan.Service, this.hap.Characteristic.Active, this.Fan.Active, 'Active') // SwingMode - await this.updateCharacteristic(this.Fan.Service, this.hap.Characteristic.SwingMode, - this.Fan.SwingMode, 'SwingMode'); + await this.updateCharacteristic(this.Fan.Service, this.hap.Characteristic.SwingMode, this.Fan.SwingMode, 'SwingMode') // RotationSpeed - await this.updateCharacteristic(this.Fan.Service, this.hap.Characteristic.RotationSpeed, - this.Fan.RotationSpeed, 'RotationSpeed'); + await this.updateCharacteristic(this.Fan.Service, this.hap.Characteristic.RotationSpeed, this.Fan.RotationSpeed, 'RotationSpeed') } async apiError(e: any): Promise { - this.Fan.Service.updateCharacteristic(this.hap.Characteristic.Active, e); - this.Fan.Service.updateCharacteristic(this.hap.Characteristic.RotationSpeed, e); - this.Fan.Service.updateCharacteristic(this.hap.Characteristic.SwingMode, e); + this.Fan.Service.updateCharacteristic(this.hap.Characteristic.Active, e) + this.Fan.Service.updateCharacteristic(this.hap.Characteristic.RotationSpeed, e) + this.Fan.Service.updateCharacteristic(this.hap.Characteristic.SwingMode, e) } } diff --git a/src/irdevice/irdevice.ts b/src/irdevice/irdevice.ts index e1aef60f..61273af0 100644 --- a/src/irdevice/irdevice.ts +++ b/src/irdevice/irdevice.ts @@ -2,41 +2,43 @@ * * device.ts: @switchbot/homebridge-switchbot. */ -import { request } from 'undici'; -import { Devices } from '../settings.js'; +import type { API, CharacteristicValue, HAP, Logging, PlatformAccessory, Service } from 'homebridge' -import type { SwitchBotPlatform } from '../platform.js'; -import type { API, HAP, Logging, PlatformAccessory, Service, CharacteristicValue } from 'homebridge'; -import type { SwitchBotPlatformConfig, irDevicesConfig } from '../settings.js'; -import type { irdevice } from '../types/irdevicelist.js'; +import type { SwitchBotPlatform } from '../platform.js' +import type { irDevicesConfig, SwitchBotPlatformConfig } from '../settings.js' +import type { irdevice } from '../types/irdevicelist.js' + +import { request } from 'undici' + +import { Devices } from '../settings.js' export abstract class irdeviceBase { - public readonly api: API; - public readonly log: Logging; - public readonly config!: SwitchBotPlatformConfig; - protected readonly hap: HAP; + public readonly api: API + public readonly log: Logging + public readonly config!: SwitchBotPlatformConfig + protected readonly hap: HAP // Config - protected deviceLogging!: string; - protected disablePushOn!: boolean; - protected disablePushOff!: boolean; - protected disablePushDetail?: boolean; + protected deviceLogging!: string + protected disablePushOn!: boolean + protected disablePushOff!: boolean + protected disablePushDetail?: boolean constructor( protected readonly platform: SwitchBotPlatform, protected accessory: PlatformAccessory, protected device: irdevice & irDevicesConfig, ) { - this.api = this.platform.api; - this.log = this.platform.log; - this.config = this.platform.config; - this.hap = this.api.hap; + this.api = this.platform.api + this.log = this.platform.log + this.config = this.platform.config + this.hap = this.api.hap - this.getDeviceLogSettings(device); - this.getDeviceConfigSettings(device); - this.getDeviceContext(accessory, device); - this.disablePushOnChanges(device); - this.disablePushOffChanges(device); + this.getDeviceLogSettings(device) + this.getDeviceConfigSettings(device) + this.getDeviceContext(accessory, device) + this.disablePushOnChanges(device) + this.disablePushOffChanges(device) // Set accessory information accessory @@ -47,117 +49,74 @@ export abstract class irdeviceBase { .setCharacteristic(this.hap.Characteristic.ConfiguredName, accessory.displayName) .setCharacteristic(this.hap.Characteristic.Model, accessory.context.model ?? 'Unknown') .setCharacteristic(this.hap.Characteristic.ProductData, device.deviceId) - .setCharacteristic(this.hap.Characteristic.SerialNumber, device.deviceId); + .setCharacteristic(this.hap.Characteristic.SerialNumber, device.deviceId) } async getDeviceLogSettings(device: irdevice & irDevicesConfig): Promise { if (this.platform.debugMode) { - this.deviceLogging = this.accessory.context.logging = 'debugMode'; - await this.debugWarnLog(`Using Debug Mode Logging: ${this.deviceLogging}`); + this.deviceLogging = this.accessory.context.logging = 'debugMode' + await this.debugWarnLog(`Using Debug Mode Logging: ${this.deviceLogging}`) } else if (device.logging) { - this.deviceLogging = this.accessory.context.logging = device.logging; - await this.debugWarnLog(`Using Device Config Logging: ${this.deviceLogging}`); + this.deviceLogging = this.accessory.context.logging = device.logging + await this.debugWarnLog(`Using Device Config Logging: ${this.deviceLogging}`) } else if (this.config.logging) { - this.deviceLogging = this.accessory.context.logging = this.config.logging; - await this.debugWarnLog(`Using Platform Config Logging: ${this.deviceLogging}`); + this.deviceLogging = this.accessory.context.logging = this.config.logging + await this.debugWarnLog(`Using Platform Config Logging: ${this.deviceLogging}`) } else { - this.deviceLogging = this.accessory.context.logging = 'standard'; - await this.debugWarnLog(`Logging Not Set, Using: ${this.deviceLogging}`); + this.deviceLogging = this.accessory.context.logging = 'standard' + await this.debugWarnLog(`Logging Not Set, Using: ${this.deviceLogging}`) } } async getDeviceConfigSettings(device: irdevice & irDevicesConfig): Promise { - const deviceConfig = {}; - if (device.logging !== 'standard') { - deviceConfig['logging'] = device.logging; - } - if (device.connectionType !== '') { - deviceConfig['connectionType'] = device.connectionType; - } - if (device.external === true) { - deviceConfig['external'] = device.external; - } - if (device.customize === true) { - deviceConfig['customize'] = device.customize; - } - if (device.commandType !== '') { - deviceConfig['commandType'] = device.commandType; - } - if (device.customOn !== '') { - deviceConfig['customOn'] = device.customOn; - } - if (device.customOff !== '') { - deviceConfig['customOff'] = device.customOff; - } - if (device.disablePushOn === true) { - deviceConfig['disablePushOn'] = device.disablePushOn; - } - if (device.disablePushOff === true) { - deviceConfig['disablePushOff'] = device.disablePushOff; - } - if (device.disablePushDetail === true) { - deviceConfig['disablePushDetail'] = device.disablePushDetail; - } - let irairConfig = {}; - if (device.irair) { - irairConfig = device.irair; - } - let irpurConfig = {}; - if (device.irpur) { - irpurConfig = device.irpur; - } - let ircamConfig = {}; - if (device.ircam) { - ircamConfig = device.ircam; - } - let irfanConfig = {}; - if (device.irfan) { - irfanConfig = device.irfan; - } - let irlightConfig = {}; - if (device.irlight) { - irlightConfig = device.irlight; - } - let otherConfig = {}; - if (device.other) { - otherConfig = device.other; - } - let irtvConfig = {}; - if (device.irtv) { - irtvConfig = device.irtv; - } - let irvcConfig = {}; - if (device.irvc) { - irvcConfig = device.irvc; - } - let irwhConfig = {}; - if (device.irwh) { - irwhConfig = device.irwh; - } - const config = Object.assign({}, deviceConfig, irairConfig, irpurConfig, ircamConfig, irfanConfig, irlightConfig, otherConfig, - irtvConfig, irvcConfig, irwhConfig); - if (Object.entries(config).length !== 0) { - this.debugSuccessLog(`Config: ${JSON.stringify(config)}`); + const deviceConfig = Object.assign( + {}, + device.logging !== 'standard' && { logging: device.logging }, + device.connectionType !== '' && { connectionType: device.connectionType }, + device.external === true && { external: device.external }, + device.customize === true && { customize: device.customize }, + device.commandType !== '' && { commandType: device.commandType }, + device.customOn !== '' && { customOn: device.customOn }, + device.customOff !== '' && { customOff: device.customOff }, + device.disablePushOn === true && { disablePushOn: device.disablePushOn }, + device.disablePushOff === true && { disablePushOff: device.disablePushOff }, + device.disablePushDetail === true && { disablePushDetail: device.disablePushDetail }, + ) + const config = Object.assign( + {}, + deviceConfig, + device.irair, + device.irpur, + device.ircam, + device.irfan, + device.irlight, + device.other, + device.irtv, + device.irvc, + device.irwh, + ) + if (Object.keys(config).length !== 0) { + this.debugSuccessLog(`Config: ${JSON.stringify(config)}`) } } async getDeviceContext(accessory: PlatformAccessory, device: irdevice & irDevicesConfig): Promise { - accessory.context.name = device.deviceName; - accessory.context.model = device.remoteType; - accessory.context.deviceId = device.deviceId; - accessory.context.remoteType = device.remoteType; - - const deviceFirmwareVersion = device.firmware ?? accessory.context.version ?? this.platform.version ?? '0.0.0'; - const version = deviceFirmwareVersion.toString(); - await this.debugLog(`version: ${version?.replace(/^V|-.*$/g, '')}`); - let deviceVersion: string; + accessory.context.name = device.deviceName + accessory.context.model = device.remoteType + accessory.context.deviceId = device.deviceId + accessory.context.remoteType = device.remoteType + + const deviceFirmwareVersion = device.firmware ?? accessory.context.version ?? this.platform.version ?? '0.0.0' + const version = deviceFirmwareVersion.toString() + await this.debugLog(`version: ${version?.replace(/^V|-.*$/g, '')}`) + let deviceVersion: string if (version?.includes('.') === false) { - const replace = version?.replace(/^V|-.*$/g, ''); - const match = replace?.match(/.{1,1}/g); - const validVersion = match?.join('.'); - deviceVersion = validVersion ?? '0.0.0'; + const replace = version?.replace(/^V|-.*$/g, '') + const match = replace?.match(/./g) + const validVersion = match?.join('.') + deviceVersion = validVersion ?? '0.0.0' } else { - deviceVersion = version.replace(/^V|-.*$/g, '') ?? '0.0.0'; + deviceVersion = version.replace(/^V|-.*$/g, '') ?? '0.0.0' } accessory .getService(this.hap.Service.AccessoryInformation)! @@ -165,185 +124,143 @@ export abstract class irdeviceBase { .setCharacteristic(this.hap.Characteristic.SoftwareRevision, deviceVersion) .setCharacteristic(this.hap.Characteristic.FirmwareRevision, deviceVersion) .getCharacteristic(this.hap.Characteristic.FirmwareRevision) - .updateValue(deviceVersion); - accessory.context.version = deviceVersion; - this.debugSuccessLog(`version: ${accessory.context.version}`); + .updateValue(deviceVersion) + accessory.context.version = deviceVersion + this.debugSuccessLog(`version: ${accessory.context.version}`) } - async pushChangeRequest(bodyChange: string): Promise<{ body: any; statusCode: any; }> { + async pushChangeRequest(bodyChange: string): Promise<{ body: any, statusCode: any }> { return await request(`${Devices}/${this.device.deviceId}/commands`, { body: bodyChange, method: 'POST', headers: this.platform.generateHeaders(), - }); + }) } async successfulStatusCodes(statusCode: any, deviceStatus: any) { - return (statusCode === 200 || statusCode === 100) && (deviceStatus.statusCode === 200 || deviceStatus.statusCode === 100); + return (statusCode === 200 || statusCode === 100) && (deviceStatus.statusCode === 200 || deviceStatus.statusCode === 100) } /** - * Update the characteristic value and log the change. - * - * @param Service: Service - * @param Characteristic: Characteristic - * @param CharacteristicValue: CharacteristicValue | undefined - * @param CharacteristicName: string - * @return: void - * - */ - async updateCharacteristic(Service: Service, Characteristic: any, - CharacteristicValue: CharacteristicValue | undefined, CharacteristicName: string): Promise { + * Update the characteristic value and log the change. + * + * @param Service Service + * @param Characteristic Characteristic + * @param CharacteristicValue CharacteristicValue | undefined + * @param CharacteristicName string + * @return: void + * + */ + async updateCharacteristic(Service: Service, Characteristic: any, CharacteristicValue: CharacteristicValue | undefined, CharacteristicName: string): Promise { if (CharacteristicValue === undefined) { - await this.debugLog(`${CharacteristicName}: ${CharacteristicValue}`); + await this.debugLog(`${CharacteristicName}: ${CharacteristicValue}`) } else { - Service.updateCharacteristic(Characteristic, CharacteristicValue); - await this.debugLog(`updateCharacteristic ${CharacteristicName}: ${CharacteristicValue}`); - await this.debugWarnLog(`${CharacteristicName} context before: ${this.accessory.context[CharacteristicName]}`); - this.accessory.context[CharacteristicName] = CharacteristicValue; - await this.debugWarnLog(`${CharacteristicName} context after: ${this.accessory.context[CharacteristicName]}`); + Service.updateCharacteristic(Characteristic, CharacteristicValue) + await this.debugLog(`updateCharacteristic ${CharacteristicName}: ${CharacteristicValue}`) + await this.debugWarnLog(`${CharacteristicName} context before: ${this.accessory.context[CharacteristicName]}`) + this.accessory.context[CharacteristicName] = CharacteristicValue + await this.debugWarnLog(`${CharacteristicName} context after: ${this.accessory.context[CharacteristicName]}`) } } async pushStatusCodes(statusCode: any, deviceStatus: any) { - await this.debugWarnLog(`statusCode: ${statusCode}`); - await this.debugWarnLog(`deviceStatus: ${JSON.stringify(deviceStatus)}`); - await this.debugWarnLog(`deviceStatus statusCode: ${deviceStatus.statusCode}`); + await this.debugWarnLog(`statusCode: ${statusCode}`) + await this.debugWarnLog(`deviceStatus: ${JSON.stringify(deviceStatus)}`) + await this.debugWarnLog(`deviceStatus statusCode: ${deviceStatus.statusCode}`) } async successfulPushChange(statusCode: any, deviceStatus: any, bodyChange: any) { - this.debugSuccessLog(`statusCode: ${statusCode} & deviceStatus ` - + `StatusCode: ${deviceStatus.statusCode}`); - this.successLog('request to SwitchBot API,' - + ` body: ${JSON.stringify(JSON.parse(bodyChange))} sent successfully`); + this.debugSuccessLog(`statusCode: ${statusCode} & deviceStatus StatusCode: ${deviceStatus.statusCode}`) + this.successLog(`request to SwitchBot API, body: ${JSON.stringify(JSON.parse(bodyChange))} sent successfully`) } async pushChangeError(e: Error) { - this.errorLog(`failed pushChanges with ${this.device.connectionType}` - + ` Connection, Error Message: ${JSON.stringify(e.message)}`); + this.errorLog(`failed pushChanges with ${this.device.connectionType} Connection, Error Message: ${JSON.stringify(e.message)}`) } async disablePushOnChanges(device: irdevice & irDevicesConfig): Promise { if (device.disablePushOn === undefined) { - this.disablePushOn = false; + this.disablePushOn = false } else { - this.disablePushOn = device.disablePushOn; + this.disablePushOn = device.disablePushOn } } async disablePushOffChanges(device: irdevice & irDevicesConfig): Promise { if (device.disablePushOff === undefined) { - this.disablePushOff = false; + this.disablePushOff = false } else { - this.disablePushOff = device.disablePushOff; + this.disablePushOff = device.disablePushOff } } async disablePushDetailChanges(device: irdevice & irDevicesConfig): Promise { if (device.disablePushDetail === undefined) { - this.disablePushDetail = false; + this.disablePushDetail = false } else { - this.disablePushDetail = device.disablePushDetail; + this.disablePushDetail = device.disablePushDetail } } async commandType(): Promise { - let commandType: string; + let commandType: string if (this.device.commandType && this.device.customize) { - commandType = this.device.commandType; + commandType = this.device.commandType } else if (this.device.customize) { - commandType = 'customize'; + commandType = 'customize' } else { - commandType = 'command'; + commandType = 'command' } - return commandType; + return commandType } async commandOn(): Promise { - let command: string; + let command: string if (this.device.customize && this.device.customOn) { - command = this.device.customOn; + command = this.device.customOn } else { - command = 'turnOn'; + command = 'turnOn' } - return command; + return command } async commandOff(): Promise { - let command: string; + let command: string if (this.device.customize && this.device.customOff) { - command = this.device.customOff; + command = this.device.customOff } else { - command = 'turnOff'; + command = 'turnOff' } - return command; + return command } async statusCode(statusCode: number): Promise { - switch (statusCode) { - case 151: - this.errorLog(`Command not supported by this deviceType, statusCode: ${statusCode}`); - break; - case 152: - this.errorLog(`Device not found, statusCode: ${statusCode}`); - break; - case 160: - this.errorLog(`Command is not supported, statusCode: ${statusCode}`); - break; - case 161: - this.errorLog(`Device is offline, statusCode: ${statusCode}`); - break; - case 171: - this.errorLog(`Hub Device is offline, statusCode: ${statusCode}. Hub: ${this.device.hubDeviceId}`); - break; - case 190: - this.errorLog('Device internal error due to device states not synchronized with server, Or command format is invalid,' - + ` statusCode: ${statusCode}`); - break; - case 100: - await this.debugLog(`Command successfully sent, statusCode: ${statusCode}`); - break; - case 200: - await this.debugLog(`Request successful, statusCode: ${statusCode}`); - break; - case 400: - this.errorLog('Bad Request, The client has issued an invalid request. This is commonly used to specify validation errors in a request' - + ` payload, statusCode: ${statusCode}`); - break; - case 401: - this.errorLog(`Unauthorized, Authorization for the API is required, but the request has not been authenticated, statusCode: ${statusCode}`); - break; - case 403: - this.errorLog('Forbidden, The request has been authenticated but does not have appropriate permissions, or a requested resource is not' - + ` found, statusCode: ${statusCode}`); - break; - case 404: - this.errorLog(`Not Found, Specifies the requested path does not exist, statusCode: ${statusCode}`); - break; - case 406: - this.errorLog('Not Acceptable, The client has requested a MIME type via the Accept header for a value not supported by the server,' - + ` statusCode: ${statusCode}`); - break; - case 415: - this.errorLog('Unsupported Media Type, The client has defined a contentType header that is not supported by the server,' - + ` statusCode: ${statusCode}`); - break; - case 422: - this.errorLog('Unprocessable Entity, 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, statusCode: ${statusCode}`); - break; - case 429: - this.errorLog('Too Many Requests, The client has exceeded the number of requests allowed for a given time window,' - + ` statusCode: ${statusCode}`); - break; - case 500: - this.errorLog('Internal Server Error, An unexpected error on the SmartThings servers has occurred. These errors should be rare,' - + ` statusCode: ${statusCode}`); - break; - default: - this.infoLog('Unknown statusCode: ' - + `${statusCode}, Submit Bugs Here: ' + 'https://tinyurl.com/SwitchBotBug`); - } + const statusMessages = { + 151: 'Command not supported by this deviceType', + 152: 'Device not found', + 160: 'Command is not supported', + 161: 'Device is offline', + 171: `Hub Device is offline. Hub: ${this.device.hubDeviceId}`, + 190: 'Device internal error due to device states not synchronized with server, or command format is invalid', + 100: 'Command successfully sent', + 200: 'Request successful', + 400: 'Bad Request, an invalid payload request', + 401: 'Unauthorized, Authorization for the API is required, but the request has not been authenticated', + 403: 'Forbidden, The request has been authenticated but does not have appropriate permissions, or a requested resource is not found', + 404: 'Not Found, Specifies the requested path does not exist', + 406: 'Not Acceptable, a MIME type has been requested via the Accept header for a value not supported by the server', + 415: 'Unsupported Media Type, a contentType header has been defined that is not supported by the server', + 422: 'Unprocessable Entity: The server cannot process the request, often due to exceeded API limits.', + 429: 'Too Many Requests, exceeded the number of requests allowed for a given time window', + 500: 'Internal Server Error, An unexpected error occurred. These errors should be rare', + } + if (statusCode === 171 && (this.device.hubDeviceId === this.device.deviceId || this.device.hubDeviceId === '000000000000')) { + this.debugErrorLog(`statusCode 171 changed to 161: hubDeviceId ${this.device.hubDeviceId} matches deviceId ${this.device.deviceId}, device is its own hub.`) + statusCode = 161 + } + const logMessage = statusMessages[statusCode] || `Unknown statusCode: ${statusCode}, Submit Bugs Here: https://tinyurl.com/SwitchBotBug` + const logMethod = [100, 200].includes(statusCode) ? 'debugLog' : statusMessages[statusCode] ? 'errorLog' : 'infoLog' + this[logMethod](`${logMessage}, statusCode: ${statusCode}`) } /** @@ -351,48 +268,48 @@ export abstract class irdeviceBase { */ async infoLog(...log: any[]): Promise { if (await this.enablingDeviceLogging()) { - this.log.info(`${this.device.remoteType}: ${this.accessory.displayName}`, String(...log)); + this.log.info(`${this.device.remoteType}: ${this.accessory.displayName}`, String(...log)) } } async successLog(...log: any[]): Promise { if (await this.enablingDeviceLogging()) { - this.log.success(`${this.device.remoteType}: ${this.accessory.displayName}`, String(...log)); + this.log.success(`${this.device.remoteType}: ${this.accessory.displayName}`, String(...log)) } } async debugSuccessLog(...log: any[]): Promise { if (await this.enablingDeviceLogging()) { if (await this.loggingIsDebug()) { - this.log.success(`[DEBUG] ${this.device.remoteType}: ${this.accessory.displayName}`, String(...log)); + this.log.success(`[DEBUG] ${this.device.remoteType}: ${this.accessory.displayName}`, String(...log)) } } } async warnLog(...log: any[]): Promise { if (await this.enablingDeviceLogging()) { - this.log.warn(`${this.device.remoteType}: ${this.accessory.displayName}`, String(...log)); + this.log.warn(`${this.device.remoteType}: ${this.accessory.displayName}`, String(...log)) } } async debugWarnLog(...log: any[]): Promise { if (await this.enablingDeviceLogging()) { if (await this.loggingIsDebug()) { - this.log.warn(`[DEBUG] ${this.device.remoteType}: ${this.accessory.displayName}`, String(...log)); + this.log.warn(`[DEBUG] ${this.device.remoteType}: ${this.accessory.displayName}`, String(...log)) } } } async errorLog(...log: any[]): Promise { if (await this.enablingDeviceLogging()) { - this.log.error(`${this.device.remoteType}: ${this.accessory.displayName}`, String(...log)); + this.log.error(`${this.device.remoteType}: ${this.accessory.displayName}`, String(...log)) } } async debugErrorLog(...log: any[]): Promise { if (await this.enablingDeviceLogging()) { if (await this.loggingIsDebug()) { - this.log.error(`[DEBUG] ${this.device.remoteType}: ${this.accessory.displayName}`, String(...log)); + this.log.error(`[DEBUG] ${this.device.remoteType}: ${this.accessory.displayName}`, String(...log)) } } } @@ -400,18 +317,18 @@ export abstract class irdeviceBase { async debugLog(...log: any[]): Promise { if (await this.enablingDeviceLogging()) { if (this.deviceLogging === 'debug') { - this.log.info(`[DEBUG] ${this.device.remoteType}: ${this.accessory.displayName}`, String(...log)); + this.log.info(`[DEBUG] ${this.device.remoteType}: ${this.accessory.displayName}`, String(...log)) } else if (this.deviceLogging === 'debugMode') { - this.log.debug(`${this.device.remoteType}: ${this.accessory.displayName}`, String(...log)); + this.log.debug(`${this.device.remoteType}: ${this.accessory.displayName}`, String(...log)) } } } async loggingIsDebug(): Promise { - return this.deviceLogging === 'debugMode' || this.deviceLogging === 'debug'; + return this.deviceLogging === 'debugMode' || this.deviceLogging === 'debug' } async enablingDeviceLogging(): Promise { - return this.deviceLogging === 'debugMode' || this.deviceLogging === 'debug' || this.deviceLogging === 'standard'; + return this.deviceLogging === 'debugMode' || this.deviceLogging === 'debug' || this.deviceLogging === 'standard' } -} \ No newline at end of file +} diff --git a/src/irdevice/light.ts b/src/irdevice/light.ts index bee5b929..bf7ae89b 100644 --- a/src/irdevice/light.ts +++ b/src/irdevice/light.ts @@ -2,12 +2,13 @@ * * light.ts: @switchbot/homebridge-switchbot. */ -import { irdeviceBase } from './irdevice.js'; +import type { CharacteristicValue, PlatformAccessory, Service } from 'homebridge' -import type { SwitchBotPlatform } from '../platform.js'; -import type { irDevicesConfig } from '../settings.js'; -import type { irdevice } from '../types/irdevicelist.js'; -import type { CharacteristicValue, PlatformAccessory, Service } from 'homebridge'; +import type { SwitchBotPlatform } from '../platform.js' +import type { irDevicesConfig } from '../settings.js' +import type { irdevice } from '../types/irdevicelist.js' + +import { irdeviceBase } from './irdevice.js' /** * Platform Accessory @@ -17,127 +18,108 @@ import type { CharacteristicValue, PlatformAccessory, Service } from 'homebridge export class Light extends irdeviceBase { // Services private LightBulb?: { - Name: CharacteristicValue; - Service: Service; - On: CharacteristicValue; - }; + Name: CharacteristicValue + Service: Service + On: CharacteristicValue + } private ProgrammableSwitchOn?: { - Name: CharacteristicValue; - Service: Service; - ProgrammableSwitchEvent: CharacteristicValue; - ProgrammableSwitchOutputState: CharacteristicValue; - }; + Name: CharacteristicValue + Service: Service + ProgrammableSwitchEvent: CharacteristicValue + ProgrammableSwitchOutputState: CharacteristicValue + } private ProgrammableSwitchOff?: { - Name: CharacteristicValue; - Service: Service; - ProgrammableSwitchEvent: CharacteristicValue; - ProgrammableSwitchOutputState: CharacteristicValue; - }; + Name: CharacteristicValue + Service: Service + ProgrammableSwitchEvent: CharacteristicValue + ProgrammableSwitchOutputState: CharacteristicValue + } constructor( readonly platform: SwitchBotPlatform, accessory: PlatformAccessory, device: irdevice & irDevicesConfig, ) { - super(platform, accessory, device); + super(platform, accessory, device) // Set category - accessory.category = this.hap.Categories.LIGHTBULB; + accessory.category = this.hap.Categories.LIGHTBULB if (!device.irlight?.stateless) { // Initialize LightBulb Service - accessory.context.LightBulb = accessory.context.LightBulb ?? {}; + accessory.context.LightBulb = accessory.context.LightBulb ?? {} this.LightBulb = { Name: accessory.displayName, Service: accessory.getService(this.hap.Service.Lightbulb) ?? accessory.addService(this.hap.Service.Lightbulb) as Service, On: accessory.context.On || false, - }; - accessory.context.LightBulb = this.LightBulb as object; + } + accessory.context.LightBulb = this.LightBulb as object - this.LightBulb.Service - .setCharacteristic(this.hap.Characteristic.Name, this.LightBulb.Name) - .getCharacteristic(this.hap.Characteristic.On) - .onGet(() => { - return this.LightBulb!.On; - }) - .onSet(this.OnSet.bind(this)); + this.LightBulb.Service.setCharacteristic(this.hap.Characteristic.Name, this.LightBulb.Name).getCharacteristic(this.hap.Characteristic.On).onGet(() => { + return this.LightBulb!.On + }).onSet(this.OnSet.bind(this)) } else { // Initialize ProgrammableSwitchOn Service - accessory.context.ProgrammableSwitchOn = accessory.context.ProgrammableSwitchOn ?? {}; + accessory.context.ProgrammableSwitchOn = accessory.context.ProgrammableSwitchOn ?? {} this.ProgrammableSwitchOn = { Name: `${accessory.displayName} On`, - Service: accessory.getService(this.hap.Service.StatefulProgrammableSwitch) - ?? accessory.addService(this.hap.Service.StatefulProgrammableSwitch) as Service, + Service: accessory.getService(this.hap.Service.StatefulProgrammableSwitch) ?? accessory.addService(this.hap.Service.StatefulProgrammableSwitch) as Service, ProgrammableSwitchEvent: accessory.context.ProgrammableSwitchEvent ?? this.hap.Characteristic.ProgrammableSwitchEvent.SINGLE_PRESS, ProgrammableSwitchOutputState: accessory.context.ProgrammableSwitchOutputState ?? 0, - }; - accessory.context.ProgrammableSwitchOn = this.ProgrammableSwitchOn as object; + } + accessory.context.ProgrammableSwitchOn = this.ProgrammableSwitchOn as object // Initialize ProgrammableSwitchOn Characteristics - this.ProgrammableSwitchOn?.Service - .setCharacteristic(this.hap.Characteristic.Name, this.ProgrammableSwitchOn.Name) - .getCharacteristic(this.hap.Characteristic.ProgrammableSwitchEvent).setProps({ - validValueRanges: [0, 0], - minValue: 0, - maxValue: 0, - validValues: [0], - }) - .onGet(() => { - return this.ProgrammableSwitchOn!.ProgrammableSwitchEvent; - }); - - this.ProgrammableSwitchOn?.Service - .getCharacteristic(this.hap.Characteristic.ProgrammableSwitchOutputState) - .onGet(() => { - return this.ProgrammableSwitchOn!.ProgrammableSwitchOutputState; - }) - .onSet(this.ProgrammableSwitchOutputStateSetOn.bind(this)); + this.ProgrammableSwitchOn?.Service.setCharacteristic(this.hap.Characteristic.Name, this.ProgrammableSwitchOn.Name).getCharacteristic(this.hap.Characteristic.ProgrammableSwitchEvent).setProps({ + validValueRanges: [0, 0], + minValue: 0, + maxValue: 0, + validValues: [0], + }).onGet(() => { + return this.ProgrammableSwitchOn!.ProgrammableSwitchEvent + }) + + this.ProgrammableSwitchOn?.Service.getCharacteristic(this.hap.Characteristic.ProgrammableSwitchOutputState).onGet(() => { + return this.ProgrammableSwitchOn!.ProgrammableSwitchOutputState + }).onSet(this.ProgrammableSwitchOutputStateSetOn.bind(this)) // Initialize ProgrammableSwitchOff Service - accessory.context.ProgrammableSwitchOff = accessory.context.ProgrammableSwitchOff ?? {}; + accessory.context.ProgrammableSwitchOff = accessory.context.ProgrammableSwitchOff ?? {} this.ProgrammableSwitchOff = { Name: `${accessory.displayName} Off`, - Service: accessory.getService(this.hap.Service.StatefulProgrammableSwitch) - ?? accessory.addService(this.hap.Service.StatefulProgrammableSwitch) as Service, + Service: accessory.getService(this.hap.Service.StatefulProgrammableSwitch) ?? accessory.addService(this.hap.Service.StatefulProgrammableSwitch) as Service, ProgrammableSwitchEvent: accessory.context.ProgrammableSwitchEvent ?? this.hap.Characteristic.ProgrammableSwitchEvent.SINGLE_PRESS, ProgrammableSwitchOutputState: accessory.context.ProgrammableSwitchOutputState ?? 0, - }; - accessory.context.ProgrammableSwitchOff = this.ProgrammableSwitchOff as object; + } + accessory.context.ProgrammableSwitchOff = this.ProgrammableSwitchOff as object // Initialize ProgrammableSwitchOff Characteristics - this.ProgrammableSwitchOff?.Service - .setCharacteristic(this.hap.Characteristic.Name, this.ProgrammableSwitchOff.Name) - .getCharacteristic(this.hap.Characteristic.ProgrammableSwitchEvent).setProps({ - validValueRanges: [0, 0], - minValue: 0, - maxValue: 0, - validValues: [0], - }) - .onGet(() => { - return this.ProgrammableSwitchOff!.ProgrammableSwitchEvent; - }); - - this.ProgrammableSwitchOff?.Service - .getCharacteristic(this.hap.Characteristic.ProgrammableSwitchOutputState) - .onGet(() => { - return this.ProgrammableSwitchOff!.ProgrammableSwitchOutputState; - }) - .onSet(this.ProgrammableSwitchOutputStateSetOff.bind(this)); + this.ProgrammableSwitchOff?.Service.setCharacteristic(this.hap.Characteristic.Name, this.ProgrammableSwitchOff.Name).getCharacteristic(this.hap.Characteristic.ProgrammableSwitchEvent).setProps({ + validValueRanges: [0, 0], + minValue: 0, + maxValue: 0, + validValues: [0], + }).onGet(() => { + return this.ProgrammableSwitchOff!.ProgrammableSwitchEvent + }) + + this.ProgrammableSwitchOff?.Service.getCharacteristic(this.hap.Characteristic.ProgrammableSwitchOutputState).onGet(() => { + return this.ProgrammableSwitchOff!.ProgrammableSwitchOutputState + }).onSet(this.ProgrammableSwitchOutputStateSetOff.bind(this)) } - } async OnSet(value: CharacteristicValue): Promise { - await this.debugLog(`On: ${value}`); + await this.debugLog(`On: ${value}`) - this.LightBulb!.On = value; + this.LightBulb!.On = value if (this.LightBulb?.On) { - const On = true; - await this.pushLightOnChanges(On); + const On = true + await this.pushLightOnChanges(On) } else { - const On = false; - await this.pushLightOffChanges(On); + const On = false + await this.pushLightOffChanges(On) } /** * pushLightOnChanges and pushLightOffChanges above assume they are measuring the state of the accessory BEFORE @@ -146,12 +128,12 @@ export class Light extends irdeviceBase { } async ProgrammableSwitchOutputStateSetOn(value: CharacteristicValue): Promise { - await this.debugLog(`On: ${value}`); + await this.debugLog(`On: ${value}`) - this.ProgrammableSwitchOn!.ProgrammableSwitchOutputState = value; + this.ProgrammableSwitchOn!.ProgrammableSwitchOutputState = value if (this.ProgrammableSwitchOn?.ProgrammableSwitchOutputState === 1) { - const On = true; - await this.pushLightOnChanges(On); + const On = true + await this.pushLightOnChanges(On) } /** * pushLightOnChanges and pushLightOffChanges above assume they are measuring the state of the accessory BEFORE @@ -160,12 +142,12 @@ export class Light extends irdeviceBase { } async ProgrammableSwitchOutputStateSetOff(value: CharacteristicValue): Promise { - await this.debugLog(`On: ${value}`); + await this.debugLog(`On: ${value}`) - this.ProgrammableSwitchOff!.ProgrammableSwitchOutputState = value; + this.ProgrammableSwitchOff!.ProgrammableSwitchOutputState = value if (this.ProgrammableSwitchOff?.ProgrammableSwitchOutputState === 1) { - const On = false; - await this.pushLightOffChanges(On); + const On = false + await this.pushLightOffChanges(On) } /** * pushLightOnChanges and pushLightOffChanges above assume they are measuring the state of the accessory BEFORE @@ -173,100 +155,94 @@ export class Light extends irdeviceBase { */ } - - /** * Pushes the requested changes to the SwitchBot API - * deviceType commandType Command command parameter Description - * Light - "command" "turnOff" "default" = set to OFF state - * Light - "command" "turnOn" "default" = set to ON state - * Light - "command" "volumeAdd" "default" = volume up - * Light - "command" "volumeSub" "default" = volume down - * Light - "command" "channelAdd" "default" = next channel - * Light - "command" "channelSub" "default" = previous channel + * deviceType commandType Command command parameter Description + * Light - "command" "turnOff" "default" = set to OFF state + * Light - "command" "turnOn" "default" = set to ON state + * Light - "command" "volumeAdd" "default" = volume up + * Light - "command" "volumeSub" "default" = volume down + * Light - "command" "channelAdd" "default" = next channel + * Light - "command" "channelSub" "default" = previous channel */ async pushLightOnChanges(On: boolean): Promise { - await this.debugLog(`pushLightOnChanges On: ${On}, disablePushOn: ${this.disablePushOn}`); + await this.debugLog(`pushLightOnChanges On: ${On}, disablePushOn: ${this.disablePushOn}`) if (On === true && this.disablePushOn === false) { - const commandType: string = await this.commandType(); - const command: string = await this.commandOn(); + const commandType: string = await this.commandType() + const command: string = await this.commandOn() const bodyChange = JSON.stringify({ - command: command, + command, parameter: 'default', - commandType: commandType, - }); - await this.pushChanges(bodyChange, On); + commandType, + }) + await this.pushChanges(bodyChange, On) } } async pushLightOffChanges(On: boolean): Promise { - await this.debugLog(`pushLightOffChanges On: ${On}, disablePushOff: ${this.disablePushOff}`); + await this.debugLog(`pushLightOffChanges On: ${On}, disablePushOff: ${this.disablePushOff}`) if (On === false && this.disablePushOff === false) { - const commandType: string = await this.commandType(); - const command: string = await this.commandOff(); + const commandType: string = await this.commandType() + const command: string = await this.commandOff() const bodyChange = JSON.stringify({ - command: command, + command, parameter: 'default', - commandType: commandType, - }); - await this.pushChanges(bodyChange, On); + commandType, + }) + await this.pushChanges(bodyChange, On) } } async pushChanges(bodyChange: any, On: boolean): Promise { - this.debugLog('pushChanges'); + this.debugLog('pushChanges') if (this.device.connectionType === 'OpenAPI') { - this.infoLog(`Sending request to SwitchBot API, body: ${bodyChange},`); + this.infoLog(`Sending request to SwitchBot API, body: ${bodyChange},`) try { - const { body, statusCode } = await this.pushChangeRequest(bodyChange); - const deviceStatus: any = await body.json(); - await this.pushStatusCodes(statusCode, deviceStatus); + const { body, statusCode } = await this.pushChangeRequest(bodyChange) + const deviceStatus: any = await body.json() + await this.pushStatusCodes(statusCode, deviceStatus) if (await this.successfulStatusCodes(statusCode, deviceStatus)) { - await this.successfulPushChange(statusCode, deviceStatus, bodyChange); - this.accessory.context.On = On; - await this.updateHomeKitCharacteristics(); + await this.successfulPushChange(statusCode, deviceStatus, bodyChange) + this.accessory.context.On = On + await this.updateHomeKitCharacteristics() } else { - await this.statusCode(statusCode); - await this.statusCode(deviceStatus.statusCode); + await this.statusCode(statusCode) + await this.statusCode(deviceStatus.statusCode) } } catch (e: any) { - await this.apiError(e); - await this.pushChangeError(e); + await this.apiError(e) + await this.pushChangeError(e) } } else { - this.warnLog('Connection Type: ' - + `${this.device.connectionType}, commands will not be sent to OpenAPI`); + this.warnLog(`Connection Type: ${this.device.connectionType}, commands will not be sent to OpenAPI`) } } async updateHomeKitCharacteristics(): Promise { - await this.debugLog('updateHomeKitCharacteristics'); + await this.debugLog('updateHomeKitCharacteristics') if (!this.device.irlight?.stateless && this.LightBulb?.Service) { // On - await this.updateCharacteristic(this.LightBulb.Service, this.hap.Characteristic.On, - this.LightBulb.On, 'On'); + await this.updateCharacteristic(this.LightBulb.Service, this.hap.Characteristic.On, this.LightBulb.On, 'On') } else { if (this.ProgrammableSwitchOn?.Service) { - // On Stateful Programmable Switch - await this.updateCharacteristic(this.ProgrammableSwitchOn.Service, this.hap.Characteristic.ProgrammableSwitchOutputState, - this.ProgrammableSwitchOn.ProgrammableSwitchOutputState, 'ProgrammableSwitchOutputState'); + // On Stateful Programmable Switch + await this.updateCharacteristic(this.ProgrammableSwitchOn.Service, this.hap.Characteristic.ProgrammableSwitchOutputState, this.ProgrammableSwitchOn.ProgrammableSwitchOutputState, 'ProgrammableSwitchOutputState') } if (this.ProgrammableSwitchOff?.Service) { - // Off Stateful Programmable Switch - await this.updateCharacteristic(this.ProgrammableSwitchOff.Service, this.hap.Characteristic.ProgrammableSwitchOutputState, - this.ProgrammableSwitchOff.ProgrammableSwitchOutputState, 'ProgrammableSwitchOutputState'); + // Off Stateful Programmable Switch + await this.updateCharacteristic(this.ProgrammableSwitchOff.Service, this.hap.Characteristic.ProgrammableSwitchOutputState, this.ProgrammableSwitchOff.ProgrammableSwitchOutputState, 'ProgrammableSwitchOutputState') } } } async apiError(e: any): Promise { if (!this.device.irlight?.stateless) { - this.LightBulb?.Service.updateCharacteristic(this.hap.Characteristic.On, e); + this.LightBulb?.Service.updateCharacteristic(this.hap.Characteristic.On, e) } else { - this.ProgrammableSwitchOn?.Service.updateCharacteristic(this.hap.Characteristic.ProgrammableSwitchEvent, e); - this.ProgrammableSwitchOn?.Service.updateCharacteristic(this.hap.Characteristic.ProgrammableSwitchOutputState, e); - this.ProgrammableSwitchOff?.Service.updateCharacteristic(this.hap.Characteristic.ProgrammableSwitchEvent, e); - this.ProgrammableSwitchOff?.Service.updateCharacteristic(this.hap.Characteristic.ProgrammableSwitchOutputState, e); + this.ProgrammableSwitchOn?.Service.updateCharacteristic(this.hap.Characteristic.ProgrammableSwitchEvent, e) + this.ProgrammableSwitchOn?.Service.updateCharacteristic(this.hap.Characteristic.ProgrammableSwitchOutputState, e) + this.ProgrammableSwitchOff?.Service.updateCharacteristic(this.hap.Characteristic.ProgrammableSwitchEvent, e) + this.ProgrammableSwitchOff?.Service.updateCharacteristic(this.hap.Characteristic.ProgrammableSwitchOutputState, e) } } } diff --git a/src/irdevice/other.ts b/src/irdevice/other.ts index 7d50beed..aef8eacf 100644 --- a/src/irdevice/other.ts +++ b/src/irdevice/other.ts @@ -2,12 +2,13 @@ * * other.ts: @switchbot/homebridge-switchbot. */ -import { irdeviceBase } from './irdevice.js'; +import type { CharacteristicValue, PlatformAccessory, Service } from 'homebridge' -import type { SwitchBotPlatform } from '../platform.js'; -import type { irDevicesConfig } from '../settings.js'; -import type { irdevice } from '../types/irdevicelist.js'; -import type { CharacteristicValue, PlatformAccessory, Service } from 'homebridge'; +import type { SwitchBotPlatform } from '../platform.js' +import type { irDevicesConfig } from '../settings.js' +import type { irdevice } from '../types/irdevicelist.js' + +import { irdeviceBase } from './irdevice.js' /** * Platform Accessory @@ -17,361 +18,323 @@ import type { CharacteristicValue, PlatformAccessory, Service } from 'homebridge export class Others extends irdeviceBase { // Services private Switch?: { - Name: CharacteristicValue; - Service: Service; - }; + Name: CharacteristicValue + Service: Service + } private GarageDoor?: { - Name: CharacteristicValue; - Service: Service; - }; + Name: CharacteristicValue + Service: Service + } private Door?: { - Name: CharacteristicValue; - Service: Service; - }; + Name: CharacteristicValue + Service: Service + } private Window?: { - Name: CharacteristicValue; - Service: Service; - }; + Name: CharacteristicValue + Service: Service + } private WindowCovering?: { - Name: CharacteristicValue; - Service: Service; - }; + Name: CharacteristicValue + Service: Service + } private LockMechanism?: { - Name: CharacteristicValue; - Service: Service; - }; + Name: CharacteristicValue + Service: Service + } private Faucet?: { - Name: CharacteristicValue; - Service: Service; - }; + Name: CharacteristicValue + Service: Service + } private Fan?: { - Name: CharacteristicValue; - Service: Service; - }; + Name: CharacteristicValue + Service: Service + } private StatefulProgrammableSwitch?: { - Name: CharacteristicValue; - Service: Service; - }; + Name: CharacteristicValue + Service: Service + } private Outlet?: { - Name: CharacteristicValue; - Service: Service; - }; + Name: CharacteristicValue + Service: Service + } - On!: boolean; + On!: boolean // Config - otherDeviceType?: string; + otherDeviceType?: string constructor( readonly platform: SwitchBotPlatform, accessory: PlatformAccessory, device: irdevice & irDevicesConfig, ) { - super(platform, accessory, device); + super(platform, accessory, device) // default placeholders - this.getOtherConfigSettings(accessory, device); + this.getOtherConfigSettings(accessory, device) // deviceType if (this.otherDeviceType === 'switch') { // Set category - accessory.category = this.hap.Categories.SWITCH; + accessory.category = this.hap.Categories.SWITCH // Initialize Switch Service - accessory.context.Switch = accessory.context.Switch ?? {}; + accessory.context.Switch = accessory.context.Switch ?? {} this.Switch = { Name: accessory.displayName, Service: accessory.getService(this.hap.Service.Switch) ?? accessory.addService(this.hap.Service.Switch) as Service, - }; - accessory.context.Switch = this.Switch as object; - this.debugLog('Displaying as Switch'); + } + accessory.context.Switch = this.Switch as object + this.debugLog('Displaying as Switch') // Initialize Switch Characteristics - this.Switch.Service - .setCharacteristic(this.hap.Characteristic.Name, this.Switch.Name) - .getCharacteristic(this.hap.Characteristic.On) - .onSet(this.OnSet.bind(this)); + this.Switch.Service.setCharacteristic(this.hap.Characteristic.Name, this.Switch.Name).getCharacteristic(this.hap.Characteristic.On).onSet(this.OnSet.bind(this)) // Remove other services - this.removeFanService(accessory); - this.removeLockService(accessory); - this.removeDoorService(accessory); - this.removeFaucetService(accessory); - this.removeOutletService(accessory); - this.removeWindowService(accessory); - this.removeGarageDoorService(accessory); - this.removeWindowCoveringService(accessory); - this.removeStatefulProgrammableSwitchService(accessory); + this.removeFanService(accessory) + this.removeLockService(accessory) + this.removeDoorService(accessory) + this.removeFaucetService(accessory) + this.removeOutletService(accessory) + this.removeWindowService(accessory) + this.removeGarageDoorService(accessory) + this.removeWindowCoveringService(accessory) + this.removeStatefulProgrammableSwitchService(accessory) } else if (this.otherDeviceType === 'garagedoor') { // Set category - accessory.category = this.hap.Categories.GARAGE_DOOR_OPENER; + accessory.category = this.hap.Categories.GARAGE_DOOR_OPENER // Initialize GarageDoor Service - accessory.context.GarageDoor = accessory.context.GarageDoor ?? {}; + accessory.context.GarageDoor = accessory.context.GarageDoor ?? {} this.GarageDoor = { Name: accessory.displayName, Service: accessory.getService(this.hap.Service.GarageDoorOpener) ?? accessory.addService(this.hap.Service.GarageDoorOpener) as Service, - }; - accessory.context.GarageDoor = this.GarageDoor as object; - this.debugLog('Displaying as Garage Door Opener'); + } + accessory.context.GarageDoor = this.GarageDoor as object + this.debugLog('Displaying as Garage Door Opener') // Initialize GarageDoor Characteristics - this.GarageDoor.Service - .setCharacteristic(this.hap.Characteristic.Name, this.GarageDoor.Name) - .setCharacteristic(this.hap.Characteristic.ObstructionDetected, false) - .getCharacteristic(this.hap.Characteristic.TargetDoorState) - .setProps({ - validValues: [0, 100], - minValue: 0, - maxValue: 100, - minStep: 100, - }) - .onSet(this.OnSet.bind(this)); + this.GarageDoor.Service.setCharacteristic(this.hap.Characteristic.Name, this.GarageDoor.Name).setCharacteristic(this.hap.Characteristic.ObstructionDetected, false).getCharacteristic(this.hap.Characteristic.TargetDoorState).setProps({ + validValues: [0, 100], + minValue: 0, + maxValue: 100, + minStep: 100, + }).onSet(this.OnSet.bind(this)) // Remove other services - this.removeFanService(accessory); - this.removeLockService(accessory); - this.removeDoorService(accessory); - this.removeFaucetService(accessory); - this.removeOutletService(accessory); - this.removeSwitchService(accessory); - this.removeWindowService(accessory); - this.removeWindowCoveringService(accessory); - this.removeStatefulProgrammableSwitchService(accessory); + this.removeFanService(accessory) + this.removeLockService(accessory) + this.removeDoorService(accessory) + this.removeFaucetService(accessory) + this.removeOutletService(accessory) + this.removeSwitchService(accessory) + this.removeWindowService(accessory) + this.removeWindowCoveringService(accessory) + this.removeStatefulProgrammableSwitchService(accessory) } else if (this.otherDeviceType === 'door') { // Set category - accessory.category = this.hap.Categories.DOOR; + accessory.category = this.hap.Categories.DOOR // Initialize Door Service - accessory.context.Door = accessory.context.Door ?? {}; + accessory.context.Door = accessory.context.Door ?? {} this.Door = { Name: accessory.displayName, Service: accessory.getService(this.hap.Service.Door) ?? accessory.addService(this.hap.Service.Door) as Service, - }; - accessory.context.Door = this.Door as object; - this.debugLog('Displaying as Door'); + } + accessory.context.Door = this.Door as object + this.debugLog('Displaying as Door') // Initialize Door Characteristics - this.Door.Service - .setCharacteristic(this.hap.Characteristic.Name, this.Door.Name) - .setCharacteristic(this.hap.Characteristic.PositionState, this.hap.Characteristic.PositionState.STOPPED) - .getCharacteristic(this.hap.Characteristic.TargetPosition).setProps({ - validValues: [0, 100], - minValue: 0, - maxValue: 100, - minStep: 100, - }) - .onSet(this.OnSet.bind(this)); + this.Door.Service.setCharacteristic(this.hap.Characteristic.Name, this.Door.Name).setCharacteristic(this.hap.Characteristic.PositionState, this.hap.Characteristic.PositionState.STOPPED).getCharacteristic(this.hap.Characteristic.TargetPosition).setProps({ + validValues: [0, 100], + minValue: 0, + maxValue: 100, + minStep: 100, + }).onSet(this.OnSet.bind(this)) // Remove other services - this.removeFanService(accessory); - this.removeLockService(accessory); - this.removeOutletService(accessory); - this.removeFaucetService(accessory); - this.removeSwitchService(accessory); - this.removeWindowService(accessory); - this.removeGarageDoorService(accessory); - this.removeWindowCoveringService(accessory); - this.removeStatefulProgrammableSwitchService(accessory); + this.removeFanService(accessory) + this.removeLockService(accessory) + this.removeOutletService(accessory) + this.removeFaucetService(accessory) + this.removeSwitchService(accessory) + this.removeWindowService(accessory) + this.removeGarageDoorService(accessory) + this.removeWindowCoveringService(accessory) + this.removeStatefulProgrammableSwitchService(accessory) } else if (this.otherDeviceType === 'window') { // Set category - accessory.category = this.hap.Categories.WINDOW; + accessory.category = this.hap.Categories.WINDOW // Initialize Window Service - accessory.context.Window = accessory.context.Window ?? {}; + accessory.context.Window = accessory.context.Window ?? {} this.Window = { Name: accessory.displayName, Service: accessory.getService(this.hap.Service.Window) ?? accessory.addService(this.hap.Service.Window) as Service, - }; - accessory.context.Window = this.Window as object; - this.debugLog('Displaying as Window'); + } + accessory.context.Window = this.Window as object + this.debugLog('Displaying as Window') // Initialize Window Characteristics - this.Window.Service - .setCharacteristic(this.hap.Characteristic.Name, this.Window.Name) - .setCharacteristic(this.hap.Characteristic.PositionState, this.hap.Characteristic.PositionState.STOPPED) - .getCharacteristic(this.hap.Characteristic.TargetPosition).setProps({ - validValues: [0, 100], - minValue: 0, - maxValue: 100, - minStep: 100, - }) - .onSet(this.OnSet.bind(this)); + this.Window.Service.setCharacteristic(this.hap.Characteristic.Name, this.Window.Name).setCharacteristic(this.hap.Characteristic.PositionState, this.hap.Characteristic.PositionState.STOPPED).getCharacteristic(this.hap.Characteristic.TargetPosition).setProps({ + validValues: [0, 100], + minValue: 0, + maxValue: 100, + minStep: 100, + }).onSet(this.OnSet.bind(this)) // Remove other services - this.removeFanService(accessory); - this.removeLockService(accessory); - this.removeDoorService(accessory); - this.removeOutletService(accessory); - this.removeFaucetService(accessory); - this.removeSwitchService(accessory); - this.removeGarageDoorService(accessory); - this.removeWindowCoveringService(accessory); - this.removeStatefulProgrammableSwitchService(accessory); + this.removeFanService(accessory) + this.removeLockService(accessory) + this.removeDoorService(accessory) + this.removeOutletService(accessory) + this.removeFaucetService(accessory) + this.removeSwitchService(accessory) + this.removeGarageDoorService(accessory) + this.removeWindowCoveringService(accessory) + this.removeStatefulProgrammableSwitchService(accessory) } else if (this.otherDeviceType === 'windowcovering') { // Set category - accessory.category = this.hap.Categories.WINDOW_COVERING; + accessory.category = this.hap.Categories.WINDOW_COVERING // Initialize WindowCovering Service - accessory.context.WindowCovering = accessory.context.WindowCovering ?? {}; + accessory.context.WindowCovering = accessory.context.WindowCovering ?? {} this.WindowCovering = { Name: accessory.displayName, Service: accessory.getService(this.hap.Service.WindowCovering) ?? accessory.addService(this.hap.Service.WindowCovering) as Service, - }; - accessory.context.WindowCovering = this.WindowCovering as object; - this.debugLog('Displaying as Window Covering'); + } + accessory.context.WindowCovering = this.WindowCovering as object + this.debugLog('Displaying as Window Covering') // Initialize WindowCovering Characteristics - this.WindowCovering.Service - .setCharacteristic(this.hap.Characteristic.Name, this.WindowCovering.Name) - .setCharacteristic(this.hap.Characteristic.PositionState, this.hap.Characteristic.PositionState.STOPPED) - .getCharacteristic(this.hap.Characteristic.TargetPosition) - .setProps({ - validValues: [0, 100], - minValue: 0, - maxValue: 100, - minStep: 100, - }) - .onSet(this.OnSet.bind(this)); + this.WindowCovering.Service.setCharacteristic(this.hap.Characteristic.Name, this.WindowCovering.Name).setCharacteristic(this.hap.Characteristic.PositionState, this.hap.Characteristic.PositionState.STOPPED).getCharacteristic(this.hap.Characteristic.TargetPosition).setProps({ + validValues: [0, 100], + minValue: 0, + maxValue: 100, + minStep: 100, + }).onSet(this.OnSet.bind(this)) // Remove other services - this.removeFanService(accessory); - this.removeLockService(accessory); - this.removeDoorService(accessory); - this.removeOutletService(accessory); - this.removeFaucetService(accessory); - this.removeSwitchService(accessory); - this.removeWindowService(accessory); - this.removeGarageDoorService(accessory); - this.removeStatefulProgrammableSwitchService(accessory); + this.removeFanService(accessory) + this.removeLockService(accessory) + this.removeDoorService(accessory) + this.removeOutletService(accessory) + this.removeFaucetService(accessory) + this.removeSwitchService(accessory) + this.removeWindowService(accessory) + this.removeGarageDoorService(accessory) + this.removeStatefulProgrammableSwitchService(accessory) } else if (this.otherDeviceType === 'lock') { // Set category - accessory.category = this.hap.Categories.DOOR_LOCK; + accessory.category = this.hap.Categories.DOOR_LOCK // Initialize Lock Service - accessory.context.LockMechanism = accessory.context.LockMechanism ?? {}; + accessory.context.LockMechanism = accessory.context.LockMechanism ?? {} this.LockMechanism = { Name: accessory.displayName, Service: accessory.getService(this.hap.Service.LockMechanism) ?? accessory.addService(this.hap.Service.LockMechanism) as Service, - }; - accessory.context.LockMechanism = this.LockMechanism as object; - this.debugLog('Displaying as Lock'); + } + accessory.context.LockMechanism = this.LockMechanism as object + this.debugLog('Displaying as Lock') // Initialize Lock Characteristics - this.LockMechanism.Service - .setCharacteristic(this.hap.Characteristic.Name, this.LockMechanism.Name) - .setCharacteristic(this.hap.Characteristic.PositionState, this.hap.Characteristic.PositionState.STOPPED) - .getCharacteristic(this.hap.Characteristic.LockTargetState) - .onSet(this.OnSet.bind(this)); + this.LockMechanism.Service.setCharacteristic(this.hap.Characteristic.Name, this.LockMechanism.Name).setCharacteristic(this.hap.Characteristic.PositionState, this.hap.Characteristic.PositionState.STOPPED).getCharacteristic(this.hap.Characteristic.LockTargetState).onSet(this.OnSet.bind(this)) // Remove other services - this.removeFanService(accessory); - this.removeDoorService(accessory); - this.removeOutletService(accessory); - this.removeSwitchService(accessory); - this.removeFaucetService(accessory); - this.removeWindowService(accessory); - this.removeGarageDoorService(accessory); - this.removeWindowCoveringService(accessory); - this.removeStatefulProgrammableSwitchService(accessory); + this.removeFanService(accessory) + this.removeDoorService(accessory) + this.removeOutletService(accessory) + this.removeSwitchService(accessory) + this.removeFaucetService(accessory) + this.removeWindowService(accessory) + this.removeGarageDoorService(accessory) + this.removeWindowCoveringService(accessory) + this.removeStatefulProgrammableSwitchService(accessory) } else if (this.otherDeviceType === 'faucet') { // Set category - accessory.category = this.hap.Categories.FAUCET; + accessory.category = this.hap.Categories.FAUCET // Initialize Faucet Service - accessory.context.Faucet = accessory.context.Faucet ?? {}; + accessory.context.Faucet = accessory.context.Faucet ?? {} this.Faucet = { Name: accessory.displayName, Service: accessory.getService(this.hap.Service.Faucet) ?? accessory.addService(this.hap.Service.Faucet) as Service, - }; - accessory.context.Faucet = this.Faucet as object; - this.debugLog('Displaying as Faucet'); + } + accessory.context.Faucet = this.Faucet as object + this.debugLog('Displaying as Faucet') // Initialize Faucet Characteristics - this.Faucet.Service - .setCharacteristic(this.hap.Characteristic.Name, this.Faucet.Name) - .getCharacteristic(this.hap.Characteristic.Active) - .onSet(this.OnSet.bind(this)); + this.Faucet.Service.setCharacteristic(this.hap.Characteristic.Name, this.Faucet.Name).getCharacteristic(this.hap.Characteristic.Active).onSet(this.OnSet.bind(this)) // Remove other services - this.removeFanService(accessory); - this.removeLockService(accessory); - this.removeDoorService(accessory); - this.removeOutletService(accessory); - this.removeSwitchService(accessory); - this.removeWindowService(accessory); - this.removeGarageDoorService(accessory); - this.removeWindowCoveringService(accessory); - this.removeStatefulProgrammableSwitchService(accessory); + this.removeFanService(accessory) + this.removeLockService(accessory) + this.removeDoorService(accessory) + this.removeOutletService(accessory) + this.removeSwitchService(accessory) + this.removeWindowService(accessory) + this.removeGarageDoorService(accessory) + this.removeWindowCoveringService(accessory) + this.removeStatefulProgrammableSwitchService(accessory) } else if (this.otherDeviceType === 'fan') { // Set category - accessory.category = this.hap.Categories.FAN; + accessory.category = this.hap.Categories.FAN // Initialize Fan Service - accessory.context.Fan = accessory.context.Fan ?? {}; + accessory.context.Fan = accessory.context.Fan ?? {} this.Fan = { Name: accessory.displayName, Service: accessory.getService(this.hap.Service.Fanv2) ?? accessory.addService(this.hap.Service.Fanv2) as Service, - }; - accessory.context.Fan = this.Fan as object; - this.debugLog('Displaying as Fan'); + } + accessory.context.Fan = this.Fan as object + this.debugLog('Displaying as Fan') // Initialize Fan Characteristics - this.Fan.Service - .setCharacteristic(this.hap.Characteristic.Name, this.Fan.Name) - .getCharacteristic(this.hap.Characteristic.Active) - .onSet(this.OnSet.bind(this)); + this.Fan.Service.setCharacteristic(this.hap.Characteristic.Name, this.Fan.Name).getCharacteristic(this.hap.Characteristic.Active).onSet(this.OnSet.bind(this)) // Remove other services - this.removeLockService(accessory); - this.removeDoorService(accessory); - this.removeFaucetService(accessory); - this.removeOutletService(accessory); - this.removeSwitchService(accessory); - this.removeWindowService(accessory); - this.removeGarageDoorService(accessory); - this.removeWindowCoveringService(accessory); - this.removeStatefulProgrammableSwitchService(accessory); + this.removeLockService(accessory) + this.removeDoorService(accessory) + this.removeFaucetService(accessory) + this.removeOutletService(accessory) + this.removeSwitchService(accessory) + this.removeWindowService(accessory) + this.removeGarageDoorService(accessory) + this.removeWindowCoveringService(accessory) + this.removeStatefulProgrammableSwitchService(accessory) } else if (this.otherDeviceType === 'stateful') { // Set category - accessory.category = this.hap.Categories.PROGRAMMABLE_SWITCH; + accessory.category = this.hap.Categories.PROGRAMMABLE_SWITCH // Initialize StatefulProgrammableSwitch Service - accessory.context.StatefulProgrammableSwitch = accessory.context.StatefulProgrammableSwitch ?? {}; + accessory.context.StatefulProgrammableSwitch = accessory.context.StatefulProgrammableSwitch ?? {} this.StatefulProgrammableSwitch = { Name: accessory.displayName, - Service: accessory.getService(this.hap.Service.StatefulProgrammableSwitch) - ?? accessory.addService(this.hap.Service.StatefulProgrammableSwitch) as Service, - }; - accessory.context.StatefulProgrammableSwitch = this.StatefulProgrammableSwitch as object; - this.debugLog('Displaying as Stateful Programmable Switch'); + Service: accessory.getService(this.hap.Service.StatefulProgrammableSwitch) ?? accessory.addService(this.hap.Service.StatefulProgrammableSwitch) as Service, + } + accessory.context.StatefulProgrammableSwitch = this.StatefulProgrammableSwitch as object + this.debugLog('Displaying as Stateful Programmable Switch') // Initialize StatefulProgrammableSwitch Characteristics - this.StatefulProgrammableSwitch.Service - .setCharacteristic(this.hap.Characteristic.Name, this.StatefulProgrammableSwitch.Name) - .getCharacteristic(this.hap.Characteristic.ProgrammableSwitchOutputState) - .onSet(this.OnSet.bind(this)); + this.StatefulProgrammableSwitch.Service.setCharacteristic(this.hap.Characteristic.Name, this.StatefulProgrammableSwitch.Name).getCharacteristic(this.hap.Characteristic.ProgrammableSwitchOutputState).onSet(this.OnSet.bind(this)) // Remove other services - this.removeFanService(accessory); - this.removeLockService(accessory); - this.removeDoorService(accessory); - this.removeFaucetService(accessory); - this.removeOutletService(accessory); - this.removeSwitchService(accessory); - this.removeWindowService(accessory); - this.removeGarageDoorService(accessory); - this.removeWindowCoveringService(accessory); + this.removeFanService(accessory) + this.removeLockService(accessory) + this.removeDoorService(accessory) + this.removeFaucetService(accessory) + this.removeOutletService(accessory) + this.removeSwitchService(accessory) + this.removeWindowService(accessory) + this.removeGarageDoorService(accessory) + this.removeWindowCoveringService(accessory) } else if (this.otherDeviceType === 'outlet') { // Set category - accessory.category = this.hap.Categories.OUTLET; + accessory.category = this.hap.Categories.OUTLET // Initialize Switch property - accessory.context.Outlet = accessory.context.Outlet ?? {}; + accessory.context.Outlet = accessory.context.Outlet ?? {} this.Outlet = { Name: accessory.displayName, Service: accessory.getService(this.hap.Service.Outlet) ?? accessory.addService(this.hap.Service.Outlet) as Service, - }; - accessory.context.Outlet = this.Outlet as object; - this.debugLog('Displaying as Outlet'); + } + accessory.context.Outlet = this.Outlet as object + this.debugLog('Displaying as Outlet') // Initialize Outlet Characteristics - this.Outlet.Service - .setCharacteristic(this.hap.Characteristic.Name, this.Outlet.Name) - .getCharacteristic(this.hap.Characteristic.On) - .onSet(this.OnSet.bind(this)); + this.Outlet.Service.setCharacteristic(this.hap.Characteristic.Name, this.Outlet.Name).getCharacteristic(this.hap.Characteristic.On).onSet(this.OnSet.bind(this)) // Remove other services - this.removeFanService(accessory); - this.removeLockService(accessory); - this.removeDoorService(accessory); - this.removeFaucetService(accessory); - this.removeSwitchService(accessory); - this.removeWindowService(accessory); - this.removeGarageDoorService(accessory); - this.removeWindowCoveringService(accessory); - this.removeStatefulProgrammableSwitchService(accessory); + this.removeFanService(accessory) + this.removeLockService(accessory) + this.removeDoorService(accessory) + this.removeFaucetService(accessory) + this.removeSwitchService(accessory) + this.removeWindowService(accessory) + this.removeGarageDoorService(accessory) + this.removeWindowCoveringService(accessory) + this.removeStatefulProgrammableSwitchService(accessory) } else { - this.errorLog('Device Type not set'); + this.errorLog('Device Type not set') } } @@ -381,458 +344,451 @@ export class Others extends irdeviceBase { async OnSet(value: CharacteristicValue): Promise { if (this.otherDeviceType === 'switch') { if (this.Switch) { - await this.debugLog(`Set On: ${value}`); - this.On = value === false ? false : true; + await this.debugLog(`Set On: ${value}`) + this.On = value !== false } } else if (this.otherDeviceType === 'garagedoor') { if (this.GarageDoor) { - await this.debugLog(`Set TargetDoorState: ${value}`); - this.On = value === this.hap.Characteristic.TargetDoorState.CLOSED ? false : true; + await this.debugLog(`Set TargetDoorState: ${value}`) + this.On = value !== this.hap.Characteristic.TargetDoorState.CLOSED } } else if (this.otherDeviceType === 'door') { if (this.Door) { - await this.debugLog(`Set TargetPosition: ${value}`); - this.On = value === 0 ? false : true; + await this.debugLog(`Set TargetPosition: ${value}`) + this.On = value !== 0 } } else if (this.otherDeviceType === 'window') { if (this.Window) { - await this.debugLog(`Set TargetPosition: ${value}`); - this.On = value === 0 ? false : true; + await this.debugLog(`Set TargetPosition: ${value}`) + this.On = value !== 0 } } else if (this.otherDeviceType === 'windowcovering') { if (this.WindowCovering) { - await this.debugLog(`Set TargetPosition: ${value}`); - this.On = value === 0 ? false : true; + await this.debugLog(`Set TargetPosition: ${value}`) + this.On = value !== 0 } } else if (this.otherDeviceType === 'lock') { if (this.LockMechanism) { - await this.debugLog(`Set LockTargetState: ${value}`); - this.On = value === this.hap.Characteristic.LockTargetState.SECURED ? false : true; + await this.debugLog(`Set LockTargetState: ${value}`) + this.On = value !== this.hap.Characteristic.LockTargetState.SECURED } } else if (this.otherDeviceType === 'faucet') { if (this.Faucet) { - await this.debugLog(`Set Active: ${value}`); - this.On = value === this.hap.Characteristic.Active.INACTIVE ? false : true; + await this.debugLog(`Set Active: ${value}`) + this.On = value !== this.hap.Characteristic.Active.INACTIVE } } else if (this.otherDeviceType === 'stateful') { if (this.StatefulProgrammableSwitch) { - await this.debugLog(`Set ProgrammableSwitchOutputState: ${value}`); - this.On = value === 0 ? false : true; + await this.debugLog(`Set ProgrammableSwitchOutputState: ${value}`) + this.On = value !== 0 } } else { if (this.Outlet) { - await this.debugLog(`Set On: ${value}`); - this.On = value === false ? false : true; + await this.debugLog(`Set On: ${value}`) + this.On = value !== false } } - //pushChanges + // pushChanges if (this.On === true) { - await this.pushOnChanges(this.On); + await this.pushOnChanges(this.On) } else { - await this.pushOffChanges(this.On); + await this.pushOffChanges(this.On) } } /** * Pushes the requested changes to the SwitchBot API - * deviceType commandType Command command parameter Description - * Other - "command" "turnOff" "default" = set to OFF state - * Other - "command" "turnOn" "default" = set to ON state - * Other - "command" "volumeAdd" "default" = volume up - * Other - "command" "volumeSub" "default" = volume down - * Other - "command" "channelAdd" "default" = next channel - * Other - "command" "channelSub" "default" = previous channel + * deviceType commandType Command command parameter Description + * Other - "command" "turnOff" "default" = set to OFF state + * Other - "command" "turnOn" "default" = set to ON state + * Other - "command" "volumeAdd" "default" = volume up + * Other - "command" "volumeSub" "default" = volume down + * Other - "command" "channelAdd" "default" = next channel + * Other - "command" "channelSub" "default" = previous channel */ async pushOnChanges(On: boolean): Promise { - await this.debugLog(`pushOnChanges On: ${On},` - + ` disablePushOn: ${this.disablePushOn}, customize: ${this.device.customize}, customOn: ${this.device.customOn}`); + await this.debugLog(`pushOnChanges On: ${On}, disablePushOn: ${this.disablePushOn}, customize: ${this.device.customize}, customOn: ${this.device.customOn}`) if (this.device.customize) { if (On === true && !this.disablePushOn) { - const commandType: string = await this.commandType(); - const command: string = await this.commandOn(); + const commandType: string = await this.commandType() + const command: string = await this.commandOn() const bodyChange = JSON.stringify({ - command: command, + command, parameter: 'default', - commandType: commandType, - }); - await this.pushChanges(bodyChange); + commandType, + }) + await this.pushChanges(bodyChange) } } else { - this.errorLog('On Command not set'); + this.errorLog('On Command not set') } } async pushOffChanges(On: boolean): Promise { - await this.debugLog(`pushOffChanges On: ${On},` - + ` disablePushOff: ${this.disablePushOff}, customize: ${this.device.customize}, customOff: ${this.device.customOff}`); + await this.debugLog(`pushOffChanges On: ${On}, disablePushOff: ${this.disablePushOff}, customize: ${this.device.customize}, customOff: ${this.device.customOff}`) if (this.device.customize) { if (On === false && !this.disablePushOff) { - const commandType: string = await this.commandType(); - const command: string = await this.commandOff(); + const commandType: string = await this.commandType() + const command: string = await this.commandOff() const bodyChange = JSON.stringify({ - command: command, + command, parameter: 'default', - commandType: commandType, - }); - await this.pushChanges(bodyChange); + commandType, + }) + await this.pushChanges(bodyChange) } } else { - this.errorLog('Off Command not set.'); + this.errorLog('Off Command not set.') } } async pushChanges(bodyChange: any): Promise { - await this.debugLog('pushChanges'); + await this.debugLog('pushChanges') if (this.device.connectionType === 'OpenAPI') { - this.infoLog(`Sending request to SwitchBot API, body: ${bodyChange},`); + this.infoLog(`Sending request to SwitchBot API, body: ${bodyChange},`) try { - const { body, statusCode } = await this.pushChangeRequest(bodyChange); - const deviceStatus: any = await body.json(); - await this.pushStatusCodes(statusCode, deviceStatus); + const { body, statusCode } = await this.pushChangeRequest(bodyChange) + const deviceStatus: any = await body.json() + await this.pushStatusCodes(statusCode, deviceStatus) if (await this.successfulStatusCodes(statusCode, deviceStatus)) { - await this.successfulPushChange(statusCode, deviceStatus, bodyChange); - await this.updateHomeKitCharacteristics(); + await this.successfulPushChange(statusCode, deviceStatus, bodyChange) + await this.updateHomeKitCharacteristics() } else { - await this.statusCode(statusCode); - await this.statusCode(deviceStatus.statusCode); + await this.statusCode(statusCode) + await this.statusCode(deviceStatus.statusCode) } } catch (e: any) { - await this.apiError(e); - await this.pushChangeError(e); + await this.apiError(e) + await this.pushChangeError(e) } } else { - await this.warnLog(`Connection Type: ${this.device.connectionType}, commands will not be sent to OpenAPI`); + await this.warnLog(`Connection Type: ${this.device.connectionType}, commands will not be sent to OpenAPI`) } } async updateHomeKitCharacteristics(): Promise { - await this.debugLog('updateHomeKitCharacteristics'); + await this.debugLog('updateHomeKitCharacteristics') // State if (this.otherDeviceType === 'switch' && this.Switch) { if (this.On === undefined) { - await this.debugLog(`On: ${this.On}`); + await this.debugLog(`On: ${this.On}`) } else { - this.Switch.Service.updateCharacteristic(this.hap.Characteristic.On, this.On); - await this.debugLog(`updateCharacteristic On: ${this.On}`); + this.Switch.Service.updateCharacteristic(this.hap.Characteristic.On, this.On) + await this.debugLog(`updateCharacteristic On: ${this.On}`) } } else if (this.otherDeviceType === 'garagedoor' && this.GarageDoor) { if (this.On === undefined) { - await this.debugLog(`On: ${this.On}`); + await this.debugLog(`On: ${this.On}`) } else { if (this.On) { - this.GarageDoor.Service.updateCharacteristic(this.hap.Characteristic.TargetDoorState, this.hap.Characteristic.TargetDoorState.OPEN); - this.GarageDoor.Service.updateCharacteristic(this.hap.Characteristic.CurrentDoorState, this.hap.Characteristic.CurrentDoorState.OPEN); - await this.debugLog(`updateCharacteristic TargetDoorState: Open, CurrentDoorState: Open (${this.On})`); + this.GarageDoor.Service.updateCharacteristic(this.hap.Characteristic.TargetDoorState, this.hap.Characteristic.TargetDoorState.OPEN) + this.GarageDoor.Service.updateCharacteristic(this.hap.Characteristic.CurrentDoorState, this.hap.Characteristic.CurrentDoorState.OPEN) + await this.debugLog(`updateCharacteristic TargetDoorState: Open, CurrentDoorState: Open (${this.On})`) } else { - this.GarageDoor.Service.updateCharacteristic(this.hap.Characteristic.TargetDoorState, this.hap.Characteristic.TargetDoorState.CLOSED); - this.GarageDoor.Service.updateCharacteristic(this.hap.Characteristic.CurrentDoorState, this.hap.Characteristic.CurrentDoorState.CLOSED); - await this.debugLog(`updateCharacteristicc TargetDoorState: Closed, CurrentDoorState: Closed (${this.On})`); + this.GarageDoor.Service.updateCharacteristic(this.hap.Characteristic.TargetDoorState, this.hap.Characteristic.TargetDoorState.CLOSED) + this.GarageDoor.Service.updateCharacteristic(this.hap.Characteristic.CurrentDoorState, this.hap.Characteristic.CurrentDoorState.CLOSED) + await this.debugLog(`updateCharacteristicc TargetDoorState: Closed, CurrentDoorState: Closed (${this.On})`) } } - await this.debugLog(`Garage Door On: ${this.On}`); + await this.debugLog(`Garage Door On: ${this.On}`) } else if (this.otherDeviceType === 'door' && this.Door) { if (this.On === undefined) { - await this.debugLog(`On: ${this.On}`); + await this.debugLog(`On: ${this.On}`) } else { if (this.On) { - this.Door.Service.updateCharacteristic(this.hap.Characteristic.TargetPosition, 100); - this.Door.Service.updateCharacteristic(this.hap.Characteristic.CurrentPosition, 100); - this.Door.Service.updateCharacteristic(this.hap.Characteristic.PositionState, this.hap.Characteristic.PositionState.STOPPED); - await this.debugLog(`updateCharacteristicc TargetPosition: 100, CurrentPosition: 100 (${this.On})`); + this.Door.Service.updateCharacteristic(this.hap.Characteristic.TargetPosition, 100) + this.Door.Service.updateCharacteristic(this.hap.Characteristic.CurrentPosition, 100) + this.Door.Service.updateCharacteristic(this.hap.Characteristic.PositionState, this.hap.Characteristic.PositionState.STOPPED) + await this.debugLog(`updateCharacteristicc TargetPosition: 100, CurrentPosition: 100 (${this.On})`) } else { - this.Door.Service.updateCharacteristic(this.hap.Characteristic.TargetPosition, 0); - this.Door.Service.updateCharacteristic(this.hap.Characteristic.CurrentPosition, 0); - this.Door.Service.updateCharacteristic(this.hap.Characteristic.PositionState, this.hap.Characteristic.PositionState.STOPPED); - await this.debugLog(`updateCharacteristicc TargetPosition: 0, CurrentPosition: 0 (${this.On})`); + this.Door.Service.updateCharacteristic(this.hap.Characteristic.TargetPosition, 0) + this.Door.Service.updateCharacteristic(this.hap.Characteristic.CurrentPosition, 0) + this.Door.Service.updateCharacteristic(this.hap.Characteristic.PositionState, this.hap.Characteristic.PositionState.STOPPED) + await this.debugLog(`updateCharacteristicc TargetPosition: 0, CurrentPosition: 0 (${this.On})`) } } - await this.debugLog(`Door On: ${this.On}`); + await this.debugLog(`Door On: ${this.On}`) } else if (this.otherDeviceType === 'window' && this.Window) { if (this.On === undefined) { - await this.debugLog(`On: ${this.On}`); + await this.debugLog(`On: ${this.On}`) } else { if (this.On) { - this.Window.Service.updateCharacteristic(this.hap.Characteristic.TargetPosition, 100); - this.Window.Service.updateCharacteristic(this.hap.Characteristic.CurrentPosition, 100); - this.Window.Service.updateCharacteristic(this.hap.Characteristic.PositionState, this.hap.Characteristic.PositionState.STOPPED); - await this.debugLog(`updateCharacteristicc TargetPosition: 100, CurrentPosition: 100 (${this.On})`); + this.Window.Service.updateCharacteristic(this.hap.Characteristic.TargetPosition, 100) + this.Window.Service.updateCharacteristic(this.hap.Characteristic.CurrentPosition, 100) + this.Window.Service.updateCharacteristic(this.hap.Characteristic.PositionState, this.hap.Characteristic.PositionState.STOPPED) + await this.debugLog(`updateCharacteristicc TargetPosition: 100, CurrentPosition: 100 (${this.On})`) } else { - this.Window.Service.updateCharacteristic(this.hap.Characteristic.TargetPosition, 0); - this.Window.Service.updateCharacteristic(this.hap.Characteristic.CurrentPosition, 0); - this.Window.Service.updateCharacteristic(this.hap.Characteristic.PositionState, this.hap.Characteristic.PositionState.STOPPED); - await this.debugLog(`updateCharacteristicc TargetPosition: 0, CurrentPosition: 0 (${this.On})`); + this.Window.Service.updateCharacteristic(this.hap.Characteristic.TargetPosition, 0) + this.Window.Service.updateCharacteristic(this.hap.Characteristic.CurrentPosition, 0) + this.Window.Service.updateCharacteristic(this.hap.Characteristic.PositionState, this.hap.Characteristic.PositionState.STOPPED) + await this.debugLog(`updateCharacteristicc TargetPosition: 0, CurrentPosition: 0 (${this.On})`) } } - await this.debugLog(`Window On: ${this.On}`); + await this.debugLog(`Window On: ${this.On}`) } else if (this.otherDeviceType === 'windowcovering' && this.WindowCovering) { if (this.On === undefined) { - await this.debugLog(`On: ${this.On}`); + await this.debugLog(`On: ${this.On}`) } else { if (this.On) { - this.WindowCovering.Service.updateCharacteristic(this.hap.Characteristic.TargetPosition, 100); - this.WindowCovering.Service.updateCharacteristic(this.hap.Characteristic.CurrentPosition, 100); - this.WindowCovering.Service.updateCharacteristic(this.hap.Characteristic.PositionState, this.hap.Characteristic.PositionState.STOPPED); - await this.debugLog(`updateCharacteristicc TargetPosition: 100, CurrentPosition: 100 (${this.On})`); + this.WindowCovering.Service.updateCharacteristic(this.hap.Characteristic.TargetPosition, 100) + this.WindowCovering.Service.updateCharacteristic(this.hap.Characteristic.CurrentPosition, 100) + this.WindowCovering.Service.updateCharacteristic(this.hap.Characteristic.PositionState, this.hap.Characteristic.PositionState.STOPPED) + await this.debugLog(`updateCharacteristicc TargetPosition: 100, CurrentPosition: 100 (${this.On})`) } else { - this.WindowCovering.Service.updateCharacteristic(this.hap.Characteristic.TargetPosition, 0); - this.WindowCovering.Service.updateCharacteristic(this.hap.Characteristic.CurrentPosition, 0); - this.WindowCovering.Service.updateCharacteristic(this.hap.Characteristic.PositionState, this.hap.Characteristic.PositionState.STOPPED); - await this.debugLog(`updateCharacteristicc TargetPosition: 0, CurrentPosition: 0 (${this.On})`); + this.WindowCovering.Service.updateCharacteristic(this.hap.Characteristic.TargetPosition, 0) + this.WindowCovering.Service.updateCharacteristic(this.hap.Characteristic.CurrentPosition, 0) + this.WindowCovering.Service.updateCharacteristic(this.hap.Characteristic.PositionState, this.hap.Characteristic.PositionState.STOPPED) + await this.debugLog(`updateCharacteristicc TargetPosition: 0, CurrentPosition: 0 (${this.On})`) } } - await this.debugLog(`Window Covering On: ${this.On}`); + await this.debugLog(`Window Covering On: ${this.On}`) } else if (this.otherDeviceType === 'lock' && this.LockMechanism) { if (this.On === undefined) { - await this.debugLog(`On: ${this.On}`); + await this.debugLog(`On: ${this.On}`) } else { if (this.On) { - this.LockMechanism.Service.updateCharacteristic(this.hap.Characteristic.LockTargetState, - this.hap.Characteristic.LockTargetState.UNSECURED); - this.LockMechanism.Service.updateCharacteristic(this.hap.Characteristic.LockCurrentState, - this.hap.Characteristic.LockCurrentState.UNSECURED); - await this.debugLog(`updateCharacteristicc LockTargetState: UNSECURED, LockCurrentState: UNSECURED (${this.On})`); + this.LockMechanism.Service.updateCharacteristic(this.hap.Characteristic.LockTargetState, this.hap.Characteristic.LockTargetState.UNSECURED) + this.LockMechanism.Service.updateCharacteristic(this.hap.Characteristic.LockCurrentState, this.hap.Characteristic.LockCurrentState.UNSECURED) + await this.debugLog(`updateCharacteristicc LockTargetState: UNSECURED, LockCurrentState: UNSECURED (${this.On})`) } else { - this.LockMechanism.Service.updateCharacteristic(this.hap.Characteristic.LockTargetState, - this.hap.Characteristic.LockTargetState.SECURED); - this.LockMechanism.Service.updateCharacteristic(this.hap.Characteristic.LockCurrentState, - this.hap.Characteristic.LockCurrentState.SECURED); - await this.debugLog(`updateCharacteristic LockTargetState: SECURED, LockCurrentState: SECURED (${this.On})`); + this.LockMechanism.Service.updateCharacteristic(this.hap.Characteristic.LockTargetState, this.hap.Characteristic.LockTargetState.SECURED) + this.LockMechanism.Service.updateCharacteristic(this.hap.Characteristic.LockCurrentState, this.hap.Characteristic.LockCurrentState.SECURED) + await this.debugLog(`updateCharacteristic LockTargetState: SECURED, LockCurrentState: SECURED (${this.On})`) } } - await this.debugLog(`Lock On: ${this.On}`); + await this.debugLog(`Lock On: ${this.On}`) } else if (this.otherDeviceType === 'faucet' && this.Faucet) { if (this.On === undefined) { - await this.debugLog(`On: ${this.On}`); + await this.debugLog(`On: ${this.On}`) } else { if (this.On) { - this.Faucet.Service.updateCharacteristic(this.hap.Characteristic.Active, this.hap.Characteristic.Active.ACTIVE); - await this.debugLog(`updateCharacteristic Active: ${this.On}`); + this.Faucet.Service.updateCharacteristic(this.hap.Characteristic.Active, this.hap.Characteristic.Active.ACTIVE) + await this.debugLog(`updateCharacteristic Active: ${this.On}`) } else { - this.Faucet.Service.updateCharacteristic(this.hap.Characteristic.Active, this.hap.Characteristic.Active.INACTIVE); - await this.debugLog(`updateCharacteristic Active: ${this.On}`); + this.Faucet.Service.updateCharacteristic(this.hap.Characteristic.Active, this.hap.Characteristic.Active.INACTIVE) + await this.debugLog(`updateCharacteristic Active: ${this.On}`) } } - await this.debugLog(`Faucet On: ${this.On}`); + await this.debugLog(`Faucet On: ${this.On}`) } else if (this.otherDeviceType === 'fan' && this.Fan) { if (this.On === undefined) { - await this.debugLog(`On: ${this.On}`); + await this.debugLog(`On: ${this.On}`) } else { if (this.On) { - this.Fan.Service.updateCharacteristic(this.hap.Characteristic.Active, this.hap.Characteristic.Active.ACTIVE); - await this.debugLog(`updateCharacteristic Active: ${this.On}`); + this.Fan.Service.updateCharacteristic(this.hap.Characteristic.Active, this.hap.Characteristic.Active.ACTIVE) + await this.debugLog(`updateCharacteristic Active: ${this.On}`) } else { - this.Fan.Service.updateCharacteristic(this.hap.Characteristic.Active, this.hap.Characteristic.Active.INACTIVE); - await this.debugLog(`updateCharacteristic Active: ${this.On}`); + this.Fan.Service.updateCharacteristic(this.hap.Characteristic.Active, this.hap.Characteristic.Active.INACTIVE) + await this.debugLog(`updateCharacteristic Active: ${this.On}`) } } - await this.debugLog(`Fan On: ${this.On}`); + await this.debugLog(`Fan On: ${this.On}`) } else if (this.otherDeviceType === 'stateful' && this.StatefulProgrammableSwitch) { if (this.On === undefined) { - await this.debugLog(`On: ${this.On}`); + await this.debugLog(`On: ${this.On}`) } else { if (this.On) { - this.StatefulProgrammableSwitch.Service.updateCharacteristic(this.hap.Characteristic.ProgrammableSwitchEvent, - this.hap.Characteristic.ProgrammableSwitchEvent.SINGLE_PRESS); - this.StatefulProgrammableSwitch.Service.updateCharacteristic(this.hap.Characteristic.ProgrammableSwitchOutputState, 1); - await this.debugLog(`updateCharacteristic ProgrammableSwitchEvent: ProgrammableSwitchOutputState: (${this.On})`); + this.StatefulProgrammableSwitch.Service.updateCharacteristic(this.hap.Characteristic.ProgrammableSwitchEvent, this.hap.Characteristic.ProgrammableSwitchEvent.SINGLE_PRESS) + this.StatefulProgrammableSwitch.Service.updateCharacteristic(this.hap.Characteristic.ProgrammableSwitchOutputState, 1) + await this.debugLog(`updateCharacteristic ProgrammableSwitchEvent: ProgrammableSwitchOutputState: (${this.On})`) } else { - this.StatefulProgrammableSwitch.Service.updateCharacteristic(this.hap.Characteristic.ProgrammableSwitchEvent, - this.hap.Characteristic.ProgrammableSwitchEvent.SINGLE_PRESS); - this.StatefulProgrammableSwitch.Service.updateCharacteristic(this.hap.Characteristic.ProgrammableSwitchOutputState, 0); - await this.debugLog(`updateCharacteristic ProgrammableSwitchEvent: ProgrammableSwitchOutputState: (${this.On})`); + this.StatefulProgrammableSwitch.Service.updateCharacteristic(this.hap.Characteristic.ProgrammableSwitchEvent, this.hap.Characteristic.ProgrammableSwitchEvent.SINGLE_PRESS) + this.StatefulProgrammableSwitch.Service.updateCharacteristic(this.hap.Characteristic.ProgrammableSwitchOutputState, 0) + await this.debugLog(`updateCharacteristic ProgrammableSwitchEvent: ProgrammableSwitchOutputState: (${this.On})`) } } - await this.debugLog(`StatefulProgrammableSwitch On: ${this.On}`); + await this.debugLog(`StatefulProgrammableSwitch On: ${this.On}`) } else if (this.otherDeviceType === 'outlet' && this.Outlet) { if (this.On === undefined) { - await this.debugLog(`On: ${this.On}`); + await this.debugLog(`On: ${this.On}`) } else { - this.Outlet.Service.updateCharacteristic(this.hap.Characteristic.On, this.On); - await this.debugLog(`updateCharacteristic On: ${this.On}`); + this.Outlet.Service.updateCharacteristic(this.hap.Characteristic.On, this.On) + await this.debugLog(`updateCharacteristic On: ${this.On}`) } } else { - await this.errorLog(`otherDeviceType: ${this.otherDeviceType}, On: ${this.On}`); + await this.errorLog(`otherDeviceType: ${this.otherDeviceType}, On: ${this.On}`) } } async apiError(e: any): Promise { if (this.otherDeviceType === 'garagedoor') { if (this.GarageDoor) { - this.GarageDoor.Service.updateCharacteristic(this.hap.Characteristic.TargetDoorState, e); - this.GarageDoor.Service.updateCharacteristic(this.hap.Characteristic.CurrentDoorState, e); - this.GarageDoor.Service.updateCharacteristic(this.hap.Characteristic.ObstructionDetected, e); + this.GarageDoor.Service.updateCharacteristic(this.hap.Characteristic.TargetDoorState, e) + this.GarageDoor.Service.updateCharacteristic(this.hap.Characteristic.CurrentDoorState, e) + this.GarageDoor.Service.updateCharacteristic(this.hap.Characteristic.ObstructionDetected, e) } } else if (this.otherDeviceType === 'door') { if (this.Door) { - this.Door.Service.updateCharacteristic(this.hap.Characteristic.TargetPosition, e); - this.Door.Service.updateCharacteristic(this.hap.Characteristic.CurrentPosition, e); - this.Door.Service.updateCharacteristic(this.hap.Characteristic.PositionState, e); + this.Door.Service.updateCharacteristic(this.hap.Characteristic.TargetPosition, e) + this.Door.Service.updateCharacteristic(this.hap.Characteristic.CurrentPosition, e) + this.Door.Service.updateCharacteristic(this.hap.Characteristic.PositionState, e) } } else if (this.otherDeviceType === 'window') { if (this.Window) { - this.Window.Service.updateCharacteristic(this.hap.Characteristic.TargetPosition, e); - this.Window.Service.updateCharacteristic(this.hap.Characteristic.CurrentPosition, e); - this.Window.Service.updateCharacteristic(this.hap.Characteristic.PositionState, e); + this.Window.Service.updateCharacteristic(this.hap.Characteristic.TargetPosition, e) + this.Window.Service.updateCharacteristic(this.hap.Characteristic.CurrentPosition, e) + this.Window.Service.updateCharacteristic(this.hap.Characteristic.PositionState, e) } } else if (this.otherDeviceType === 'windowcovering') { if (this.WindowCovering) { - this.WindowCovering.Service.updateCharacteristic(this.hap.Characteristic.TargetPosition, e); - this.WindowCovering.Service.updateCharacteristic(this.hap.Characteristic.CurrentPosition, e); - this.WindowCovering.Service.updateCharacteristic(this.hap.Characteristic.PositionState, e); + this.WindowCovering.Service.updateCharacteristic(this.hap.Characteristic.TargetPosition, e) + this.WindowCovering.Service.updateCharacteristic(this.hap.Characteristic.CurrentPosition, e) + this.WindowCovering.Service.updateCharacteristic(this.hap.Characteristic.PositionState, e) } } else if (this.otherDeviceType === 'lock') { if (this.LockMechanism) { - this.LockMechanism.Service.updateCharacteristic(this.hap.Characteristic.LockTargetState, e); - this.LockMechanism.Service.updateCharacteristic(this.hap.Characteristic.LockCurrentState, e); + this.LockMechanism.Service.updateCharacteristic(this.hap.Characteristic.LockTargetState, e) + this.LockMechanism.Service.updateCharacteristic(this.hap.Characteristic.LockCurrentState, e) } } else if (this.otherDeviceType === 'faucet') { if (this.Faucet) { - this.Faucet.Service.updateCharacteristic(this.hap.Characteristic.Active, e); + this.Faucet.Service.updateCharacteristic(this.hap.Characteristic.Active, e) } } else if (this.otherDeviceType === 'fan') { if (this.Fan) { - this.Fan.Service.updateCharacteristic(this.hap.Characteristic.On, e); + this.Fan.Service.updateCharacteristic(this.hap.Characteristic.On, e) } } else if (this.otherDeviceType === 'stateful') { if (this.StatefulProgrammableSwitch) { - this.StatefulProgrammableSwitch.Service.updateCharacteristic(this.hap.Characteristic.ProgrammableSwitchEvent, e); - this.StatefulProgrammableSwitch.Service.updateCharacteristic(this.hap.Characteristic.ProgrammableSwitchOutputState, e); + this.StatefulProgrammableSwitch.Service.updateCharacteristic(this.hap.Characteristic.ProgrammableSwitchEvent, e) + this.StatefulProgrammableSwitch.Service.updateCharacteristic(this.hap.Characteristic.ProgrammableSwitchOutputState, e) } } else if (this.otherDeviceType === 'switch') { if (this.Switch) { - this.Switch.Service.updateCharacteristic(this.hap.Characteristic.On, e); + this.Switch.Service.updateCharacteristic(this.hap.Characteristic.On, e) } } else { if (this.Outlet) { - this.Outlet.Service.updateCharacteristic(this.hap.Characteristic.On, e); + this.Outlet.Service.updateCharacteristic(this.hap.Characteristic.On, e) } } } async removeOutletService(accessory: PlatformAccessory): Promise { // If Outlet.Service still present, then remove first - accessory.context.Outlet = accessory.context.Outlet ?? {}; + accessory.context.Outlet = accessory.context.Outlet ?? {} this.Outlet = { Name: accessory.displayName, Service: accessory.getService(this.hap.Service.Outlet) as Service, - }; - accessory.context.Outlet = this.Outlet as object; - this.warnLog('Removing any leftover Outlet Service'); - accessory.removeService(this.Outlet.Service); + } + accessory.context.Outlet = this.Outlet as object + this.warnLog('Removing any leftover Outlet Service') + accessory.removeService(this.Outlet.Service) } async removeGarageDoorService(accessory: PlatformAccessory): Promise { // If GarageDoor.Service still present, then remove first - accessory.context.GarageDoor = accessory.context.GarageDoor ?? {}; + accessory.context.GarageDoor = accessory.context.GarageDoor ?? {} this.GarageDoor = { Name: accessory.displayName, Service: accessory.getService(this.hap.Service.GarageDoorOpener) as Service, - }; - accessory.context.GarageDoor = this.GarageDoor as object; - this.warnLog('Removing any leftover Garage Door Service'); - accessory.removeService(this.GarageDoor.Service); + } + accessory.context.GarageDoor = this.GarageDoor as object + this.warnLog('Removing any leftover Garage Door Service') + accessory.removeService(this.GarageDoor.Service) } async removeDoorService(accessory: PlatformAccessory): Promise { // If Door.Service still present, then remove first - accessory.context.Door = accessory.context.Door ?? {}; + accessory.context.Door = accessory.context.Door ?? {} this.Door = { Name: accessory.displayName, Service: accessory.getService(this.hap.Service.Door) as Service, - }; - accessory.context.Door = this.Door as object; - this.warnLog('Removing any leftover Door Service'); - accessory.removeService(this.Door.Service); + } + accessory.context.Door = this.Door as object + this.warnLog('Removing any leftover Door Service') + accessory.removeService(this.Door.Service) } async removeLockService(accessory: PlatformAccessory): Promise { // If Lock.Service still present, then remove first - accessory.context.LockMechanism = accessory.context.LockMechanism ?? {}; + accessory.context.LockMechanism = accessory.context.LockMechanism ?? {} this.LockMechanism = { Name: accessory.displayName, Service: accessory.getService(this.hap.Service.LockMechanism) as Service, - }; - accessory.context.LockMechanism = this.LockMechanism as object; - this.warnLog('Removing any leftover Lock Service'); - accessory.removeService(this.LockMechanism.Service); + } + accessory.context.LockMechanism = this.LockMechanism as object + this.warnLog('Removing any leftover Lock Service') + accessory.removeService(this.LockMechanism.Service) } async removeFaucetService(accessory: PlatformAccessory): Promise { // If Faucet.Service still present, then remove first - accessory.context.Faucet = accessory.context.Faucet ?? {}; + accessory.context.Faucet = accessory.context.Faucet ?? {} this.Faucet = { Name: accessory.displayName, Service: accessory.getService(this.hap.Service.Faucet) as Service, - }; - accessory.context.Faucet = this.Faucet as object; - this.warnLog('Removing any leftover Faucet Service'); - accessory.removeService(this.Faucet.Service); + } + accessory.context.Faucet = this.Faucet as object + this.warnLog('Removing any leftover Faucet Service') + accessory.removeService(this.Faucet.Service) } async removeFanService(accessory: PlatformAccessory): Promise { // If Fan Service still present, then remove first - accessory.context.Fan = accessory.context.Fan ?? {}; + accessory.context.Fan = accessory.context.Fan ?? {} this.Fan = { Name: accessory.displayName, Service: accessory.getService(this.hap.Service.Fanv2) as Service, - }; - accessory.context.Fan = this.Fan as object; - this.warnLog('Removing any leftover Fan Service'); - accessory.removeService(this.Fan.Service); + } + accessory.context.Fan = this.Fan as object + this.warnLog('Removing any leftover Fan Service') + accessory.removeService(this.Fan.Service) } async removeWindowService(accessory: PlatformAccessory): Promise { // If Window.Service still present, then remove first - accessory.context.Window = accessory.context.Window ?? {}; + accessory.context.Window = accessory.context.Window ?? {} this.Window = { Name: accessory.displayName, Service: accessory.getService(this.hap.Service.Window) as Service, - }; - accessory.context.Window = this.Window as object; - this.warnLog('Removing any leftover Window Service'); - accessory.removeService(this.Window.Service); + } + accessory.context.Window = this.Window as object + this.warnLog('Removing any leftover Window Service') + accessory.removeService(this.Window.Service) } async removeWindowCoveringService(accessory: PlatformAccessory): Promise { // If WindowCovering.Service still present, then remove first - accessory.context.WindowCovering = accessory.context.WindowCovering ?? {}; + accessory.context.WindowCovering = accessory.context.WindowCovering ?? {} this.WindowCovering = { Name: accessory.displayName, Service: accessory.getService(this.hap.Service.WindowCovering) as Service, - }; - accessory.context.WindowCovering = this.WindowCovering as object; - this.warnLog('Removing any leftover Window Covering Service'); - accessory.removeService(this.WindowCovering.Service); + } + accessory.context.WindowCovering = this.WindowCovering as object + this.warnLog('Removing any leftover Window Covering Service') + accessory.removeService(this.WindowCovering.Service) } async removeStatefulProgrammableSwitchService(accessory: PlatformAccessory): Promise { // If StatefulProgrammableSwitch.Service still present, then remove first - accessory.context.StatefulProgrammableSwitch = accessory.context.StatefulProgrammableSwitch ?? {}; + accessory.context.StatefulProgrammableSwitch = accessory.context.StatefulProgrammableSwitch ?? {} this.StatefulProgrammableSwitch = { Name: accessory.displayName, Service: accessory.getService(this.hap.Service.StatefulProgrammableSwitch) as Service, - }; - accessory.context.StatefulProgrammableSwitch = this.StatefulProgrammableSwitch as object; - this.warnLog('Removing any leftover Stateful Programmable Switch Service'); - accessory.removeService(this.StatefulProgrammableSwitch.Service); + } + accessory.context.StatefulProgrammableSwitch = this.StatefulProgrammableSwitch as object + this.warnLog('Removing any leftover Stateful Programmable Switch Service') + accessory.removeService(this.StatefulProgrammableSwitch.Service) } async removeSwitchService(accessory: PlatformAccessory): Promise { // If Switch.Service still present, then remove first - accessory.context.Switch = accessory.context.Switch ?? {}; + accessory.context.Switch = accessory.context.Switch ?? {} this.Switch = { Name: accessory.displayName, Service: accessory.getService(this.hap.Service.Switch) as Service, - }; - accessory.context.Switch = this.Switch as object; - this.warnLog('Removing any leftover Switch Service'); - accessory.removeService(this.Switch.Service); + } + accessory.context.Switch = this.Switch as object + this.warnLog('Removing any leftover Switch Service') + accessory.removeService(this.Switch.Service) } async getOtherConfigSettings(accessory: PlatformAccessory, device: irdevice & irDevicesConfig): Promise { - this.otherDeviceType = accessory.context.otherDeviceType ? accessory.context.otherDeviceType - : device.other?.deviceType ? device.other.deviceType : 'outlet'; - const deviceType = accessory.context.otherDeviceType ? 'Accessory Cache': device.other?.deviceType ? 'Device Config' : 'Default'; - await this.debugLog(`Use ${deviceType} Type: ${this.otherDeviceType}`); + this.otherDeviceType = accessory.context.otherDeviceType + ? accessory.context.otherDeviceType + : device.other?.deviceType ? device.other.deviceType : 'outlet' + const deviceType = accessory.context.otherDeviceType ? 'Accessory Cache' : device.other?.deviceType ? 'Device Config' : 'Default' + await this.debugLog(`Use ${deviceType} Type: ${this.otherDeviceType}`) } } diff --git a/src/irdevice/tv.ts b/src/irdevice/tv.ts index ea5678d4..f3177852 100644 --- a/src/irdevice/tv.ts +++ b/src/irdevice/tv.ts @@ -2,12 +2,13 @@ * * tv.ts: @switchbot/homebridge-switchbot. */ -import { irdeviceBase } from './irdevice.js'; +import type { CharacteristicValue, PlatformAccessory, Service } from 'homebridge' -import type { SwitchBotPlatform } from '../platform.js'; -import type { irDevicesConfig } from '../settings.js'; -import type { irdevice } from '../types/irdevicelist.js'; -import type { CharacteristicValue, PlatformAccessory, Service } from 'homebridge'; +import type { SwitchBotPlatform } from '../platform.js' +import type { irDevicesConfig } from '../settings.js' +import type { irdevice } from '../types/irdevicelist.js' + +import { irdeviceBase } from './irdevice.js' /** * Platform Accessory @@ -17,22 +18,22 @@ import type { CharacteristicValue, PlatformAccessory, Service } from 'homebridge export class TV extends irdeviceBase { // Services private Television: { - Name: CharacteristicValue; - ConfiguredName: CharacteristicValue; - Service: Service; - Active: CharacteristicValue; - ActiveIdentifier: CharacteristicValue; - SleepDiscoveryMode: CharacteristicValue; - RemoteKey: CharacteristicValue; - }; + Name: CharacteristicValue + ConfiguredName: CharacteristicValue + Service: Service + Active: CharacteristicValue + ActiveIdentifier: CharacteristicValue + SleepDiscoveryMode: CharacteristicValue + RemoteKey: CharacteristicValue + } private TelevisionSpeaker: { - Name: CharacteristicValue; - Service: Service; - Active: CharacteristicValue; - VolumeControlType: CharacteristicValue; - VolumeSelector: CharacteristicValue; - }; + Name: CharacteristicValue + Service: Service + Active: CharacteristicValue + VolumeControlType: CharacteristicValue + VolumeSelector: CharacteristicValue + } // Characteristic Values @@ -41,10 +42,10 @@ export class TV extends irdeviceBase { accessory: PlatformAccessory, device: irdevice & irDevicesConfig, ) { - super(platform, accessory, device); + super(platform, accessory, device) // Initialize Television Service - accessory.context.Television = accessory.context.Television ?? {}; + accessory.context.Television = accessory.context.Television ?? {} this.Television = { Name: accessory.displayName, ConfiguredName: accessory.displayName, @@ -53,350 +54,327 @@ export class TV extends irdeviceBase { ActiveIdentifier: accessory.context.ActiveIdentifier ?? 1, SleepDiscoveryMode: accessory.context.SleepDiscoveryMode ?? this.hap.Characteristic.SleepDiscoveryMode.ALWAYS_DISCOVERABLE, RemoteKey: accessory.context.RemoteKey ?? this.hap.Characteristic.RemoteKey.EXIT, - }; - accessory.context.Television = this.Television as object; + } + accessory.context.Television = this.Television as object switch (device.remoteType) { case 'Speaker': case 'DIY Speaker': - accessory.category = this.hap.Categories.SPEAKER; - break; + accessory.category = this.hap.Categories.SPEAKER + break case 'IPTV': case 'DIY IPTV': - accessory.category = this.hap.Categories.TV_STREAMING_STICK; - break; + accessory.category = this.hap.Categories.TV_STREAMING_STICK + break case 'DVD': case 'DIY DVD': case 'Set Top Box': case 'DIY Set Top Box': - accessory.category = this.hap.Categories.TV_SET_TOP_BOX; - break; + accessory.category = this.hap.Categories.TV_SET_TOP_BOX + break default: - accessory.category = this.hap.Categories.TELEVISION; + accessory.category = this.hap.Categories.TELEVISION } - this.Television.Service - .setCharacteristic(this.hap.Characteristic.SleepDiscoveryMode, this.hap.Characteristic.SleepDiscoveryMode.ALWAYS_DISCOVERABLE) - .setCharacteristic(this.hap.Characteristic.ConfiguredName, this.Television.ConfiguredName) - .getCharacteristic(this.hap.Characteristic.ConfiguredName); + this.Television.Service.setCharacteristic(this.hap.Characteristic.SleepDiscoveryMode, this.hap.Characteristic.SleepDiscoveryMode.ALWAYS_DISCOVERABLE).setCharacteristic(this.hap.Characteristic.ConfiguredName, this.Television.ConfiguredName).getCharacteristic(this.hap.Characteristic.ConfiguredName) - this.Television.Service - .setCharacteristic(this.hap.Characteristic.ActiveIdentifier, 1) - .getCharacteristic(this.hap.Characteristic.Active) - .onGet(() => { - return this.Television.Active; - }) - .onSet(this.ActiveSet.bind(this)); + this.Television.Service.setCharacteristic(this.hap.Characteristic.ActiveIdentifier, 1).getCharacteristic(this.hap.Characteristic.Active).onGet(() => { + return this.Television.Active + }).onSet(this.ActiveSet.bind(this)) - this.Television.Service - .getCharacteristic(this.hap.Characteristic.ActiveIdentifier) - .onGet(() => { - return this.Television.ActiveIdentifier; - }) - .onSet(this.ActiveIdentifierSet.bind(this)); + this.Television.Service.getCharacteristic(this.hap.Characteristic.ActiveIdentifier).onGet(() => { + return this.Television.ActiveIdentifier + }).onSet(this.ActiveIdentifierSet.bind(this)) - this.Television.Service - .getCharacteristic(this.hap.Characteristic.RemoteKey) - .onGet(() => { - return this.Television.RemoteKey; - }) - .onSet(this.RemoteKeySet.bind(this)); + this.Television.Service.getCharacteristic(this.hap.Characteristic.RemoteKey).onGet(() => { + return this.Television.RemoteKey + }).onSet(this.RemoteKeySet.bind(this)) // Initialize TelevisionSpeaker Service - accessory.context.TelevisionSpeaker = accessory.context.TelevisionSpeaker ?? {}; + accessory.context.TelevisionSpeaker = accessory.context.TelevisionSpeaker ?? {} this.TelevisionSpeaker = { Name: `${accessory.displayName} Speaker`, Service: accessory.getService(this.hap.Service.TelevisionSpeaker) ?? accessory.addService(this.hap.Service.TelevisionSpeaker) as Service, Active: accessory.context.Active ?? false, VolumeControlType: accessory.context.VolumeControlType ?? this.hap.Characteristic.VolumeControlType.ABSOLUTE, VolumeSelector: accessory.context.VolumeSelector ?? this.hap.Characteristic.VolumeSelector.INCREMENT, - }; - accessory.context.TelevisionSpeaker = this.TelevisionSpeaker as object; - - this.TelevisionSpeaker.Service - .setCharacteristic(this.hap.Characteristic.Name, this.TelevisionSpeaker.Name) - .setCharacteristic(this.hap.Characteristic.Active, this.hap.Characteristic.Active.ACTIVE) - .setCharacteristic(this.hap.Characteristic.VolumeControlType, this.hap.Characteristic.VolumeControlType.ABSOLUTE) - .getCharacteristic(this.hap.Characteristic.VolumeSelector) - .onGet(() => { - return this.TelevisionSpeaker.VolumeSelector; - }) - .onSet(this.VolumeSelectorSet.bind(this)); + } + accessory.context.TelevisionSpeaker = this.TelevisionSpeaker as object + + this.TelevisionSpeaker.Service.setCharacteristic(this.hap.Characteristic.Name, this.TelevisionSpeaker.Name).setCharacteristic(this.hap.Characteristic.Active, this.hap.Characteristic.Active.ACTIVE).setCharacteristic(this.hap.Characteristic.VolumeControlType, this.hap.Characteristic.VolumeControlType.ABSOLUTE).getCharacteristic(this.hap.Characteristic.VolumeSelector).onGet(() => { + return this.TelevisionSpeaker.VolumeSelector + }).onSet(this.VolumeSelectorSet.bind(this)) } async VolumeSelectorSet(value: CharacteristicValue): Promise { - await this.debugLog(`VolumeSelector: ${value}`); + await this.debugLog(`VolumeSelector: ${value}`) if (value === this.hap.Characteristic.VolumeSelector.INCREMENT) { - this.pushVolumeUpChanges(); + this.pushVolumeUpChanges() } else { - this.pushVolumeDownChanges(); + this.pushVolumeDownChanges() } } async RemoteKeySet(value: CharacteristicValue): Promise { switch (value) { case this.hap.Characteristic.RemoteKey.REWIND: { - await this.debugLog('Set Remote Key Pressed: REWIND'); - break; + await this.debugLog('Set Remote Key Pressed: REWIND') + break } case this.hap.Characteristic.RemoteKey.FAST_FORWARD: { - await this.debugLog('Set Remote Key Pressed: FAST_FORWARD'); - break; + await this.debugLog('Set Remote Key Pressed: FAST_FORWARD') + break } case this.hap.Characteristic.RemoteKey.NEXT_TRACK: { - await this.debugLog('Set Remote Key Pressed: NEXT_TRACK'); - break; + await this.debugLog('Set Remote Key Pressed: NEXT_TRACK') + break } case this.hap.Characteristic.RemoteKey.PREVIOUS_TRACK: { - await this.debugLog('Set Remote Key Pressed: PREVIOUS_TRACK'); - break; + await this.debugLog('Set Remote Key Pressed: PREVIOUS_TRACK') + break } case this.hap.Characteristic.RemoteKey.ARROW_UP: { - await this.debugLog('Set Remote Key Pressed: ARROW_UP'); - //this.pushUpChanges(); - break; + await this.debugLog('Set Remote Key Pressed: ARROW_UP') + // this.pushUpChanges(); + break } case this.hap.Characteristic.RemoteKey.ARROW_DOWN: { - await this.debugLog('Set Remote Key Pressed: ARROW_DOWN'); - //this.pushDownChanges(); - break; + await this.debugLog('Set Remote Key Pressed: ARROW_DOWN') + // this.pushDownChanges(); + break } case this.hap.Characteristic.RemoteKey.ARROW_LEFT: { - await this.debugLog('Set Remote Key Pressed: ARROW_LEFT'); - //this.pushLeftChanges(); - break; + await this.debugLog('Set Remote Key Pressed: ARROW_LEFT') + // this.pushLeftChanges(); + break } case this.hap.Characteristic.RemoteKey.ARROW_RIGHT: { - await this.debugLog('Set Remote Key Pressed: ARROW_RIGHT'); - //this.pushRightChanges(); - break; + await this.debugLog('Set Remote Key Pressed: ARROW_RIGHT') + // this.pushRightChanges(); + break } case this.hap.Characteristic.RemoteKey.SELECT: { - await this.debugLog('Set Remote Key Pressed: SELECT'); - //this.pushOkChanges(); - break; + await this.debugLog('Set Remote Key Pressed: SELECT') + // this.pushOkChanges(); + break } case this.hap.Characteristic.RemoteKey.BACK: { - await this.debugLog('Set Remote Key Pressed: BACK'); - //this.pushBackChanges(); - break; + await this.debugLog('Set Remote Key Pressed: BACK') + // this.pushBackChanges(); + break } case this.hap.Characteristic.RemoteKey.EXIT: { - await this.debugLog('Set Remote Key Pressed: EXIT'); - break; + await this.debugLog('Set Remote Key Pressed: EXIT') + break } case this.hap.Characteristic.RemoteKey.PLAY_PAUSE: { - await this.debugLog('Set Remote Key Pressed: PLAY_PAUSE'); - break; + await this.debugLog('Set Remote Key Pressed: PLAY_PAUSE') + break } case this.hap.Characteristic.RemoteKey.INFORMATION: { - await this.debugLog('Set Remote Key Pressed: INFORMATION'); - //this.pushMenuChanges(); - break; + await this.debugLog('Set Remote Key Pressed: INFORMATION') + // this.pushMenuChanges(); + break } } } async ActiveIdentifierSet(value: CharacteristicValue): Promise { - await this.debugLog(`ActiveIdentifier: ${value}`); - this.Television.ActiveIdentifier = value; + await this.debugLog(`ActiveIdentifier: ${value}`) + this.Television.ActiveIdentifier = value } async ActiveSet(value: CharacteristicValue): Promise { - await this.debugLog(`Active (value): ${value}`); + await this.debugLog(`Active (value): ${value}`) - this.Television.Active = value; + this.Television.Active = value if (this.Television.Active === this.hap.Characteristic.Active.ACTIVE) { - await this.pushTvOnChanges(); + await this.pushTvOnChanges() } else { - await this.pushTvOffChanges(); + await this.pushTvOffChanges() } } /** * Pushes the requested changes to the SwitchBot API - * deviceType commandType Command Parameter Description - * TV "command" "turnOff" "default" set to OFF state - * TV "command" "turnOn" "default" set to ON state - * TV "command" "volumeAdd" "default" volume up - * TV "command" "volumeSub" "default" volume down - * TV "command" "channelAdd" "default" next channel - * TV "command" "channelSub" "default" previous channel + * deviceType commandType Command Parameter Description + * TV "command" "turnOff" "default" set to OFF state + * TV "command" "turnOn" "default" set to ON state + * TV "command" "volumeAdd" "default" volume up + * TV "command" "volumeSub" "default" volume down + * TV "command" "channelAdd" "default" next channel + * TV "command" "channelSub" "default" previous channel */ async pushTvOnChanges(): Promise { - await this.debugLog('pushTvOnChanges' - + ` Active: ${this.Television.Active}, disablePushOn: ${this.disablePushOn}`); + await this.debugLog(`pushTvOnChanges Active: ${this.Television.Active}, disablePushOn: ${this.disablePushOn}`) if (this.Television.Active === this.hap.Characteristic.Active.ACTIVE && !this.disablePushOn) { - const commandType: string = await this.commandType(); - const command: string = await this.commandOn(); + const commandType: string = await this.commandType() + const command: string = await this.commandOn() const bodyChange = JSON.stringify({ - command: command, + command, parameter: 'default', - commandType: commandType, - }); - await this.pushTVChanges(bodyChange); + commandType, + }) + await this.pushTVChanges(bodyChange) } } async pushTvOffChanges(): Promise { - await this.debugLog('pushTvOffChanges' - + ` Active: ${this.Television.Active}, disablePushOff: ${this.disablePushOff}`); + await this.debugLog(`pushTvOffChanges Active: ${this.Television.Active}, disablePushOff: ${this.disablePushOff}`) if (this.Television.Active === this.hap.Characteristic.Active.INACTIVE && !this.disablePushOff) { - const commandType: string = await this.commandType(); - const command: string = await this.commandOff(); + const commandType: string = await this.commandType() + const command: string = await this.commandOff() const bodyChange = JSON.stringify({ - command: command, + command, parameter: 'default', - commandType: commandType, - }); - await this.pushTVChanges(bodyChange); + commandType, + }) + await this.pushTVChanges(bodyChange) } } async pushOkChanges(): Promise { - await this.debugLog(`pushOkChanges disablePushDetail: ${this.disablePushDetail}`); + await this.debugLog(`pushOkChanges disablePushDetail: ${this.disablePushDetail}`) if (!this.disablePushDetail) { const bodyChange = JSON.stringify({ command: 'Ok', parameter: 'default', commandType: 'command', - }); - await this.pushTVChanges(bodyChange); + }) + await this.pushTVChanges(bodyChange) } } async pushBackChanges(): Promise { - await this.debugLog(`pushBackChanges disablePushDetail: ${this.disablePushDetail}`); + await this.debugLog(`pushBackChanges disablePushDetail: ${this.disablePushDetail}`) if (!this.disablePushDetail) { const bodyChange = JSON.stringify({ command: 'Back', parameter: 'default', commandType: 'command', - }); - await this.pushTVChanges(bodyChange); + }) + await this.pushTVChanges(bodyChange) } } async pushMenuChanges(): Promise { - await this.debugLog(`pushMenuChanges disablePushDetail: ${this.disablePushDetail}`); + await this.debugLog(`pushMenuChanges disablePushDetail: ${this.disablePushDetail}`) if (!this.disablePushDetail) { const bodyChange = JSON.stringify({ command: 'Menu', parameter: 'default', commandType: 'command', - }); - await this.pushTVChanges(bodyChange); + }) + await this.pushTVChanges(bodyChange) } } async pushUpChanges(): Promise { - await this.debugLog(`pushUpChanges disablePushDetail: ${this.disablePushDetail}`); + await this.debugLog(`pushUpChanges disablePushDetail: ${this.disablePushDetail}`) if (!this.disablePushDetail) { const bodyChange = JSON.stringify({ command: 'Up', parameter: 'default', commandType: 'command', - }); - await this.pushTVChanges(bodyChange); + }) + await this.pushTVChanges(bodyChange) } } async pushDownChanges(): Promise { - await this.debugLog(`pushDownChanges disablePushDetail: ${this.disablePushDetail}`); + await this.debugLog(`pushDownChanges disablePushDetail: ${this.disablePushDetail}`) if (!this.disablePushDetail) { const bodyChange = JSON.stringify({ command: 'Down', parameter: 'default', commandType: 'command', - }); - await this.pushTVChanges(bodyChange); + }) + await this.pushTVChanges(bodyChange) } } async pushRightChanges(): Promise { - await this.debugLog(`pushRightChanges disablePushDetail: ${this.disablePushDetail}`); + await this.debugLog(`pushRightChanges disablePushDetail: ${this.disablePushDetail}`) if (!this.disablePushDetail) { const bodyChange = JSON.stringify({ command: 'Right', parameter: 'default', commandType: 'command', - }); - await this.pushTVChanges(bodyChange); + }) + await this.pushTVChanges(bodyChange) } } async pushLeftChanges(): Promise { - await this.debugLog(`pushLeftChanges disablePushDetail: ${this.disablePushDetail}`); + await this.debugLog(`pushLeftChanges disablePushDetail: ${this.disablePushDetail}`) if (!this.disablePushDetail) { const bodyChange = JSON.stringify({ command: 'Left', parameter: 'default', commandType: 'command', - }); - await this.pushTVChanges(bodyChange); + }) + await this.pushTVChanges(bodyChange) } } async pushVolumeUpChanges(): Promise { - await this.debugLog(`pushVolumeUpChanges disablePushDetail: ${this.disablePushDetail}`); + await this.debugLog(`pushVolumeUpChanges disablePushDetail: ${this.disablePushDetail}`) if (!this.disablePushDetail) { const bodyChange = JSON.stringify({ command: 'volumeAdd', parameter: 'default', commandType: 'command', - }); - await this.pushTVChanges(bodyChange); + }) + await this.pushTVChanges(bodyChange) } } async pushVolumeDownChanges(): Promise { - await this.debugLog(`pushVolumeDownChanges disablePushDetail: ${this.disablePushDetail}`); + await this.debugLog(`pushVolumeDownChanges disablePushDetail: ${this.disablePushDetail}`) if (!this.disablePushDetail) { const bodyChange = JSON.stringify({ command: 'volumeSub', parameter: 'default', commandType: 'command', - }); - await this.pushTVChanges(bodyChange); + }) + await this.pushTVChanges(bodyChange) } } async pushTVChanges(bodyChange: any): Promise { - await this.debugLog('pushTVChanges'); + await this.debugLog('pushTVChanges') if (this.device.connectionType === 'OpenAPI') { - await this.infoLog(`Sending request to SwitchBot API, body: ${bodyChange},`); + await this.infoLog(`Sending request to SwitchBot API, body: ${bodyChange},`) try { - const { body, statusCode } = await this.pushChangeRequest(bodyChange); - const deviceStatus: any = await body.json(); - await this.pushStatusCodes(statusCode, deviceStatus); + const { body, statusCode } = await this.pushChangeRequest(bodyChange) + const deviceStatus: any = await body.json() + await this.pushStatusCodes(statusCode, deviceStatus) if (await this.successfulStatusCodes(statusCode, deviceStatus)) { - await this.successfulPushChange(statusCode, deviceStatus, bodyChange); - await this.updateHomeKitCharacteristics(); + await this.successfulPushChange(statusCode, deviceStatus, bodyChange) + await this.updateHomeKitCharacteristics() } else { - await this.statusCode(statusCode); - await this.statusCode(deviceStatus.statusCode); + await this.statusCode(statusCode) + await this.statusCode(deviceStatus.statusCode) } } catch (e: any) { - await this.apiError(e); - await this.pushChangeError(e); + await this.apiError(e) + await this.pushChangeError(e) } } else { - await this.warnLog(`Connection Type: ${this.device.connectionType}, commands will not be sent to OpenAPI`); + await this.warnLog(`Connection Type: ${this.device.connectionType}, commands will not be sent to OpenAPI`) } } async updateHomeKitCharacteristics(): Promise { - await this.debugLog('updateHomeKitCharacteristics'); + await this.debugLog('updateHomeKitCharacteristics') // Active - await this.updateCharacteristic(this.Television.Service, this.hap.Characteristic.Active, - this.Television.Active, 'Active'); + await this.updateCharacteristic(this.Television.Service, this.hap.Characteristic.Active, this.Television.Active, 'Active') // ActiveIdentifier - await this.updateCharacteristic(this.Television.Service, this.hap.Characteristic.ActiveIdentifier, - this.Television.ActiveIdentifier, 'ActiveIdentifier'); + await this.updateCharacteristic(this.Television.Service, this.hap.Characteristic.ActiveIdentifier, this.Television.ActiveIdentifier, 'ActiveIdentifier') } async apiError(e: any): Promise { - this.Television.Service.updateCharacteristic(this.hap.Characteristic.Active, e); - this.Television.Service.updateCharacteristic(this.hap.Characteristic.ActiveIdentifier, e); + this.Television.Service.updateCharacteristic(this.hap.Characteristic.Active, e) + this.Television.Service.updateCharacteristic(this.hap.Characteristic.ActiveIdentifier, e) } } diff --git a/src/irdevice/vacuumcleaner.ts b/src/irdevice/vacuumcleaner.ts index 03b4495f..f8cacb62 100644 --- a/src/irdevice/vacuumcleaner.ts +++ b/src/irdevice/vacuumcleaner.ts @@ -2,12 +2,13 @@ * * vacuumcleaner.ts: @switchbot/homebridge-switchbot. */ -import { irdeviceBase } from './irdevice.js'; +import type { CharacteristicValue, PlatformAccessory, Service } from 'homebridge' -import type { SwitchBotPlatform } from '../platform.js'; -import type { irDevicesConfig } from '../settings.js'; -import type { irdevice } from '../types/irdevicelist.js'; -import type { CharacteristicValue, PlatformAccessory, Service } from 'homebridge'; +import type { SwitchBotPlatform } from '../platform.js' +import type { irDevicesConfig } from '../settings.js' +import type { irdevice } from '../types/irdevicelist.js' + +import { irdeviceBase } from './irdevice.js' /** * Platform Accessory @@ -17,118 +18,111 @@ import type { CharacteristicValue, PlatformAccessory, Service } from 'homebridge export class VacuumCleaner extends irdeviceBase { // Services private Switch: { - Name: CharacteristicValue; - Service: Service; - On: CharacteristicValue; - }; + Name: CharacteristicValue + Service: Service + On: CharacteristicValue + } constructor( readonly platform: SwitchBotPlatform, accessory: PlatformAccessory, device: irdevice & irDevicesConfig, ) { - super(platform, accessory, device); + super(platform, accessory, device) // Set category - accessory.category = this.hap.Categories.OTHER; + accessory.category = this.hap.Categories.OTHER // Initialize Switch Service - accessory.context.Switch = accessory.context.Switch ?? {}; + accessory.context.Switch = accessory.context.Switch ?? {} this.Switch = { Name: accessory.displayName, Service: accessory.getService(this.hap.Service.Switch) ?? accessory.addService(this.hap.Service.Switch) as Service, On: accessory.context.On ?? false, - }; - accessory.context.Switch = this.Switch as object; + } + accessory.context.Switch = this.Switch as object - this.Switch.Service - .setCharacteristic(this.hap.Characteristic.Name, this.Switch.Name) - .getCharacteristic(this.hap.Characteristic.On) - .onGet(() => { - return this.Switch.On; - }) - .onSet(this.OnSet.bind(this)); + this.Switch.Service.setCharacteristic(this.hap.Characteristic.Name, this.Switch.Name).getCharacteristic(this.hap.Characteristic.On).onGet(() => { + return this.Switch.On + }).onSet(this.OnSet.bind(this)) } async OnSet(value: CharacteristicValue): Promise { - await this.debugLog(`On: ${value}`); + await this.debugLog(`On: ${value}`) // Set the requested state - this.Switch.On = value; + this.Switch.On = value if (this.Switch.On) { - await this.pushOnChanges(); + await this.pushOnChanges() } else { - await this.pushOffChanges(); + await this.pushOffChanges() } } /** * Pushes the requested changes to the SwitchBot API - * deviceType CommandType Command Parameter Description - * Vacuum Cleaner "command" "turnOff" "default" set to OFF state - * Vacuum Cleaner "command" "turnOn" "default" set to ON state + * deviceType CommandType Command Parameter Description + * Vacuum Cleaner "command" "turnOff" "default" set to OFF state + * Vacuum Cleaner "command" "turnOn" "default" set to ON state */ async pushOnChanges(): Promise { - await this.debugLog('pushOnChanges' - + ` On: ${this.Switch.On}, disablePushOn: ${this.disablePushOn}`); + await this.debugLog(`pushOnChanges On: ${this.Switch.On}, disablePushOn: ${this.disablePushOn}`) if (this.Switch.On && !this.disablePushOn) { - const commandType: string = await this.commandType(); - const command: string = await this.commandOn(); + const commandType: string = await this.commandType() + const command: string = await this.commandOn() const bodyChange = JSON.stringify({ - command: command, + command, parameter: 'default', - commandType: commandType, - }); - await this.pushChanges(bodyChange); + commandType, + }) + await this.pushChanges(bodyChange) } } async pushOffChanges(): Promise { - await this.debugLog('pushOffChanges' - + ` On: ${this.Switch.On}, disablePushOff: ${this.disablePushOff}`); + await this.debugLog(`pushOffChanges On: ${this.Switch.On}, disablePushOff: ${this.disablePushOff}`) if (!this.Switch.On && !this.disablePushOff) { - const commandType: string = await this.commandType(); - const command: string = await this.commandOff(); + const commandType: string = await this.commandType() + const command: string = await this.commandOff() const bodyChange = JSON.stringify({ - command: command, + command, parameter: 'default', - commandType: commandType, - }); - await this.pushChanges(bodyChange); + commandType, + }) + await this.pushChanges(bodyChange) } } async pushChanges(bodyChange: any): Promise { - await this.debugLog('pushChanges'); + await this.debugLog('pushChanges') if (this.device.connectionType === 'OpenAPI') { - await this.infoLog(`Sending request to SwitchBot API, body: ${bodyChange},`); + await this.infoLog(`Sending request to SwitchBot API, body: ${bodyChange},`) try { - const { body, statusCode } = await this.pushChangeRequest(bodyChange); - const deviceStatus: any = await body.json(); - await this.pushStatusCodes(statusCode, deviceStatus); + const { body, statusCode } = await this.pushChangeRequest(bodyChange) + const deviceStatus: any = await body.json() + await this.pushStatusCodes(statusCode, deviceStatus) if (await this.successfulStatusCodes(statusCode, deviceStatus)) { - await this.successfulPushChange(statusCode, deviceStatus, bodyChange); - await this.updateHomeKitCharacteristics(); + await this.successfulPushChange(statusCode, deviceStatus, bodyChange) + await this.updateHomeKitCharacteristics() } else { - await this.statusCode(statusCode); - await this.statusCode(deviceStatus.statusCode); + await this.statusCode(statusCode) + await this.statusCode(deviceStatus.statusCode) } } catch (e: any) { - await this.apiError(e); - await this.pushChangeError(e); + await this.apiError(e) + await this.pushChangeError(e) } } else { - await this.warnLog(`Connection Type: ${this.device.connectionType}, commands will not be sent to OpenAPI`); + await this.warnLog(`Connection Type: ${this.device.connectionType}, commands will not be sent to OpenAPI`) } } async updateHomeKitCharacteristics(): Promise { - await this.debugLog('updateHomeKitCharacteristics'); + await this.debugLog('updateHomeKitCharacteristics') // On - await this.updateCharacteristic(this.Switch.Service, this.hap.Characteristic.On, - this.Switch.On, 'On'); + await this.updateCharacteristic(this.Switch.Service, this.hap.Characteristic.On, this.Switch.On, 'On') } async apiError(e: any): Promise { - this.Switch.Service.updateCharacteristic(this.hap.Characteristic.On, e); + this.Switch.Service.updateCharacteristic(this.hap.Characteristic.On, e) } } diff --git a/src/irdevice/waterheater.ts b/src/irdevice/waterheater.ts index b98a3cd3..4a4132f4 100644 --- a/src/irdevice/waterheater.ts +++ b/src/irdevice/waterheater.ts @@ -2,12 +2,13 @@ * * waterheater.ts: @switchbot/homebridge-switchbot. */ -import { irdeviceBase } from './irdevice.js'; +import type { CharacteristicValue, PlatformAccessory, Service } from 'homebridge' -import type { SwitchBotPlatform } from '../platform.js'; -import type { irDevicesConfig } from '../settings.js'; -import type { irdevice } from '../types/irdevicelist.js'; -import type { CharacteristicValue, PlatformAccessory, Service } from 'homebridge'; +import type { SwitchBotPlatform } from '../platform.js' +import type { irDevicesConfig } from '../settings.js' +import type { irdevice } from '../types/irdevicelist.js' + +import { irdeviceBase } from './irdevice.js' /** * Platform Accessory @@ -17,120 +18,112 @@ import type { CharacteristicValue, PlatformAccessory, Service } from 'homebridge export class WaterHeater extends irdeviceBase { // Services private Valve: { - Name: CharacteristicValue; - Service: Service; - Active: CharacteristicValue; - }; + Name: CharacteristicValue + Service: Service + Active: CharacteristicValue + } constructor( readonly platform: SwitchBotPlatform, accessory: PlatformAccessory, device: irdevice & irDevicesConfig, ) { - super(platform, accessory, device); + super(platform, accessory, device) // Set category - accessory.category = this.hap.Categories.FAUCET; + accessory.category = this.hap.Categories.FAUCET // Initialize Switch Service - accessory.context.Valve = accessory.context.Valve ?? {}; + accessory.context.Valve = accessory.context.Valve ?? {} this.Valve = { Name: accessory.displayName, Service: accessory.getService(this.hap.Service.Valve) ?? accessory.addService(this.hap.Service.Valve) as Service, Active: accessory.context.Active ?? this.hap.Characteristic.Active.INACTIVE, - }; - accessory.context.Valve = this.Valve as object; + } + accessory.context.Valve = this.Valve as object - this.Valve.Service - .setCharacteristic(this.hap.Characteristic.Name, this.Valve.Name) - .setCharacteristic(this.hap.Characteristic.ValveType, this.hap.Characteristic.ValveType.GENERIC_VALVE) - .getCharacteristic(this.hap.Characteristic.Active) - .onGet(() => { - return this.Valve.Active; - }) - .onSet(this.ActiveSet.bind(this)); + this.Valve.Service.setCharacteristic(this.hap.Characteristic.Name, this.Valve.Name).setCharacteristic(this.hap.Characteristic.ValveType, this.hap.Characteristic.ValveType.GENERIC_VALVE).getCharacteristic(this.hap.Characteristic.Active).onGet(() => { + return this.Valve.Active + }).onSet(this.ActiveSet.bind(this)) } async ActiveSet(value: CharacteristicValue): Promise { - await this.debugLog(`Active: ${value}`); + await this.debugLog(`Active: ${value}`) - this.Valve.Active = value; + this.Valve.Active = value if (this.Valve.Active === this.hap.Characteristic.Active.ACTIVE) { - await this.pushWaterHeaterOnChanges(); - this.Valve.Service.setCharacteristic(this.hap.Characteristic.InUse, this.hap.Characteristic.InUse.IN_USE); + await this.pushWaterHeaterOnChanges() + this.Valve.Service.setCharacteristic(this.hap.Characteristic.InUse, this.hap.Characteristic.InUse.IN_USE) } else { - await this.pushWaterHeaterOffChanges(); - this.Valve.Service.setCharacteristic(this.hap.Characteristic.InUse, this.hap.Characteristic.InUse.NOT_IN_USE); + await this.pushWaterHeaterOffChanges() + this.Valve.Service.setCharacteristic(this.hap.Characteristic.InUse, this.hap.Characteristic.InUse.NOT_IN_USE) } } /** * Pushes the requested changes to the SwitchBot API - * deviceType Command Type Command Parameter Description - * WaterHeater "command" "turnOff" "default" set to OFF state - * WaterHeater "command" "turnOn" "default" set to ON state + * deviceType Command Type Command Parameter Description + * WaterHeater "command" "turnOff" "default" set to OFF state + * WaterHeater "command" "turnOn" "default" set to ON state */ async pushWaterHeaterOnChanges(): Promise { - await this.debugLog(`pushWaterHeaterOnChanges Active: ${this.Valve.Active},` - + ` disablePushOn: ${this.disablePushOn}`); + await this.debugLog(`pushWaterHeaterOnChanges Active: ${this.Valve.Active}, disablePushOn: ${this.disablePushOn}`) if (this.Valve.Active === this.hap.Characteristic.Active.ACTIVE && !this.disablePushOn) { - const commandType: string = await this.commandType(); - const command: string = await this.commandOn(); + const commandType: string = await this.commandType() + const command: string = await this.commandOn() const bodyChange = JSON.stringify({ - command: command, + command, parameter: 'default', - commandType: commandType, - }); - await this.pushChanges(bodyChange); + commandType, + }) + await this.pushChanges(bodyChange) } } async pushWaterHeaterOffChanges(): Promise { - await this.debugLog(`pushWaterHeaterOffChanges Active: ${this.Valve.Active},` - + ` disablePushOff: ${this.disablePushOff}`); + await this.debugLog(`pushWaterHeaterOffChanges Active: ${this.Valve.Active}, disablePushOff: ${this.disablePushOff}`) if (this.Valve.Active === this.hap.Characteristic.Active.INACTIVE && !this.disablePushOff) { - const commandType: string = await this.commandType(); - const command: string = await this.commandOff(); + const commandType: string = await this.commandType() + const command: string = await this.commandOff() const bodyChange = JSON.stringify({ - command: command, + command, parameter: 'default', - commandType: commandType, - }); - await this.pushChanges(bodyChange); + commandType, + }) + await this.pushChanges(bodyChange) } } async pushChanges(bodyChange: any): Promise { - await this.debugLog('pushChanges'); + await this.debugLog('pushChanges') if (this.device.connectionType === 'OpenAPI') { - this.infoLog(`Sending request to SwitchBot API, body: ${bodyChange},`); + this.infoLog(`Sending request to SwitchBot API, body: ${bodyChange},`) try { - const { body, statusCode } = await this.pushChangeRequest(bodyChange); - const deviceStatus: any = await body.json(); - await this.pushStatusCodes(statusCode, deviceStatus); + const { body, statusCode } = await this.pushChangeRequest(bodyChange) + const deviceStatus: any = await body.json() + await this.pushStatusCodes(statusCode, deviceStatus) if (await this.successfulStatusCodes(statusCode, deviceStatus)) { - await this.successfulPushChange(statusCode, deviceStatus, bodyChange); - await this.updateHomeKitCharacteristics(); + await this.successfulPushChange(statusCode, deviceStatus, bodyChange) + await this.updateHomeKitCharacteristics() } else { - await this.statusCode(statusCode); - await this.statusCode(deviceStatus.statusCode); + await this.statusCode(statusCode) + await this.statusCode(deviceStatus.statusCode) } } catch (e: any) { - await this.apiError(e); - await this.pushChangeError(e); + await this.apiError(e) + await this.pushChangeError(e) } } else { - await this.warnLog(`Connection Type: ${this.device.connectionType}, commands will not be sent to OpenAPI`); + await this.warnLog(`Connection Type: ${this.device.connectionType}, commands will not be sent to OpenAPI`) } } async updateHomeKitCharacteristics(): Promise { - await this.debugLog('updateHomeKitCharacteristics'); + await this.debugLog('updateHomeKitCharacteristics') // Active - await this.updateCharacteristic(this.Valve.Service, this.hap.Characteristic.Active, - this.Valve.Active, 'Active'); + await this.updateCharacteristic(this.Valve.Service, this.hap.Characteristic.Active, this.Valve.Active, 'Active') } async apiError({ e }: { e: any }): Promise { - this.Valve.Service.updateCharacteristic(this.hap.Characteristic.Active, e); + this.Valve.Service.updateCharacteristic(this.hap.Characteristic.Active, e) } } diff --git a/src/platform.ts b/src/platform.ts index 37462649..297061a6 100644 --- a/src/platform.ts +++ b/src/platform.ts @@ -2,56 +2,63 @@ * * platform.ts: @switchbot/homebridge-switchbot platform class. */ -import { Hub } from './device/hub.js'; -import { Bot } from './device/bot.js'; -import { Plug } from './device/plug.js'; -import { Lock } from './device/lock.js'; -import { Meter } from './device/meter.js'; -import { Motion } from './device/motion.js'; -import { Contact } from './device/contact.js'; -import { Curtain } from './device/curtain.js'; -import { IOSensor } from './device/iosensor.js'; -import { MeterPlus } from './device/meterplus.js'; -import { ColorBulb } from './device/colorbulb.js'; -import { StripLight } from './device/lightstrip.js'; -import { Humidifier } from './device/humidifier.js'; -import { CeilingLight } from './device/ceilinglight.js'; -import { WaterDetector } from './device/waterdetector.js'; -import { RobotVacuumCleaner } from './device/robotvacuumcleaner.js'; - -import { Fan } from './device/fan.js'; -import { TV } from './irdevice/tv.js'; -import { IRFan } from './irdevice/fan.js'; -import { Light } from './irdevice/light.js'; -import { Others } from './irdevice/other.js'; -import { Camera } from './irdevice/camera.js'; -import { BlindTilt } from './device/blindtilt.js'; -import { AirPurifier } from './irdevice/airpurifier.js'; -import { WaterHeater } from './irdevice/waterheater.js'; -import { VacuumCleaner } from './irdevice/vacuumcleaner.js'; -import { AirConditioner } from './irdevice/airconditioner.js'; - -import { Buffer } from 'buffer'; -import { request } from 'undici'; -import asyncmqtt from 'async-mqtt'; -import { createServer } from 'http'; -import { queueScheduler } from 'rxjs'; -import fakegato from 'fakegato-history'; -import crypto, { randomUUID } from 'crypto'; -import { readFileSync, writeFileSync } from 'fs'; -import { EveHomeKitTypes } from 'homebridge-lib/EveHomeKitTypes'; -import { PLATFORM_NAME, PLUGIN_NAME, Devices, deleteWebhook, queryWebhook, setupWebhook, updateWebhook } from './settings.js'; -import { isBlindTiltDevice, isCurtainDevice, sleep } from './utils.js'; - -import type { UrlObject } from 'url'; -import type { MqttClient } from 'mqtt'; -import type { Dispatcher } from 'undici'; -import type { irdevice } from './types/irdevicelist.js'; -import type { blindTilt, curtain, curtain3, device } from './types/devicelist.js'; -import type { API, DynamicPlatformPlugin, Logging, PlatformAccessory } from 'homebridge'; -import type { SwitchBotPlatformConfig, devicesConfig, irDevicesConfig } from './settings.js'; -import type { IncomingMessage, Server, ServerResponse } from 'http'; -import { SwitchBotModel } from 'node-switchbot'; +import type { IncomingMessage, Server, ServerResponse } from 'node:http' +import type { UrlObject } from 'node:url' + +import type { API, DynamicPlatformPlugin, Logging, PlatformAccessory } from 'homebridge' +import type { MqttClient } from 'mqtt' +import type { Dispatcher } from 'undici' + +import type { devicesConfig, irDevicesConfig, options, SwitchBotPlatformConfig } from './settings.js' +import type { blindTilt, curtain, curtain3, device } from './types/devicelist.js' +import type { irdevice } from './types/irdevicelist.js' + +import { Buffer } from 'node:buffer' +import crypto, { randomUUID } from 'node:crypto' +import { readFileSync, writeFileSync } from 'node:fs' +import { createServer } from 'node:http' +import process from 'node:process' + +import asyncmqtt from 'async-mqtt' +import fakegato from 'fakegato-history' +import { EveHomeKitTypes } from 'homebridge-lib/EveHomeKitTypes' +/* +* For Testing Locally: +* import { SwitchBotModel } from '/Users/Shared/GitHub/OpenWonderLabs/node-switchbot/dist/index.js'; +*/ +import { SwitchBot, SwitchBotModel } from 'node-switchbot' +import { queueScheduler } from 'rxjs' +import { request } from 'undici' + +import { BlindTilt } from './device/blindtilt.js' +import { Bot } from './device/bot.js' +import { CeilingLight } from './device/ceilinglight.js' +import { ColorBulb } from './device/colorbulb.js' +import { Contact } from './device/contact.js' +import { Curtain } from './device/curtain.js' +import { Fan } from './device/fan.js' +import { Hub } from './device/hub.js' +import { Humidifier } from './device/humidifier.js' +import { IOSensor } from './device/iosensor.js' +import { StripLight } from './device/lightstrip.js' +import { Lock } from './device/lock.js' +import { Meter } from './device/meter.js' +import { MeterPlus } from './device/meterplus.js' +import { Motion } from './device/motion.js' +import { Plug } from './device/plug.js' +import { RobotVacuumCleaner } from './device/robotvacuumcleaner.js' +import { WaterDetector } from './device/waterdetector.js' +import { AirConditioner } from './irdevice/airconditioner.js' +import { AirPurifier } from './irdevice/airpurifier.js' +import { Camera } from './irdevice/camera.js' +import { IRFan } from './irdevice/fan.js' +import { Light } from './irdevice/light.js' +import { Others } from './irdevice/other.js' +import { TV } from './irdevice/tv.js' +import { VacuumCleaner } from './irdevice/vacuumcleaner.js' +import { WaterHeater } from './irdevice/waterheater.js' +import { deleteWebhook, Devices, PLATFORM_NAME, PLUGIN_NAME, queryWebhook, setupWebhook, updateWebhook } from './settings.js' +import { isBlindTiltDevice, isCurtainDevice, sleep } from './utils.js' /** * HomebridgePlatform @@ -59,40 +66,44 @@ import { SwitchBotModel } from 'node-switchbot'; * parse the user config and discover/register accessories with Homebridge. */ export class SwitchBotPlatform implements DynamicPlatformPlugin { - public accessories: PlatformAccessory[]; - public readonly api: API; - public readonly log: Logging; - - version!: string; - Logging?: string; - debugMode!: boolean; - maxRetries!: number; - delayBetweenRetries!: number; - platformConfig!: SwitchBotPlatformConfig['options']; - platformLogging!: SwitchBotPlatformConfig['logging']; - config!: SwitchBotPlatformConfig; - - mqttClient: MqttClient | null = null; - webhookEventListener: Server | null = null; - - public readonly eve: any; - public readonly fakegatoAPI: any; - public readonly webhookEventHandler: { [x: string]: (context: any) => void } = {}; - public readonly bleEventHandler: { [x: string]: (context: any) => void } = {}; + // Platform properties + public accessories: PlatformAccessory[] = [] + public readonly api: API + public readonly log: Logging + + // Configuration properties + version!: string + Logging?: string + debugMode!: boolean + maxRetries!: number + delayBetweenRetries!: number + platformConfig!: SwitchBotPlatformConfig['options'] + platformLogging!: SwitchBotPlatformConfig['logging'] + config!: SwitchBotPlatformConfig + + // MQTT and Webhook properties + mqttClient: MqttClient | null = null + webhookEventListener: Server | null = null + + // External APIs + public readonly eve: any + public readonly fakegatoAPI: any + + // Event Handlers + public readonly webhookEventHandler: { [x: string]: (context: any) => void } = {} + public readonly bleEventHandler: { [x: string]: (context: any) => void } = {} constructor( log: Logging, config: SwitchBotPlatformConfig, api: API, ) { - // initialize - this.accessories = []; - this.api = api; - this.log = log; + this.api = api + this.log = log // only load if configured if (!config) { - return; + return } // Plugin options into our config variables. @@ -101,130 +112,138 @@ export class SwitchBotPlatform implements DynamicPlatformPlugin { name: config.name, credentials: config.credentials as object, options: config.options as object, - }; + } // Plugin Configuration - this.getPlatformLogSettings(); - this.getPlatformConfigSettings(); - this.getVersion(); + this.getPlatformLogSettings() + this.getPlatformConfigSettings() + this.getVersion() // Finish initializing the platform - this.debugLog(`Finished initializing platform: ${config.name}`); + this.debugLog(`Finished initializing platform: ${config.name}`) // verify the config try { - this.verifyConfig(); - this.debugLog('Config OK'); + this.verifyConfig() + this.debugLog('Config OK') } catch (e: any) { - this.errorLog(`Verify Config, Error Message: ${e.message}, Submit Bugs Here: ` + 'https://tinyurl.com/SwitchBotBug'); - this.debugErrorLog(`Verify Config, Error: ${e}`); - return; + this.errorLog(`Verify Config, Error Message: ${e.message}, Submit Bugs Here: ` + 'https://tinyurl.com/SwitchBotBug') + this.debugErrorLog(`Verify Config, Error: ${e}`) + return } // import fakegato-history module and EVE characteristics - this.fakegatoAPI = fakegato(api); - this.eve = new EveHomeKitTypes(api); + this.fakegatoAPI = fakegato(api) + this.eve = new EveHomeKitTypes(api) // 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 // to start discovery of new accessories. this.api.on('didFinishLaunching', async () => { - this.debugLog('Executed didFinishLaunching callback'); + await this.debugLog('Executed didFinishLaunching callback') // run the method to discover / register your devices as accessories try { if (this.config.credentials?.openToken && !this.config.credentials.token) { - await this.updateToken(); + await this.updateToken() } else if (this.config.credentials?.token && !this.config.credentials?.secret) { - - this.errorLog('"secret" config is not populated, you must populate then please restart Homebridge.'); + await this.errorLog('"secret" config is not populated, you must populate then please restart Homebridge.') } else { - this.discoverDevices(); + await this.discoverDevices() } } catch (e: any) { - this.errorLog(`Failed to Discover, Error Message: ${e.message}, Submit Bugs Here: ` + 'https://tinyurl.com/SwitchBotBug'); - await this.debugErrorLog(`Failed to Discover, Error: ${e}`); + await this.errorLog(`Failed to Discover, Error Message: ${e.message}, Submit Bugs Here: ` + 'https://tinyurl.com/SwitchBotBug') + await this.debugErrorLog(`Failed to Discover, Error: ${e}`) } - }); + }) - this.setupMqtt(); - this.setupwebhook(); - this.setupBlE(); + try { + this.setupMqtt() + } catch (e: any) { + this.errorLog(`Setup MQTT, Error Message: ${e.message}, Submit Bugs Here: ` + 'https://tinyurl.com/SwitchBotBug') + } + try { + this.setupwebhook() + } catch (e: any) { + this.errorLog(`Setup Webhook, Error Message: ${e.message}, Submit Bugs Here: ` + 'https://tinyurl.com/SwitchBotBug') + } + try { + this.setupBlE() + } catch (e: any) { + this.errorLog(`Setup Platform BLE, Error Message: ${e.message}, Submit Bugs Here: ` + 'https://tinyurl.com/SwitchBotBug') + } } async setupMqtt(): Promise { if (this.config.options?.mqttURL) { try { - const { connectAsync } = asyncmqtt; - this.mqttClient = await connectAsync(this.config.options?.mqttURL, this.config.options.mqttOptions || {}); - await this.debugLog('MQTT connection has been established successfully.'); + const { connectAsync } = asyncmqtt + this.mqttClient = await connectAsync(this.config.options?.mqttURL, this.config.options.mqttOptions || {}) + await this.debugLog('MQTT connection has been established successfully.') this.mqttClient.on('error', async (e: Error) => { - await this.errorLog(`Failed to publish MQTT messages. ${e}`); - }); + await this.errorLog(`Failed to publish MQTT messages. ${e}`) + }) if (!this.config.options?.webhookURL) { // receive webhook events via MQTT - await this.infoLog(`Webhook is configured to be received through ${this.config.options.mqttURL}/homebridge-switchbot/webhook.`); - this.mqttClient.subscribe('homebridge-switchbot/webhook/+'); + await this.infoLog(`Webhook is configured to be received through ${this.config.options.mqttURL}/homebridge-switchbot/webhook.`) + this.mqttClient.subscribe('homebridge-switchbot/webhook/+') this.mqttClient.on('message', async (topic: string, message) => { try { - await await this.debugLog(`Received Webhook via MQTT: ${topic}=${message}`); - const context = JSON.parse(message.toString()); - this.webhookEventHandler[context.deviceMac]?.(context); + await this.debugLog(`Received Webhook via MQTT: ${topic}=${message}`) + const context = JSON.parse(message.toString()) + this.webhookEventHandler[context.deviceMac]?.(context) } catch (e: any) { - await this.errorLog(`Failed to handle webhook event. Error:${e}`); + await this.errorLog(`Failed to handle webhook event. Error:${e}`) } - }); + }) } } catch (e) { - this.mqttClient = null; - await this.errorLog(`Failed to establish MQTT connection. ${e}`); + this.mqttClient = null + await this.errorLog(`Failed to establish MQTT connection. ${e}`) } } } async setupwebhook() { - //webhook configuration + // webhook configuration if (this.config.options?.webhookURL) { - const url = this.config.options?.webhookURL; + const url = this.config.options?.webhookURL try { - const xurl = new URL(url); - const port = Number(xurl.port); - const path = xurl.pathname; + const xurl = new URL(url) + const port = Number(xurl.port) + const path = xurl.pathname this.webhookEventListener = createServer((request: IncomingMessage, response: ServerResponse) => { try { if (request.url === path && request.method === 'POST') { request.on('data', async (data) => { try { - const body = JSON.parse(data); - await this.debugLog(`Received Webhook: ${JSON.stringify(body)}`); + const body = JSON.parse(data) + await this.debugLog(`Received Webhook: ${JSON.stringify(body)}`) if (this.config.options?.mqttURL) { - const mac = body.context.deviceMac - ?.toLowerCase() - .match(/[\s\S]{1,2}/g) - ?.join(':'); - const options = this.config.options?.mqttPubOptions || {}; - this.mqttClient?.publish(`homebridge-switchbot/webhook/${mac}`, `${JSON.stringify(body.context)}`, options); + const mac = body.context.deviceMac?.toLowerCase().match(/[\s\S]{1,2}/g)?.join(':') + const options = this.config.options?.mqttPubOptions || {} + this.mqttClient?.publish(`homebridge-switchbot/webhook/${mac}`, `${JSON.stringify(body.context)}`, options) } - this.webhookEventHandler[body.context.deviceMac]?.(body.context); + this.webhookEventHandler[body.context.deviceMac]?.(body.context) } catch (e: any) { - this.errorLog(`Failed to handle webhook event. Error:${e}`); + await this.errorLog(`Failed to handle webhook event. Error:${e}`) } - }); - response.writeHead(200, { 'Content-Type': 'text/plain' }); - response.end('OK'); + }) + response.writeHead(200, { 'Content-Type': 'text/plain' }) + response.end('OK') } // else { // response.writeHead(403, {'Content-Type': 'text/plain'}); // response.end(`NG`); // } } catch (e: any) { - this.errorLog(`Failed to handle webhook event. Error:${e}`); + this.errorLog(`Failed to handle webhook event. Error:${e}`) } - }).listen(port ? port : 80); + }).listen(port || 80) } catch (e: any) { - this.errorLog(`Failed to create webhook listener. Error:${e.message}`); - return; + await this.errorLog(`Failed to create webhook listener. Error:${e.message}`) + return } try { @@ -232,42 +251,39 @@ export class SwitchBotPlatform implements DynamicPlatformPlugin { method: 'POST', headers: this.generateHeaders(), body: JSON.stringify({ - 'action': 'setupWebhook', - 'url': url, - 'deviceList': 'ALL', + action: 'setupWebhook', + url, + deviceList: 'ALL', }), - }); - const response: any = await body.json(); - await this.debugLog(`setupWebhook: url:${url}`); - await this.debugLog(`setupWebhook: body:${JSON.stringify(response)}`); - await this.debugLog(`setupWebhook: statusCode:${statusCode}`); + }) + const response: any = await body.json() + await this.debugLog(`setupWebhook: url:${url}, body:${JSON.stringify(response)}, statusCode:${statusCode}`) if (statusCode !== 200 || response?.statusCode !== 100) { - this.errorLog(`Failed to configure webhook. Existing webhook well be overridden. HTTP:${statusCode} API:${response?.statusCode} ` - + `message:${response?.message}`); + await this.errorLog(`Failed to configure webhook. Existing webhook well be overridden. HTTP:${statusCode} API:${response?.statusCode} message:${response?.message}`) } } catch (e: any) { - this.errorLog(`Failed to configure webhook. Error: ${e.message}`); + await this.errorLog(`Failed to configure webhook. Error: ${e.message}`) } try { const { body, statusCode } = await request(updateWebhook, { - method: 'POST', headers: this.generateHeaders(), body: JSON.stringify({ - 'action': 'updateWebhook', - 'config': { - 'url': url, - 'enable': true, + method: 'POST', + headers: this.generateHeaders(), + body: JSON.stringify({ + action: 'updateWebhook', + config: { + url, + enable: true, }, }), - }); - const response: any = await body.json(); - await this.debugLog(`updateWebhook: url:${url}`); - await this.debugLog(`updateWebhook: body:${JSON.stringify(response)}`); - await this.debugLog(`updateWebhook: statusCode:${statusCode}`); + }) + const response: any = await body.json() + await this.debugLog(`updateWebhook: url:${url}, body:${JSON.stringify(response)}, statusCode:${statusCode}`) if (statusCode !== 200 || response?.statusCode !== 100) { - this.errorLog(`Failed to update webhook. HTTP:${statusCode} API:${response?.statusCode} message:${response?.message}`); + await this.errorLog(`Failed to update webhook. HTTP:${statusCode} API:${response?.statusCode} message:${response?.message}`) } } catch (e: any) { - this.errorLog(`Failed to update webhook. Error:${e.message}`); + await this.errorLog(`Failed to update webhook. Error:${e.message}`) } try { @@ -275,19 +291,19 @@ export class SwitchBotPlatform implements DynamicPlatformPlugin { method: 'POST', headers: this.generateHeaders(), body: JSON.stringify({ - 'action': 'queryUrl', + action: 'queryUrl', }), - }); - const response: any = await body.json(); - await this.debugLog(`queryWebhook: body:${JSON.stringify(response)}`); - await this.debugLog(`queryWebhook: statusCode:${statusCode}`); + }) + const response: any = await body.json() + await this.debugLog(`queryWebhook: body:${JSON.stringify(response)}`) + await this.debugLog(`queryWebhook: statusCode:${statusCode}`) if (statusCode !== 200 || response?.statusCode !== 100) { - this.errorLog(`Failed to query webhook. HTTP:${statusCode} API:${response?.statusCode} message:${response?.message}`); + await this.errorLog(`Failed to query webhook. HTTP:${statusCode} API:${response?.statusCode} message:${response?.message}`) } else { - this.infoLog(`Listening webhook on ${response?.body?.urls[0]}`); + await this.infoLog(`Listening webhook on ${response?.body?.urls[0]}`) } } catch (e: any) { - this.errorLog(`Failed to query webhook. Error:${e}`); + await this.errorLog(`Failed to query webhook. Error:${e}`) } this.api.on('shutdown', async () => { @@ -296,58 +312,57 @@ export class SwitchBotPlatform implements DynamicPlatformPlugin { method: 'POST', headers: this.generateHeaders(), body: JSON.stringify({ - 'action': 'deleteWebhook', - 'url': url, + action: 'deleteWebhook', + url, }), - }); - const response: any = await body.json(); - await this.debugLog(`deleteWebhook: url:${url}, body:${JSON.stringify(response)}, statusCode:${statusCode}`); + }) + const response: any = await body.json() + await this.debugLog(`deleteWebhook: url:${url}, body:${JSON.stringify(response)}, statusCode:${statusCode}`) if (statusCode !== 200 || response?.statusCode !== 100) { - this.errorLog(`Failed to delete webhook. HTTP:${statusCode} API:${response?.statusCode} message:${response?.message}`); + await this.errorLog(`Failed to delete webhook. HTTP:${statusCode} API:${response?.statusCode} message:${response?.message}`) } else { - this.infoLog('Unregistered webhook to close listening.'); + await this.infoLog('Unregistered webhook to close listening.') } } catch (e: any) { - this.errorLog(`Failed to delete webhook. Error:${e.message}`); + await this.errorLog(`Failed to delete webhook. Error:${e.message}`) } - }); + }) } } async setupBlE() { if (this.config.options?.BLE) { - await this.debugLog('setupBLE'); - const SwitchBot = (await import('node-switchbot')).SwitchBot; - const switchbot = new SwitchBot(); + await this.debugLog('setupBLE') + const switchbot = new SwitchBot() if (switchbot === undefined) { - await this.errorLog(`wasn't able to establish BLE Connection, node-switchbot: ${switchbot}`); + await this.errorLog(`wasn't able to establish BLE Connection, node-switchbot: ${switchbot}`) } else { // Start to monitor advertisement packets (async () => { // Start to monitor advertisement packets - await this.debugLog('Scanning for BLE SwitchBot devices...'); - await switchbot.startScan(); + await this.debugLog('Scanning for BLE SwitchBot devices...') + await switchbot.startScan() // Set an event handler to monitor advertisement packets switchbot.onadvertisement = async (ad: any) => { try { - this.bleEventHandler[ad.address]?.(ad.serviceData); + this.bleEventHandler[ad.address]?.(ad.serviceData) } catch (e: any) { - await this.errorLog(`Failed to handle BLE event. Error:${e}`); + await this.errorLog(`Failed to handle BLE event. Error:${e}`) } - }; - })(); + } + })() this.api.on('shutdown', async () => { try { - switchbot.stopScan(); - this.infoLog('Stopped BLE scanning to close listening.'); + switchbot.stopScan() + await this.infoLog('Stopped BLE scanning to close listening.') } catch (e: any) { - this.errorLog(`Failed to stop Platform BLE scanning. Error:${e.message}`); + await this.errorLog(`Failed to stop Platform BLE scanning. Error:${e.message}`) } - }); + }) } } else { - await this.debugLog('Platform BLE is not enabled'); + await this.debugLog('Platform BLE is not enabled') } } @@ -356,32 +371,33 @@ export class SwitchBotPlatform implements DynamicPlatformPlugin { * It should be used to setup event handlers for characteristics and update respective values. */ async configureAccessory(accessory: PlatformAccessory) { - await this.debugLog(`Loading accessory from cache: ${accessory.displayName}`); + const { displayName } = accessory + await this.debugLog(`Loading accessory from cache: ${displayName}`) // add the restored accessory to the accessories cache so we can track if it has already been registered - this.accessories.push(accessory); + this.accessories.push(accessory) } /** * Verify the config passed to the plugin is valid */ async verifyConfig() { - this.debugLog('Verifying Config'); - this.config = this.config || {}; - this.config.options = this.config.options || {}; + await this.debugLog('Verifying Config') + this.config = this.config || {} + this.config.options = this.config.options || {} - const platformConfig = {}; + const platformConfig: options = {} if (this.config.options.logging) { - platformConfig['logging'] = this.config.options.logging; + platformConfig.logging = this.config.options.logging } if (this.config.options.logging && this.config.options.refreshRate) { - platformConfig['refreshRate'] = this.config.options.refreshRate; + platformConfig.refreshRate = this.config.options.refreshRate } if (this.config.options.logging && this.config.options.pushRate) { - platformConfig['pushRate'] = this.config.options.pushRate; + platformConfig.pushRate = this.config.options.pushRate } if (Object.entries(platformConfig).length !== 0) { - await this.debugWarnLog(`Platform Config: ${JSON.stringify(platformConfig)}`); + await this.debugWarnLog(`Platform Config: ${JSON.stringify(platformConfig)}`) } if (this.config.options) { @@ -390,10 +406,10 @@ export class SwitchBotPlatform implements DynamicPlatformPlugin { for (const deviceConfig of this.config.options.devices) { if (!deviceConfig.hide_device) { if (!deviceConfig.deviceId) { - throw new Error('The devices config section is missing the *Device ID* in the config. Please check your config.'); + throw new Error('The devices config section is missing the *Device ID* in the config. Please check your config.') } if (!deviceConfig.configDeviceType && deviceConfig.connectionType) { - throw new Error('The devices config section is missing the *Device Type* in the config. Please check your config.'); + throw new Error('The devices config section is missing the *Device Type* in the config. Please check your config.') } } } @@ -404,10 +420,10 @@ export class SwitchBotPlatform implements DynamicPlatformPlugin { for (const irDeviceConfig of this.config.options.irdevices) { if (!irDeviceConfig.hide_device) { if (!irDeviceConfig.deviceId) { - this.errorLog('The devices config section is missing the *Device ID* in the config. Please check your config.'); + await this.errorLog('The devices config section is missing the *Device ID* in the config. Please check your config.') } if (!irDeviceConfig.deviceId && !irDeviceConfig.configRemoteType) { - this.errorLog('The devices config section is missing the *Device Type* in the config. Please check your config.'); + await this.errorLog('The devices config section is missing the *Device Type* in the config. Please check your config.') } } } @@ -415,47 +431,47 @@ export class SwitchBotPlatform implements DynamicPlatformPlugin { } if (this.config.options!.refreshRate! < 5) { - throw new Error('Refresh Rate must be above 5 (5 seconds).'); + throw new Error('Refresh Rate must be above 5 (5 seconds).') } if (!this.config.options.refreshRate) { // default 120 seconds (2 minutes) - this.config.options!.refreshRate! = 120; - this.debugWarnLog('Using Default Refresh Rate (2 minutes).'); + this.config.options!.refreshRate! = 120 + await this.debugWarnLog('Using Default Refresh Rate (2 minutes).') } if (!this.config.options.pushRate) { // default 100 milliseconds - this.config.options!.pushRate! = 0.1; - this.debugWarnLog('Using Default Push Rate.'); + this.config.options!.pushRate! = 0.1 + await this.debugWarnLog('Using Default Push Rate.') } if (!this.config.options.maxRetries) { - this.config.options.maxRetries = 5; - this.debugWarnLog('Using Default Max Retries.'); + this.config.options.maxRetries = 5 + await this.debugWarnLog('Using Default Max Retries.') } else { - this.maxRetries = this.config.options.maxRetries; + this.maxRetries = this.config.options.maxRetries } if (!this.config.options.delayBetweenRetries) { // default 3 seconds - this.config.options!.delayBetweenRetries! = 3000; - this.debugWarnLog('Using Default Delay Between Retries.'); + this.config.options!.delayBetweenRetries! = 3000 + await this.debugWarnLog('Using Default Delay Between Retries.') } else { - this.delayBetweenRetries = this.config.options.delayBetweenRetries * 1000; + this.delayBetweenRetries = this.config.options.delayBetweenRetries * 1000 } if (!this.config.credentials && !this.config.options) { - this.debugWarnLog('Missing Credentials'); + await this.debugWarnLog('Missing Credentials') } else if (this.config.credentials && !this.config.credentials.notice) { if (!this.config.credentials?.token) { - this.debugErrorLog('Missing token'); - this.debugWarnLog('Cloud Enabled SwitchBot Devices & IR Devices will not work'); + await this.debugErrorLog('Missing token') + await this.debugWarnLog('Cloud Enabled SwitchBot Devices & IR Devices will not work') } if (this.config.credentials?.token) { if (!this.config.credentials?.secret) { - this.debugErrorLog('Missing secret'); - this.debugWarnLog('Cloud Enabled SwitchBot Devices & IR Devices will not work'); + await this.debugErrorLog('Missing secret') + await this.debugWarnLog('Cloud Enabled SwitchBot Devices & IR Devices will not work') } } } @@ -464,1007 +480,892 @@ export class SwitchBotPlatform implements DynamicPlatformPlugin { /** * The openToken was old config. * This method saves the openToken as the token in the config.json file - * @param this.config.credentials.openToken */ async updateToken() { try { // check the new token was provided if (!this.config.credentials?.openToken) { - throw new Error('New token not provided'); + throw new Error('New token not provided') } // load in the current config - const currentConfig = JSON.parse(readFileSync(this.api.user.configPath(), 'utf8')); + const currentConfig = JSON.parse(readFileSync(this.api.user.configPath(), 'utf8')) // check the platforms section is an array before we do array things on it if (!Array.isArray(currentConfig.platforms)) { - throw new Error('Cannot find platforms array in config'); + throw new TypeError('Cannot find platforms array in config') } // find this plugins current config - const pluginConfig = currentConfig.platforms.find((x: { platform: string }) => x.platform === PLATFORM_NAME); + const pluginConfig = currentConfig.platforms.find((x: { platform: string }) => x.platform === PLATFORM_NAME) if (!pluginConfig) { - throw new Error(`Cannot find config for ${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 if (typeof pluginConfig.credentials !== 'object') { - throw new Error('pluginConfig.credentials is not an object'); + throw new TypeError('pluginConfig.credentials is not an object') } // Move openToken to token if (!this.config.credentials.secret) { - this.warnLog('This plugin has been updated to use OpenAPI v1.1, config is set with openToken,' - + ' "openToken" cconfig has been moved to the "token" config'); - this.errorLog('"secret" config is not populated, you must populate then please restart Homebridge.'); + await this.warnLog('This plugin has been updated to use OpenAPI v1.1, config is set with openToken, "openToken" cconfig has been moved to the "token" config') + this.errorLog('"secret" config is not populated, you must populate then please restart Homebridge.') } else { - this.warnLog('This plugin has been updated to use OpenAPI v1.1, config is set with openToken, ' - + '"openToken" config has been moved to the "token" config, please restart Homebridge.'); + await this.warnLog('This plugin has been updated to use OpenAPI v1.1, config is set with openToken, "openToken" config has been moved to the "token" config, please restart Homebridge.') } // set the refresh token - pluginConfig.credentials.token = this.config.credentials?.openToken; + pluginConfig.credentials.token = this.config.credentials?.openToken if (pluginConfig.credentials.token) { - pluginConfig.credentials.openToken = undefined; + pluginConfig.credentials.openToken = undefined } - await this.debugWarnLog(`token: ${pluginConfig.credentials.token}`); + await this.debugWarnLog(`token: ${pluginConfig.credentials.token}`) // save the config, ensuring we maintain pretty json - writeFileSync(this.api.user.configPath(), JSON.stringify(currentConfig, null, 4)); - this.verifyConfig(); + writeFileSync(this.api.user.configPath(), JSON.stringify(currentConfig, null, 4)) + await this.verifyConfig() } catch (e: any) { - this.errorLog(`Update Token: ${e}`); + await this.errorLog(`Update Token: ${e}`) } } generateHeaders = () => { - const t = `${Date.now()}`; - const nonce = randomUUID(); - const data = this.config.credentials?.token + t + nonce; + const t = `${Date.now()}` + const nonce = randomUUID() + const data = this.config.credentials?.token + t + nonce const signTerm = crypto .createHmac('sha256', this.config.credentials?.secret) .update(Buffer.from(data, 'utf-8')) - .digest(); - const sign = signTerm.toString('base64'); + .digest() + const sign = signTerm.toString('base64') return { - Authorization: this.config.credentials?.token, - sign: sign, - nonce: nonce, - t: t, + 'Authorization': this.config.credentials?.token, + 'sign': sign, + 'nonce': nonce, + 't': t, 'Content-Type': 'application/json', - }; - }; + } + } - /** - * this method discovers devices - * - const t = `${Date.now()}`; - const nonce = 'requestID'; - const data = this.config.credentials?.token + t + nonce; - const signTerm = crypto.createHmac('sha256', this.config.credentials?.secret).update(Buffer.from(data, 'utf-8')).digest(); - const sign = signTerm.toString('base64'); - */ async discoverDevices() { - if (this.config.credentials?.token) { - let retryCount = 0; - const maxRetries = this.maxRetries; // Maximum number of retries - const delayBetweenRetries = this.delayBetweenRetries; // Delay between retries in milliseconds - await this.debugWarnLog(`Retry Count: ${retryCount}`); - await this.debugWarnLog(`Max Retries: ${maxRetries}`); - await this.debugWarnLog(`Delay Between Retries: ${delayBetweenRetries}`); - while (retryCount < maxRetries) { - try { - const { body, statusCode } = await request(Devices, { - headers: this.generateHeaders(), - }); - await this.debugWarnLog(`statusCode: ${statusCode}`); - const devicesAPI: any = await body.json(); - await this.debugWarnLog(`devicesAPI: ${JSON.stringify(devicesAPI)}`); - await this.debugWarnLog(`devicesAPI Body: ${JSON.stringify(devicesAPI.body)}`); - await this.debugWarnLog(`devicesAPI StatusCode: ${devicesAPI.statusCode}`); - if ((statusCode === 200 || statusCode === 100) && (devicesAPI.statusCode === 200 || devicesAPI.statusCode === 100)) { - await this.debugErrorLog(`statusCode: ${statusCode} & devicesAPI StatusCode: ${devicesAPI.statusCode}`); - // SwitchBot Devices - const deviceLists = devicesAPI.body.deviceList; - await this.debugWarnLog(`DeviceLists: ${JSON.stringify(deviceLists)}`); - await this.debugWarnLog(`DeviceLists Length: ${deviceLists.length}`); - if (!this.config.options?.devices) { - await this.debugLog(`SwitchBot Device Config Not Set: ${JSON.stringify(this.config.options?.devices)}`); - if (deviceLists.length === 0) { - await this.debugLog(`SwitchBot API Has No Devices With Cloud Services Enabled: ${JSON.stringify(devicesAPI.body)}`); - } else { - const devices = deviceLists.map((v: any) => v); - for (const device of devices) { - if (device.deviceType) { - if (device.configDeviceName) { - device.deviceName = device.configDeviceName; - } - this.createDevice(device); - } - } - } - } else if (this.config.credentials?.token && this.config.options.devices) { - await this.debugLog(`SwitchBot Device Config Set: ${JSON.stringify(this.config.options?.devices)}`); - if (deviceLists.length === 0) { - await this.debugLog(`SwitchBot API Has No Devices With Cloud Services Enabled: ${JSON.stringify(devicesAPI.body)}`); - } else { - const deviceConfigs = this.config.options?.devices; - - const mergeBydeviceId = (a1: { deviceId: string }[], a2: any[]) => - a1.map((itm: { deviceId: string }) => ({ - ...a2.find( - (item: { deviceId: string }) => - item.deviceId.toUpperCase().replace(/[^A-Z0-9]+/g, '') === itm.deviceId.toUpperCase().replace(/[^A-Z0-9]+/g, '') && item, - ), - ...itm, - })); - - const devices = mergeBydeviceId(deviceLists, deviceConfigs); - await this.debugLog(`SwitchBot Devices: ${JSON.stringify(devices)}`); - for (const device of devices) { - if (!device.deviceType) { - device.deviceType = device.configDeviceType; - this.errorLog(`API has displaying no deviceType: ${device.deviceType}, So using configDeviceType: ${device.configDeviceType}`); - } - if (device.deviceType) { - if (device.configDeviceName) { - device.deviceName = device.configDeviceName; - } - this.createDevice(device); - } - } - } - } else { - this.errorLog('SwitchBot Token Supplied, Issue with Auth.'); - } - if (devicesAPI.body.deviceList.length !== 0) { - this.infoLog(`Total SwitchBot Devices Found: ${devicesAPI.body.deviceList.length}`); - } else { - await this.debugLog(`Total SwitchBot Devices Found: ${devicesAPI.body.deviceList.length}`); - } + if (!this.config.credentials?.token) { + return this.handleManualConfig() + } - // IR Devices - const irDeviceLists = devicesAPI.body.infraredRemoteList; - if (!this.config.options?.irdevices) { - await this.debugLog(`IR Device Config Not Set: ${JSON.stringify(this.config.options?.irdevices)}`); - const devices = irDeviceLists.map((v: any) => v); - for (const device of devices) { - if (device.remoteType) { - this.createIRDevice(device); - } - } - } else { - await this.debugLog(`IR Device Config Set: ${JSON.stringify(this.config.options?.irdevices)}`); - const irDeviceConfig = this.config.options?.irdevices; - - const mergeIRBydeviceId = (a1: { deviceId: string }[], a2: any[]) => - a1.map((itm: { deviceId: string }) => ({ - ...a2.find( - (item: { deviceId: string }) => - item.deviceId.toUpperCase().replace(/[^A-Z0-9]+/g, '') === itm.deviceId.toUpperCase().replace(/[^A-Z0-9]+/g, '') && item, - ), - ...itm, - })); - - const devices = mergeIRBydeviceId(irDeviceLists, irDeviceConfig); - await this.debugLog(`IR Devices: ${JSON.stringify(devices)}`); - for (const device of devices) { - this.createIRDevice(device); - } - } - if (devicesAPI.body.infraredRemoteList.length !== 0) { - this.infoLog(`Total IR Devices Found: ${devicesAPI.body.infraredRemoteList.length}`); - } else { - await this.debugLog(`Total IR Devices Found: ${devicesAPI.body.infraredRemoteList.length}`); - } - break; - } else { - this.statusCode(statusCode); - this.statusCode(devicesAPI.statusCode); - if (statusCode === 500) { - retryCount++; - this.infoLog(`statusCode: ${statusCode} Attempt ${retryCount} of ${maxRetries}`); - await sleep(delayBetweenRetries); + let retryCount = 0 + const maxRetries = this.maxRetries + const delayBetweenRetries = this.delayBetweenRetries + + await this.debugWarnLog(`Retry Count: ${retryCount}`) + await this.debugWarnLog(`Max Retries: ${maxRetries}`) + await this.debugWarnLog(`Delay Between Retries: ${delayBetweenRetries}`) + + while (retryCount < maxRetries) { + try { + const { body, statusCode } = await request(Devices, { headers: this.generateHeaders() }) + await this.debugWarnLog(`statusCode: ${statusCode}`) + const devicesAPI: any = await body.json() + await this.debugWarnLog(`devicesAPI: ${JSON.stringify(devicesAPI)}`) + + if (this.isSuccessfulResponse(statusCode, devicesAPI.statusCode)) { + await this.handleDevices(devicesAPI.body.deviceList) + await this.handleIRDevices(devicesAPI.body.infraredRemoteList) + break + } else { + await this.handleErrorResponse(statusCode, devicesAPI.statusCode, retryCount, maxRetries, delayBetweenRetries) + retryCount++ + } + } catch (e: any) { + retryCount++ + await this.debugErrorLog(`Failed to Discover Devices, Error Message: ${JSON.stringify(e.message)}, Submit Bugs Here: https://tinyurl.com/SwitchBotBug`) + await this.debugErrorLog(`Failed to Discover Devices, Error: ${e}`) + } + } + } + + private async handleManualConfig() { + if (this.config.options?.devices) { + await this.debugLog(`SwitchBot Device Manual Config Set: ${JSON.stringify(this.config.options?.devices)}`) + const devices = this.config.options.devices.map((v: any) => v) + for (const device of devices) { + device.deviceType = device.configDeviceType + device.deviceName = device.configDeviceName + if (device.deviceType) { + await this.createDevice(device) + } + } + } else { + await this.errorLog('Neither SwitchBot Token or Device Config are set.') + } + } + + private isSuccessfulResponse(statusCode: number, apiStatusCode: number): boolean { + return (statusCode === 200 || statusCode === 100) && (apiStatusCode === 200 || apiStatusCode === 100) + } + + private async handleDevices(deviceLists: any[]) { + if (!this.config.options?.devices) { + await this.debugLog(`SwitchBot Device Config Not Set: ${JSON.stringify(this.config.options?.devices)}`) + if (deviceLists.length === 0) { + await this.debugLog('SwitchBot API Has No Devices With Cloud Services Enabled') + } else { + for (const device of deviceLists) { + if (device.deviceType) { + if (device.configDeviceName) { + device.deviceName = device.configDeviceName } + await this.createDevice(device) } - } catch (e: any) { - retryCount++; - this.debugErrorLog( - `Failed to Discover Devices, Error Message: ${JSON.stringify(e.message)}, Submit Bugs Here: ` + 'https://tinyurl.com/SwitchBotBug'); - await this.debugErrorLog(`Failed to Discover Devices, Error: ${e}`); } } - } else if (!this.config.credentials?.token && this.config.options?.devices) { - await this.debugLog(`SwitchBot Device Manual Config Set: ${JSON.stringify(this.config.options?.devices)}`); - const deviceConfigs = this.config.options?.devices; - const devices = deviceConfigs.map((v: any) => v); + } else { + await this.debugLog(`SwitchBot Device Config Set: ${JSON.stringify(this.config.options?.devices)}`) + const devices = this.mergeByDeviceId(deviceLists, this.config.options.devices) + await this.debugLog(`SwitchBot Devices: ${JSON.stringify(devices)}`) for (const device of devices) { - device.deviceType = device.configDeviceType; - device.deviceName = device.configDeviceName; + if (!device.deviceType && device.configDeviceType) { + device.deviceType = device.configDeviceType + await this.warnLog(`API is displaying no deviceType: ${device.deviceType}, So using configDeviceType: ${device.configDeviceType}`) + } else if (!device.deviceType && !device.configDeviceName) { + await this.errorLog('No deviceType or configDeviceType for device. No device will be created.') + } if (device.deviceType) { - this.createDevice(device); + if (device.configDeviceName) { + device.deviceName = device.configDeviceName + } + await this.createDevice(device) + } + } + } + } + + private async handleIRDevices(irDeviceLists: any[]) { + if (!this.config.options?.irdevices) { + await this.debugLog(`IR Device Config Not Set: ${JSON.stringify(this.config.options?.irdevices)}`) + for (const device of irDeviceLists) { + if (device.remoteType) { + await this.createIRDevice(device) } } } else { - this.errorLog('Neither SwitchBot Token or Device Config are not set.'); + await this.debugLog(`IR Device Config Set: ${JSON.stringify(this.config.options?.irdevices)}`) + const devices = this.mergeByDeviceId(irDeviceLists, this.config.options.irdevices) + await this.debugLog(`IR Devices: ${JSON.stringify(devices)}`) + for (const device of devices) { + await this.createIRDevice(device) + } + } + } + + private mergeByDeviceId(a1: { deviceId: string }[], a2: any[]) { + const normalizeDeviceId = (deviceId: string) => deviceId.toUpperCase().replace(/[^A-Z0-9]+/g, '') + return a1.map((itm) => { + const matchingItem = a2.find(item => normalizeDeviceId(item.deviceId) === normalizeDeviceId(itm.deviceId)) + return { ...matchingItem, ...itm } + }) + } + + private async handleErrorResponse(statusCode: number, apiStatusCode: number, retryCount: number, maxRetries: number, delayBetweenRetries: number) { + await this.statusCode(statusCode) + await this.statusCode(apiStatusCode) + if (statusCode === 500) { + this.infoLog(`statusCode: ${statusCode} Attempt ${retryCount + 1} of ${maxRetries}`) + await sleep(delayBetweenRetries) } } private async createDevice(device: device & devicesConfig) { - switch (device.deviceType!) { - case 'Humidifier': - await this.debugLog(`Discovered ${device.deviceType}: ${device.deviceId}`); - this.createHumidifier(device); - break; - case 'Hub Mini': - await this.debugLog(`Discovered ${device.deviceType}: ${device.deviceId}`); - break; - case 'Hub Plus': - await this.debugLog(`Discovered ${device.deviceType}: ${device.deviceId}`); - break; - case 'Hub 2': - await this.debugLog(`Discovered ${device.deviceType}: ${device.deviceId}`); - this.createHub2(device); - break; - case 'Bot': - await this.debugLog(`Discovered ${device.deviceType}: ${device.deviceId}`); - this.createBot(device); - break; - case 'Meter': - await this.debugLog(`Discovered ${device.deviceType}: ${device.deviceId}`); - this.createMeter(device); - break; - case 'MeterPlus': - case 'Meter Plus (JP)': - await this.debugLog(`Discovered ${device.deviceType}: ${device.deviceId}`); - this.createMeterPlus(device); - break; - case 'WoIOSensor': - await this.debugLog(`Discovered ${device.deviceType}: ${device.deviceId}`); - this.createIOSensor(device); - break; - case 'Water Detector': - await this.debugLog(`Discovered ${device.deviceType}: ${device.deviceId}`); - this.createWaterDetector(device); - break; - case 'Motion Sensor': - await this.debugLog(`Discovered ${device.deviceType}: ${device.deviceId}`); - this.createMotion(device); - break; - case 'Contact Sensor': - await this.debugLog(`Discovered ${device.deviceType}: ${device.deviceId}`); - this.createContact(device); - break; - case 'Curtain': - case 'Curtain3': - await this.debugLog(`Discovered ${device.deviceType} ${device.deviceName}: ${device.deviceId}`); - this.createCurtain(device); - break; - case 'Blind Tilt': - await this.debugLog(`Discovered ${device.deviceType} ${device.deviceName}: ${device.deviceId}`); - this.createBlindTilt(device); - break; - case 'Plug': - case 'Plug Mini (US)': - case 'Plug Mini (JP)': - await this.debugLog(`Discovered ${device.deviceType}: ${device.deviceId}`); - this.createPlug(device); - break; - case 'Smart Lock': - case 'Smart Lock Pro': - await this.debugLog(`Discovered ${device.deviceType}: ${device.deviceId}`); - this.createLock(device); - break; - case 'Color Bulb': - await this.debugLog(`Discovered ${device.deviceType}: ${device.deviceId}`); - this.createColorBulb(device); - break; - case 'K10+': - case 'WoSweeper': - case 'WoSweeperMini': - case 'Robot Vacuum Cleaner S1': - case 'Robot Vacuum Cleaner S1 Plus': - case 'Robot Vacuum Cleaner S10': - await this.debugLog(`Discovered ${device.deviceType}: ${device.deviceId}`); - this.createRobotVacuumCleaner(device); - break; - case 'Ceiling Light': - case 'Ceiling Light Pro': - await this.debugLog(`Discovered ${device.deviceType}: ${device.deviceId}`); - this.createCeilingLight(device); - break; - case 'Strip Light': - await this.debugLog(`Discovered ${device.deviceType}: ${device.deviceId}`); - this.createStripLight(device); - break; - case 'Battery Circulator Fan': - await this.debugLog(`Discovered ${device.deviceType}: ${device.deviceId}`); - this.createFan(device); - break; - case 'Remote': - case 'Indoor Cam': - case 'remote with screen+': - await this.debugLog(`Discovered ${device.deviceType}: ${device.deviceId}, is currently not supported, device: ${JSON.stringify(device)}`); - break; - default: - this.warnLog(`Device: ${device.deviceName} with Device Type: ${device.deviceType}, is currently not supported.`, - + `Submit Feature Requests Here: https://tinyurl.com/SwitchBotFeatureRequest, device: ${JSON.stringify(device)}`); + const deviceTypeHandlers: { [key: string]: (device: device & devicesConfig) => Promise } = { + 'Humidifier': this.createHumidifier.bind(this), + 'Hub 2': this.createHub2.bind(this), + 'Bot': this.createBot.bind(this), + 'Meter': this.createMeter.bind(this), + 'MeterPlus': this.createMeterPlus.bind(this), + 'Meter Plus (JP)': this.createMeterPlus.bind(this), + 'WoIOSensor': this.createIOSensor.bind(this), + 'Water Detector': this.createWaterDetector.bind(this), + 'Motion Sensor': this.createMotion.bind(this), + 'Contact Sensor': this.createContact.bind(this), + 'Curtain': this.createCurtain.bind(this), + 'Curtain3': this.createCurtain.bind(this), + 'WoRollerShade': this.createCurtain.bind(this), + 'Roller Shade': this.createCurtain.bind(this), + 'Blind Tilt': this.createBlindTilt.bind(this), + 'Plug': this.createPlug.bind(this), + 'Plug Mini (US)': this.createPlug.bind(this), + 'Plug Mini (JP)': this.createPlug.bind(this), + 'Smart Lock': this.createLock.bind(this), + 'Smart Lock Pro': this.createLock.bind(this), + 'Color Bulb': this.createColorBulb.bind(this), + 'K10+': this.createRobotVacuumCleaner.bind(this), + 'WoSweeper': this.createRobotVacuumCleaner.bind(this), + 'WoSweeperMini': this.createRobotVacuumCleaner.bind(this), + 'Robot Vacuum Cleaner S1': this.createRobotVacuumCleaner.bind(this), + 'Robot Vacuum Cleaner S1 Plus': this.createRobotVacuumCleaner.bind(this), + 'Robot Vacuum Cleaner S10': this.createRobotVacuumCleaner.bind(this), + 'Ceiling Light': this.createCeilingLight.bind(this), + 'Ceiling Light Pro': this.createCeilingLight.bind(this), + 'Strip Light': this.createStripLight.bind(this), + 'Battery Circulator Fan': this.createFan.bind(this), + } + + if (deviceTypeHandlers[device.deviceType!]) { + await this.debugLog(`Discovered ${device.deviceType}: ${device.deviceId}`) + await deviceTypeHandlers[device.deviceType!](device) + } else if (['Hub Mini', 'Hub Plus', 'Remote', 'Indoor Cam', 'remote with screen'].includes(device.deviceType!)) { + await this.debugLog(`Discovered ${device.deviceType}: ${device.deviceId}, is currently not supported, device: ${JSON.stringify(device)}`) + } else { + await this.warnLog(`Device: ${device.deviceName} with Device Type: ${device.deviceType}, is currently not supported. Submit Feature Requests Here: https://tinyurl.com/SwitchBotFeatureRequest, device: ${JSON.stringify(device)}`) } } private async createIRDevice(device: irdevice & devicesConfig) { - if (device.connectionType === undefined) { - device.connectionType = 'OpenAPI'; - } - switch (device.remoteType) { - case 'TV': - case 'DIY TV': - case 'Projector': - case 'DIY Projector': - case 'Set Top Box': - case 'DIY Set Top Box': - case 'IPTV': - case 'DIY IPTV': - case 'DVD': - case 'DIY DVD': - case 'Speaker': - case 'DIY Speaker': - await this.debugLog(`Discovered ${device.remoteType}: ${device.deviceId}`); - if (device.external === undefined) { - device.external = true; - this.createTV(device); - } else { - this.createTV(device); - } - break; - case 'Fan': - case 'DIY Fan': - await this.debugLog(`Discovered ${device.remoteType}: ${device.deviceId}`); - this.createIRFan(device); - break; - case 'Air Conditioner': - case 'DIY Air Conditioner': - await this.debugLog(`Discovered ${device.remoteType}: ${device.deviceId}`); - this.createAirConditioner(device); - break; - case 'Light': - case 'DIY Light': - await this.debugLog(`Discovered ${device.remoteType}: ${device.deviceId}`); - this.createLight(device); - break; - case 'Air Purifier': - case 'DIY Air Purifier': - await this.debugLog(`Discovered ${device.remoteType}: ${device.deviceId}`); - this.createAirPurifier(device); - break; - case 'Water Heater': - case 'DIY Water Heater': - await this.debugLog(`Discovered ${device.remoteType}: ${device.deviceId}`); - this.createWaterHeater(device); - break; - case 'Vacuum Cleaner': - case 'DIY Vacuum Cleaner': - await this.debugLog(`Discovered ${device.remoteType}: ${device.deviceId}`); - this.createVacuumCleaner(device); - break; - case 'Camera': - case 'DIY Camera': - await this.debugLog(`Discovered ${device.remoteType}: ${device.deviceId}`); - this.createCamera(device); - break; - case 'Others': - await this.debugLog(`Discovered ${device.remoteType}: ${device.deviceId}`); - this.createOthers(device); - break; - default: - await this.debugLog(`Unsupported Device: ${JSON.stringify(device)}`); - this.warnLog(`Device: ${device.deviceName} with Device Type: ${device.remoteType}, is currently not supported.`); - this.warnLog('Submit Feature Requests Here: ' + 'https://tinyurl.com/SwitchBotFeatureRequest'); + device.connectionType = device.connectionType ?? 'OpenAPI' + const deviceTypeHandlers: { [key: string]: (device: irdevice & devicesConfig) => Promise } = { + 'TV': this.createTV.bind(this), + 'DIY TV': this.createTV.bind(this), + 'Projector': this.createTV.bind(this), + 'DIY Projector': this.createTV.bind(this), + 'Set Top Box': this.createTV.bind(this), + 'DIY Set Top Box': this.createTV.bind(this), + 'IPTV': this.createTV.bind(this), + 'DIY IPTV': this.createTV.bind(this), + 'DVD': this.createTV.bind(this), + 'DIY DVD': this.createTV.bind(this), + 'Speaker': this.createTV.bind(this), + 'DIY Speaker': this.createTV.bind(this), + 'Fan': this.createIRFan.bind(this), + 'DIY Fan': this.createIRFan.bind(this), + 'Air Conditioner': this.createAirConditioner.bind(this), + 'DIY Air Conditioner': this.createAirConditioner.bind(this), + 'Light': this.createLight.bind(this), + 'DIY Light': this.createLight.bind(this), + 'Air Purifier': this.createAirPurifier.bind(this), + 'DIY Air Purifier': this.createAirPurifier.bind(this), + 'Water Heater': this.createWaterHeater.bind(this), + 'DIY Water Heater': this.createWaterHeater.bind(this), + 'Vacuum Cleaner': this.createVacuumCleaner.bind(this), + 'DIY Vacuum Cleaner': this.createVacuumCleaner.bind(this), + 'Camera': this.createCamera.bind(this), + 'DIY Camera': this.createCamera.bind(this), + 'Others': this.createOthers.bind(this), + } + + if (deviceTypeHandlers[device.remoteType!]) { + await this.debugLog(`Discovered ${device.remoteType}: ${device.deviceId}`) + if (device.remoteType.startsWith('DIY') && device.external === undefined) { + device.external = true + } + await deviceTypeHandlers[device.remoteType!](device) + } else { + await this.warnLog(`Device: ${device.deviceName} with Device Type: ${device.remoteType}, is currently not supported. Submit Feature Requests Here: https://tinyurl.com/SwitchBotFeatureRequest, device: ${JSON.stringify(device)}`) } } private async createHumidifier(device: device & devicesConfig) { - const uuid = this.api.hap.uuid.generate(`${device.deviceId}-${device.deviceType}`); + const uuid = this.api.hap.uuid.generate(`${device.deviceId}-${device.deviceType}`) // see if an accessory with the same uuid has already been registered and restored from // the cached devices we stored in the `configureAccessory` method above - const existingAccessory = this.accessories.find((accessory) => accessory.UUID === uuid); + const existingAccessory = this.accessories.find(accessory => accessory.UUID === uuid) if (existingAccessory) { // the accessory already exists if (await this.registerDevice(device)) { // if you need to update the accessory.context then you should run `api.updatePlatformAccessories`. eg.: - existingAccessory.context.device = device; - existingAccessory.context.deviceId = device.deviceId; - existingAccessory.context.deviceType = device.deviceType; - existingAccessory.context.model = SwitchBotModel.Humidifier; + existingAccessory.context.device = device + existingAccessory.context.deviceId = device.deviceId + existingAccessory.context.deviceType = device.deviceType + existingAccessory.context.model = SwitchBotModel.Humidifier existingAccessory.displayName = device.configDeviceName ? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName) - : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName); - existingAccessory.context.connectionType = await this.connectionType(device); - existingAccessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0'; - await this.infoLog(`Restoring existing accessory from cache: ${existingAccessory.displayName} deviceId: ${device.deviceId}`); - this.api.updatePlatformAccessories([existingAccessory]); + : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName) + existingAccessory.context.connectionType = await this.connectionType(device) + existingAccessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0' + await this.infoLog(`Restoring existing accessory from cache: ${existingAccessory.displayName} deviceId: ${device.deviceId}`) + this.api.updatePlatformAccessories([existingAccessory]) // create the accessory handler for the restored accessory // this is imported from `platformAccessory.ts` - new Humidifier(this, existingAccessory, device); - await this.debugLog(`${device.deviceType} uuid: ${device.deviceId}-${device.deviceType}, (${existingAccessory.UUID})`); + new Humidifier(this, existingAccessory, device) + await this.debugLog(`${device.deviceType} uuid: ${device.deviceId}-${device.deviceType}, (${existingAccessory.UUID})`) } else { - this.unregisterPlatformAccessories(existingAccessory); + this.unregisterPlatformAccessories(existingAccessory) } } else if (await this.registerDevice(device)) { // create a new accessory const accessory = new this.api.platformAccessory(device.configDeviceName ? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName) - : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName), uuid); + : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName), uuid) // store a copy of the device object in the `accessory.context` // the `context` property can be used to store any data about the accessory you may need - accessory.context.device = device; - accessory.context.deviceId = device.deviceId; - accessory.context.deviceType = device.deviceType; - accessory.context.model = SwitchBotModel.Humidifier; + accessory.context.device = device + accessory.context.deviceId = device.deviceId + accessory.context.deviceType = device.deviceType + accessory.context.model = SwitchBotModel.Humidifier accessory.displayName = device.configDeviceName ? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName) - : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName); - accessory.context.connectionType = await this.connectionType(device); - accessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0'; - const newOrExternal = !device.external ? 'Adding new' : 'Loading external'; - await this.infoLog(`${newOrExternal} accessory: ${accessory.displayName} deviceId: ${device.deviceId}`); + : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName) + accessory.context.connectionType = await this.connectionType(device) + accessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0' + const newOrExternal = !device.external ? 'Adding new' : 'Loading external' + await this.infoLog(`${newOrExternal} accessory: ${accessory.displayName} deviceId: ${device.deviceId}`) // create the accessory handler for the newly create accessory // this is imported from `platformAccessory.ts` - new Humidifier(this, accessory, device); - await this.debugLog(`${device.deviceType} uuid: ${device.deviceId}-${device.deviceType}, (${accessory.UUID})`); + new Humidifier(this, accessory, device) + await this.debugLog(`${device.deviceType} uuid: ${device.deviceId}-${device.deviceType}, (${accessory.UUID})`) // publish device externally or link the accessory to your platform - this.externalOrPlatform(device, accessory); - this.accessories.push(accessory); + this.externalOrPlatform(device, accessory) + this.accessories.push(accessory) } else { - await this.debugLog(`Device not registered: ${device.deviceName} ${device.deviceType} deviceId: ${device.deviceId}`); + await this.debugLog(`Device not registered: ${device.deviceName} ${device.deviceType} deviceId: ${device.deviceId}`) } } private async createBot(device: device & devicesConfig) { - const uuid = this.api.hap.uuid.generate(`${device.deviceId}-${device.deviceType}`); + const uuid = this.api.hap.uuid.generate(`${device.deviceId}-${device.deviceType}`) // see if an accessory with the same uuid has already been registered and restored from // the cached devices we stored in the `configureAccessory` method above - const existingAccessory = this.accessories.find((accessory) => accessory.UUID === uuid); + const existingAccessory = this.accessories.find(accessory => accessory.UUID === uuid) if (existingAccessory) { // the accessory already exists if (await this.registerDevice(device)) { // if you need to update the accessory.context then you should run `api.updatePlatformAccessories`. eg.: - existingAccessory.context.device = device; - existingAccessory.context.deviceId = device.deviceId; - existingAccessory.context.deviceType = device.deviceType; - existingAccessory.context.model = SwitchBotModel.Bot; + existingAccessory.context.device = device + existingAccessory.context.deviceId = device.deviceId + existingAccessory.context.deviceType = device.deviceType + existingAccessory.context.model = SwitchBotModel.Bot existingAccessory.displayName = device.configDeviceName ? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName) - : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName); - existingAccessory.context.connectionType = await this.connectionType(device); - existingAccessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0'; - this.infoLog(`Restoring existing accessory from cache: ${existingAccessory.displayName} deviceId: ${device.deviceId}`); - this.api.updatePlatformAccessories([existingAccessory]); + : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName) + existingAccessory.context.connectionType = await this.connectionType(device) + existingAccessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0' + this.infoLog(`Restoring existing accessory from cache: ${existingAccessory.displayName} deviceId: ${device.deviceId}`) + this.api.updatePlatformAccessories([existingAccessory]) // create the accessory handler for the restored accessory // this is imported from `platformAccessory.ts` - new Bot(this, existingAccessory, device); - await this.debugLog(`${device.deviceType} uuid: ${device.deviceId}-${device.deviceType}, (${existingAccessory.UUID})`); + new Bot(this, existingAccessory, device) + await this.debugLog(`${device.deviceType} uuid: ${device.deviceId}-${device.deviceType}, (${existingAccessory.UUID})`) } else { - this.unregisterPlatformAccessories(existingAccessory); + this.unregisterPlatformAccessories(existingAccessory) } } else if (await this.registerDevice(device)) { // create a new accessory const accessory = new this.api.platformAccessory(device.configDeviceName ? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName) - : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName), uuid); + : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName), uuid) // store a copy of the device object in the `accessory.context` // the `context` property can be used to store any data about the accessory you may need - accessory.context.device = device; - accessory.context.deviceId = device.deviceId; - accessory.context.deviceType = device.deviceType; - accessory.context.model = SwitchBotModel.Bot; + accessory.context.device = device + accessory.context.deviceId = device.deviceId + accessory.context.deviceType = device.deviceType + accessory.context.model = SwitchBotModel.Bot accessory.displayName = device.configDeviceName ? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName) - : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName); - accessory.context.connectionType = await this.connectionType(device); - accessory.context.connectionType = await this.connectionType(device); - accessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0'; - const newOrExternal = !device.external ? 'Adding new' : 'Loading external'; - await this.infoLog(`${newOrExternal} accessory: ${accessory.displayName} deviceId: ${device.deviceId}`); + : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName) + accessory.context.connectionType = await this.connectionType(device) + accessory.context.connectionType = await this.connectionType(device) + accessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0' + const newOrExternal = !device.external ? 'Adding new' : 'Loading external' + await this.infoLog(`${newOrExternal} accessory: ${accessory.displayName} deviceId: ${device.deviceId}`) // accessory.context.version = findaccessories.accessoryAttribute.softwareRevision; // create the accessory handler for the newly create accessory // this is imported from `platformAccessory.ts` - new Bot(this, accessory, device); - await this.debugLog(`${device.deviceType} uuid: ${device.deviceId}-${device.deviceType}, (${accessory.UUID})`); + new Bot(this, accessory, device) + await this.debugLog(`${device.deviceType} uuid: ${device.deviceId}-${device.deviceType}, (${accessory.UUID})`) // publish device externally or link the accessory to your platform - this.externalOrPlatform(device, accessory); - this.accessories.push(accessory); + this.externalOrPlatform(device, accessory) + this.accessories.push(accessory) } else { - await this.debugLog(`Device not registered: ${device.deviceName} ${device.deviceType} deviceId: ${device.deviceId}`); + await this.debugLog(`Device not registered: ${device.deviceName} ${device.deviceType} deviceId: ${device.deviceId}`) } } private async createMeter(device: device & devicesConfig) { - const uuid = this.api.hap.uuid.generate(`${device.deviceId}-${device.deviceType}`); + const uuid = this.api.hap.uuid.generate(`${device.deviceId}-${device.deviceType}`) // see if an accessory with the same uuid has already been registered and restored from // the cached devices we stored in the `configureAccessory` method above - const existingAccessory = this.accessories.find((accessory) => accessory.UUID === uuid); + const existingAccessory = this.accessories.find(accessory => accessory.UUID === uuid) if (existingAccessory) { // the accessory already exists if (await this.registerDevice(device)) { // if you need to update the accessory.context then you should run `api.updatePlatformAccessories`. eg.: - existingAccessory.context.device = device; - existingAccessory.context.model = SwitchBotModel.Meter; - existingAccessory.context.deviceId = device.deviceId; - existingAccessory.context.deviceType = device.deviceType; + existingAccessory.context.device = device + existingAccessory.context.model = SwitchBotModel.Meter + existingAccessory.context.deviceId = device.deviceId + existingAccessory.context.deviceType = device.deviceType existingAccessory.displayName = device.configDeviceName ? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName) - : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName); - existingAccessory.context.connectionType = await this.connectionType(device); - existingAccessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0'; - this.infoLog(`Restoring existing accessory from cache: ${existingAccessory.displayName} deviceId: ${device.deviceId}`); - this.api.updatePlatformAccessories([existingAccessory]); + : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName) + existingAccessory.context.connectionType = await this.connectionType(device) + existingAccessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0' + this.infoLog(`Restoring existing accessory from cache: ${existingAccessory.displayName} deviceId: ${device.deviceId}`) + this.api.updatePlatformAccessories([existingAccessory]) // create the accessory handler for the restored accessory // this is imported from `platformAccessory.ts` - new Meter(this, existingAccessory, device); - await this.debugLog(`${device.deviceType} uuid: ${device.deviceId}-${device.deviceType}, (${existingAccessory.UUID})`); + new Meter(this, existingAccessory, device) + await this.debugLog(`${device.deviceType} uuid: ${device.deviceId}-${device.deviceType}, (${existingAccessory.UUID})`) } else { - this.unregisterPlatformAccessories(existingAccessory); + this.unregisterPlatformAccessories(existingAccessory) } } else if (await this.registerDevice(device)) { // create a new accessory const accessory = new this.api.platformAccessory(device.configDeviceName ? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName) - : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName), uuid); + : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName), uuid) // store a copy of the device object in the `accessory.context` // the `context` property can be used to store any data about the accessory you may need - accessory.context.device = device; - accessory.context.model = SwitchBotModel.Meter; - accessory.context.deviceId = device.deviceId; - accessory.context.deviceType = device.deviceType; + accessory.context.device = device + accessory.context.model = SwitchBotModel.Meter + accessory.context.deviceId = device.deviceId + accessory.context.deviceType = device.deviceType accessory.displayName = device.configDeviceName ? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName) - : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName); - accessory.context.connectionType = await this.connectionType(device); - accessory.context.connectionType = await this.connectionType(device); - accessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0'; - const newOrExternal = !device.external ? 'Adding new' : 'Loading external'; - await this.infoLog(`${newOrExternal} accessory: ${accessory.displayName} deviceId: ${device.deviceId}`); + : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName) + accessory.context.connectionType = await this.connectionType(device) + accessory.context.connectionType = await this.connectionType(device) + accessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0' + const newOrExternal = !device.external ? 'Adding new' : 'Loading external' + await this.infoLog(`${newOrExternal} accessory: ${accessory.displayName} deviceId: ${device.deviceId}`) // create the accessory handler for the newly create accessory // this is imported from `platformAccessory.ts` - new Meter(this, accessory, device); - await this.debugLog(`${device.deviceType} uuid: ${device.deviceId}-${device.deviceType}, (${accessory.UUID})`); + new Meter(this, accessory, device) + await this.debugLog(`${device.deviceType} uuid: ${device.deviceId}-${device.deviceType}, (${accessory.UUID})`) // publish device externally or link the accessory to your platform - this.externalOrPlatform(device, accessory); - this.accessories.push(accessory); + this.externalOrPlatform(device, accessory) + this.accessories.push(accessory) } else { - await this.debugLog(`Device not registered: ${device.deviceName} ${device.deviceType} deviceId: ${device.deviceId}`); + await this.debugLog(`Device not registered: ${device.deviceName} ${device.deviceType} deviceId: ${device.deviceId}`) } } private async createMeterPlus(device: device & devicesConfig) { - const uuid = this.api.hap.uuid.generate(`${device.deviceId}-${device.deviceType}`); + const uuid = this.api.hap.uuid.generate(`${device.deviceId}-${device.deviceType}`) // see if an accessory with the same uuid has already been registered and restored from // the cached devices we stored in the `configureAccessory` method above - const existingAccessory = this.accessories.find((accessory) => accessory.UUID === uuid); + const existingAccessory = this.accessories.find(accessory => accessory.UUID === uuid) if (existingAccessory) { // the accessory already exists if (await this.registerDevice(device)) { // console.log("existingAccessory", existingAccessory); // if you need to update the accessory.context then you should run `api.updatePlatformAccessories`. eg.: - existingAccessory.context.device = device; - existingAccessory.context.model = SwitchBotModel.MeterPlusUS ?? SwitchBotModel.MeterPlusJP; - existingAccessory.context.deviceId = device.deviceId; - existingAccessory.context.deviceType = device.deviceType; + existingAccessory.context.device = device + existingAccessory.context.model = SwitchBotModel.MeterPlusUS ?? SwitchBotModel.MeterPlusJP + existingAccessory.context.deviceId = device.deviceId + existingAccessory.context.deviceType = device.deviceType existingAccessory.displayName = device.configDeviceName ? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName) - : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName); - existingAccessory.context.connectionType = await this.connectionType(device); - existingAccessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0'; - this.infoLog(`Restoring existing accessory from cache: ${existingAccessory.displayName} deviceId: ${device.deviceId}`); - this.api.updatePlatformAccessories([existingAccessory]); + : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName) + existingAccessory.context.connectionType = await this.connectionType(device) + existingAccessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0' + this.infoLog(`Restoring existing accessory from cache: ${existingAccessory.displayName} deviceId: ${device.deviceId}`) + this.api.updatePlatformAccessories([existingAccessory]) // create the accessory handler for the restored accessory // this is imported from `platformAccessory.ts` - new MeterPlus(this, existingAccessory, device); - await this.debugLog(`${device.deviceType} uuid: ${device.deviceId}-${device.deviceType}, (${existingAccessory.UUID})`); + new MeterPlus(this, existingAccessory, device) + await this.debugLog(`${device.deviceType} uuid: ${device.deviceId}-${device.deviceType}, (${existingAccessory.UUID})`) } else { - this.unregisterPlatformAccessories(existingAccessory); + this.unregisterPlatformAccessories(existingAccessory) } } else if (await this.registerDevice(device)) { // create a new accessory const accessory = new this.api.platformAccessory(device.configDeviceName ? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName) - : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName), uuid); + : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName), uuid) // store a copy of the device object in the `accessory.context` // the `context` property can be used to store any data about the accessory you may need - accessory.context.device = device; - accessory.context.model = SwitchBotModel.MeterPlusUS ?? SwitchBotModel.MeterPlusJP; - accessory.context.deviceId = device.deviceId; - accessory.context.deviceType = device.deviceType; + accessory.context.device = device + accessory.context.model = SwitchBotModel.MeterPlusUS ?? SwitchBotModel.MeterPlusJP + accessory.context.deviceId = device.deviceId + accessory.context.deviceType = device.deviceType accessory.displayName = device.configDeviceName ? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName) - : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName); - accessory.context.connectionType = await this.connectionType(device); - accessory.context.connectionType = await this.connectionType(device); - accessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0'; - const newOrExternal = !device.external ? 'Adding new' : 'Loading external'; - await this.infoLog(`${newOrExternal} accessory: ${accessory.displayName} deviceId: ${device.deviceId}`); + : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName) + accessory.context.connectionType = await this.connectionType(device) + accessory.context.connectionType = await this.connectionType(device) + accessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0' + const newOrExternal = !device.external ? 'Adding new' : 'Loading external' + await this.infoLog(`${newOrExternal} accessory: ${accessory.displayName} deviceId: ${device.deviceId}`) // create the accessory handler for the newly create accessory // this is imported from `platformAccessory.ts` - new MeterPlus(this, accessory, device); - await this.debugLog(`${device.deviceType} uuid: ${device.deviceId}-${device.deviceType}, (${accessory.UUID})`); + new MeterPlus(this, accessory, device) + await this.debugLog(`${device.deviceType} uuid: ${device.deviceId}-${device.deviceType}, (${accessory.UUID})`) // publish device externally or link the accessory to your platform - this.externalOrPlatform(device, accessory); - this.accessories.push(accessory); + this.externalOrPlatform(device, accessory) + this.accessories.push(accessory) } else { - await this.debugLog(`Device not registered: ${device.deviceName} ${device.deviceType} deviceId: ${device.deviceId}`); + await this.debugLog(`Device not registered: ${device.deviceName} ${device.deviceType} deviceId: ${device.deviceId}`) } } private async createHub2(device: device & devicesConfig) { - const uuid = this.api.hap.uuid.generate(`${device.deviceId}-${device.deviceType}`); + const uuid = this.api.hap.uuid.generate(`${device.deviceId}-${device.deviceType}`) // see if an accessory with the same uuid has already been registered and restored from // the cached devices we stored in the `configureAccessory` method above - const existingAccessory = this.accessories.find((accessory) => accessory.UUID === uuid); + const existingAccessory = this.accessories.find(accessory => accessory.UUID === uuid) if (existingAccessory) { // the accessory already exists if (await this.registerDevice(device)) { // console.log("existingAccessory", existingAccessory); // if you need to update the accessory.context then you should run `api.updatePlatformAccessories`. eg.: - existingAccessory.context.device = device; - existingAccessory.context.model = SwitchBotModel.Hub2; - existingAccessory.context.deviceId = device.deviceId; - existingAccessory.context.deviceType = device.deviceType; + existingAccessory.context.device = device + existingAccessory.context.model = SwitchBotModel.Hub2 + existingAccessory.context.deviceId = device.deviceId + existingAccessory.context.deviceType = device.deviceType existingAccessory.displayName = device.configDeviceName ? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName) - : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName); - existingAccessory.context.connectionType = await this.connectionType(device); - existingAccessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0'; - this.infoLog(`Restoring existing accessory from cache: ${existingAccessory.displayName} deviceId: ${device.deviceId}`); - this.api.updatePlatformAccessories([existingAccessory]); + : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName) + existingAccessory.context.connectionType = await this.connectionType(device) + existingAccessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0' + this.infoLog(`Restoring existing accessory from cache: ${existingAccessory.displayName} deviceId: ${device.deviceId}`) + this.api.updatePlatformAccessories([existingAccessory]) // create the accessory handler for the restored accessory // this is imported from `platformAccessory.ts` - new Hub(this, existingAccessory, device); - await this.debugLog(`${device.deviceType} uuid: ${device.deviceId}-${device.deviceType}, (${existingAccessory.UUID})`); + new Hub(this, existingAccessory, device) + await this.debugLog(`${device.deviceType} uuid: ${device.deviceId}-${device.deviceType}, (${existingAccessory.UUID})`) } else { - this.unregisterPlatformAccessories(existingAccessory); + this.unregisterPlatformAccessories(existingAccessory) } } else if (await this.registerDevice(device)) { // create a new accessory const accessory = new this.api.platformAccessory(device.configDeviceName ? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName) - : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName), uuid); + : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName), uuid) // store a copy of the device object in the `accessory.context` // the `context` property can be used to store any data about the accessory you may need - accessory.context.device = device; - accessory.context.model = SwitchBotModel.Hub2; - accessory.context.deviceId = device.deviceId; - accessory.context.deviceType = device.deviceType; + accessory.context.device = device + accessory.context.model = SwitchBotModel.Hub2 + accessory.context.deviceId = device.deviceId + accessory.context.deviceType = device.deviceType accessory.displayName = device.configDeviceName ? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName) - : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName); - accessory.context.connectionType = await this.connectionType(device); - accessory.context.connectionType = await this.connectionType(device); - accessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0'; - const newOrExternal = !device.external ? 'Adding new' : 'Loading external'; - await this.infoLog(`${newOrExternal} accessory: ${accessory.displayName} deviceId: ${device.deviceId}`); + : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName) + accessory.context.connectionType = await this.connectionType(device) + accessory.context.connectionType = await this.connectionType(device) + accessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0' + const newOrExternal = !device.external ? 'Adding new' : 'Loading external' + await this.infoLog(`${newOrExternal} accessory: ${accessory.displayName} deviceId: ${device.deviceId}`) // create the accessory handler for the newly create accessory // this is imported from `platformAccessory.ts` - new Hub(this, accessory, device); - await this.debugLog(`${device.deviceType} uuid: ${device.deviceId}-${device.deviceType}, (${accessory.UUID})`); + new Hub(this, accessory, device) + await this.debugLog(`${device.deviceType} uuid: ${device.deviceId}-${device.deviceType}, (${accessory.UUID})`) // publish device externally or link the accessory to your platform - this.externalOrPlatform(device, accessory); - this.accessories.push(accessory); + this.externalOrPlatform(device, accessory) + this.accessories.push(accessory) } else { - await this.debugLog(`Device not registered: ${device.deviceName} ${device.deviceType} deviceId: ${device.deviceId}`); + await this.debugLog(`Device not registered: ${device.deviceName} ${device.deviceType} deviceId: ${device.deviceId}`) } } private async createIOSensor(device: device & devicesConfig) { - const uuid = this.api.hap.uuid.generate(`${device.deviceId}-${device.deviceType}`); + const uuid = this.api.hap.uuid.generate(`${device.deviceId}-${device.deviceType}`) // see if an accessory with the same uuid has already been registered and restored from // the cached devices we stored in the `configureAccessory` method above - const existingAccessory = this.accessories.find((accessory) => accessory.UUID === uuid); + const existingAccessory = this.accessories.find(accessory => accessory.UUID === uuid) if (existingAccessory) { // the accessory already exists if (await this.registerDevice(device)) { // if you need to update the accessory.context then you should run `api.updatePlatformAccessories`. eg.: - existingAccessory.context.device = device; - existingAccessory.context.model = SwitchBotModel.OutdoorMeter; - existingAccessory.context.deviceId = device.deviceId; - existingAccessory.context.deviceType = device.deviceType; + existingAccessory.context.device = device + existingAccessory.context.model = SwitchBotModel.OutdoorMeter + existingAccessory.context.deviceId = device.deviceId + existingAccessory.context.deviceType = device.deviceType existingAccessory.displayName = device.configDeviceName ? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName) - : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName); - existingAccessory.context.connectionType = await this.connectionType(device); - existingAccessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0'; - await this.infoLog(`Restoring existing accessory from cache: ${existingAccessory.displayName} deviceId: ${device.deviceId}`); - this.api.updatePlatformAccessories([existingAccessory]); + : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName) + existingAccessory.context.connectionType = await this.connectionType(device) + existingAccessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0' + await this.infoLog(`Restoring existing accessory from cache: ${existingAccessory.displayName} deviceId: ${device.deviceId}`) + this.api.updatePlatformAccessories([existingAccessory]) // create the accessory handler for the restored accessory // this is imported from `platformAccessory.ts` - new IOSensor(this, existingAccessory, device); - await this.debugLog(`${device.deviceType} uuid: ${device.deviceId}-${device.deviceType}, (${existingAccessory.UUID})`); + new IOSensor(this, existingAccessory, device) + await this.debugLog(`${device.deviceType} uuid: ${device.deviceId}-${device.deviceType}, (${existingAccessory.UUID})`) } else { - this.unregisterPlatformAccessories(existingAccessory); + this.unregisterPlatformAccessories(existingAccessory) } } else if (await this.registerDevice(device)) { // create a new accessory const accessory = new this.api.platformAccessory(device.configDeviceName ? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName) - : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName), uuid); + : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName), uuid) // store a copy of the device object in the `accessory.context` // the `context` property can be used to store any data about the accessory you may need - accessory.context.device = device; - accessory.context.model = SwitchBotModel.OutdoorMeter; - accessory.context.deviceId = device.deviceId; - accessory.context.deviceType = device.deviceType; + accessory.context.device = device + accessory.context.model = SwitchBotModel.OutdoorMeter + accessory.context.deviceId = device.deviceId + accessory.context.deviceType = device.deviceType accessory.displayName = device.configDeviceName ? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName) - : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName); - accessory.context.connectionType = await this.connectionType(device); - accessory.context.connectionType = await this.connectionType(device); - accessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0'; - const newOrExternal = !device.external ? 'Adding new' : 'Loading external'; - await this.infoLog(`${newOrExternal} accessory: ${accessory.displayName} deviceId: ${device.deviceId}`); + : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName) + accessory.context.connectionType = await this.connectionType(device) + accessory.context.connectionType = await this.connectionType(device) + accessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0' + const newOrExternal = !device.external ? 'Adding new' : 'Loading external' + await this.infoLog(`${newOrExternal} accessory: ${accessory.displayName} deviceId: ${device.deviceId}`) // create the accessory handler for the newly create accessory // this is imported from `platformAccessory.ts` - new IOSensor(this, accessory, device); - await this.debugLog(`${device.deviceType} uuid: ${device.deviceId}-${device.deviceType}, (${accessory.UUID})`); + new IOSensor(this, accessory, device) + await this.debugLog(`${device.deviceType} uuid: ${device.deviceId}-${device.deviceType}, (${accessory.UUID})`) // publish device externally or link the accessory to your platform - this.externalOrPlatform(device, accessory); - this.accessories.push(accessory); + this.externalOrPlatform(device, accessory) + this.accessories.push(accessory) } else { - await this.debugLog(`Device not registered: ${device.deviceName} ${device.deviceType} deviceId: ${device.deviceId}`); + await this.debugLog(`Device not registered: ${device.deviceName} ${device.deviceType} deviceId: ${device.deviceId}`) } } private async createWaterDetector(device: device & devicesConfig) { - const uuid = this.api.hap.uuid.generate(`${device.deviceId}-${device.deviceType}`); + const uuid = this.api.hap.uuid.generate(`${device.deviceId}-${device.deviceType}`) // see if an accessory with the same uuid has already been registered and restored from // the cached devices we stored in the `configureAccessory` method above - const existingAccessory = this.accessories.find((accessory) => accessory.UUID === uuid); + const existingAccessory = this.accessories.find(accessory => accessory.UUID === uuid) if (existingAccessory) { // the accessory already exists if (await this.registerDevice(device)) { // if you need to update the accessory.context then you should run `api.updatePlatformAccessories`. eg.: - existingAccessory.context.device = device; - existingAccessory.context.deviceId = device.deviceId; - existingAccessory.context.deviceType = device.deviceType; - existingAccessory.context.model = SwitchBotModel.WaterDetector; + existingAccessory.context.device = device + existingAccessory.context.deviceId = device.deviceId + existingAccessory.context.deviceType = device.deviceType + existingAccessory.context.model = SwitchBotModel.WaterDetector existingAccessory.displayName = device.configDeviceName ? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName) - : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName); - existingAccessory.context.connectionType = await this.connectionType(device); - existingAccessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0'; - this.infoLog(`Restoring existing accessory from cache: ${existingAccessory.displayName} deviceId: ${device.deviceId}`); - this.api.updatePlatformAccessories([existingAccessory]); + : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName) + existingAccessory.context.connectionType = await this.connectionType(device) + existingAccessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0' + this.infoLog(`Restoring existing accessory from cache: ${existingAccessory.displayName} deviceId: ${device.deviceId}`) + this.api.updatePlatformAccessories([existingAccessory]) // create the accessory handler for the restored accessory // this is imported from `platformAccessory.ts` - new WaterDetector(this, existingAccessory, device); - await this.debugLog(`${device.deviceType} uuid: ${device.deviceId}-${device.deviceType}, (${existingAccessory.UUID})`); + new WaterDetector(this, existingAccessory, device) + await this.debugLog(`${device.deviceType} uuid: ${device.deviceId}-${device.deviceType}, (${existingAccessory.UUID})`) } else { - this.unregisterPlatformAccessories(existingAccessory); + this.unregisterPlatformAccessories(existingAccessory) } } else if (await this.registerDevice(device)) { // create a new accessory const accessory = new this.api.platformAccessory(device.configDeviceName ? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName) - : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName), uuid); + : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName), uuid) // store a copy of the device object in the `accessory.context` // the `context` property can be used to store any data about the accessory you may need - accessory.context.device = device; - accessory.context.deviceId = device.deviceId; - accessory.context.deviceType = device.deviceType; - accessory.context.model = SwitchBotModel.WaterDetector; + accessory.context.device = device + accessory.context.deviceId = device.deviceId + accessory.context.deviceType = device.deviceType + accessory.context.model = SwitchBotModel.WaterDetector accessory.displayName = device.configDeviceName ? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName) - : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName); - accessory.context.connectionType = await this.connectionType(device); - accessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0'; - accessory.context.connectionType = await this.connectionType(device); - const newOrExternal = !device.external ? 'Adding new' : 'Loading external'; - await this.infoLog(`${newOrExternal} accessory: ${accessory.displayName} deviceId: ${device.deviceId}`); + : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName) + accessory.context.connectionType = await this.connectionType(device) + accessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0' + accessory.context.connectionType = await this.connectionType(device) + const newOrExternal = !device.external ? 'Adding new' : 'Loading external' + await this.infoLog(`${newOrExternal} accessory: ${accessory.displayName} deviceId: ${device.deviceId}`) // create the accessory handler for the newly create accessory // this is imported from `platformAccessory.ts` - new WaterDetector(this, accessory, device); - await this.debugLog(`${device.deviceType} uuid: ${device.deviceId}-${device.deviceType}, (${accessory.UUID})`); + new WaterDetector(this, accessory, device) + await this.debugLog(`${device.deviceType} uuid: ${device.deviceId}-${device.deviceType}, (${accessory.UUID})`) // publish device externally or link the accessory to your platform - this.externalOrPlatform(device, accessory); - this.accessories.push(accessory); + this.externalOrPlatform(device, accessory) + this.accessories.push(accessory) } else { - await this.debugLog(`Device not registered: ${device.deviceName} ${device.deviceType} deviceId: ${device.deviceId}`); + await this.debugLog(`Device not registered: ${device.deviceName} ${device.deviceType} deviceId: ${device.deviceId}`) } } private async createMotion(device: device & devicesConfig) { - const uuid = this.api.hap.uuid.generate(`${device.deviceId}-${device.deviceType}`); + const uuid = this.api.hap.uuid.generate(`${device.deviceId}-${device.deviceType}`) // see if an accessory with the same uuid has already been registered and restored from // the cached devices we stored in the `configureAccessory` method above - const existingAccessory = this.accessories.find((accessory) => accessory.UUID === uuid); + const existingAccessory = this.accessories.find(accessory => accessory.UUID === uuid) if (existingAccessory) { // the accessory already exists if (await this.registerDevice(device)) { // if you need to update the accessory.context then you should run `api.updatePlatformAccessories`. eg.: - existingAccessory.context.device = device; - existingAccessory.context.deviceId = device.deviceId; - existingAccessory.context.deviceType = device.deviceType; - existingAccessory.context.model = SwitchBotModel.MotionSensor; + existingAccessory.context.device = device + existingAccessory.context.deviceId = device.deviceId + existingAccessory.context.deviceType = device.deviceType + existingAccessory.context.model = SwitchBotModel.MotionSensor existingAccessory.displayName = device.configDeviceName ? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName) - : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName); - existingAccessory.context.connectionType = await this.connectionType(device); - existingAccessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0'; - this.infoLog(`Restoring existing accessory from cache: ${existingAccessory.displayName} deviceId: ${device.deviceId}`); - this.api.updatePlatformAccessories([existingAccessory]); + : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName) + existingAccessory.context.connectionType = await this.connectionType(device) + existingAccessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0' + this.infoLog(`Restoring existing accessory from cache: ${existingAccessory.displayName} deviceId: ${device.deviceId}`) + this.api.updatePlatformAccessories([existingAccessory]) // create the accessory handler for the restored accessory // this is imported from `platformAccessory.ts` - new Motion(this, existingAccessory, device); - await this.debugLog(`${device.deviceType} uuid: ${device.deviceId}-${device.deviceType}, (${existingAccessory.UUID})`); + new Motion(this, existingAccessory, device) + await this.debugLog(`${device.deviceType} uuid: ${device.deviceId}-${device.deviceType}, (${existingAccessory.UUID})`) } else { - this.unregisterPlatformAccessories(existingAccessory); + this.unregisterPlatformAccessories(existingAccessory) } } else if (await this.registerDevice(device)) { // create a new accessory const accessory = new this.api.platformAccessory(device.configDeviceName ? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName) - : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName), uuid); + : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName), uuid) // store a copy of the device object in the `accessory.context` // the `context` property can be used to store any data about the accessory you may need - accessory.context.device = device; - accessory.context.deviceId = device.deviceId; - accessory.context.deviceType = device.deviceType; - accessory.context.model = SwitchBotModel.MotionSensor; + accessory.context.device = device + accessory.context.deviceId = device.deviceId + accessory.context.deviceType = device.deviceType + accessory.context.model = SwitchBotModel.MotionSensor accessory.displayName = device.configDeviceName ? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName) - : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName); - accessory.context.connectionType = await this.connectionType(device); - accessory.context.connectionType = await this.connectionType(device); - accessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0'; - const newOrExternal = !device.external ? 'Adding new' : 'Loading external'; - await this.infoLog(`${newOrExternal} accessory: ${accessory.displayName} deviceId: ${device.deviceId}`); + : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName) + accessory.context.connectionType = await this.connectionType(device) + accessory.context.connectionType = await this.connectionType(device) + accessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0' + const newOrExternal = !device.external ? 'Adding new' : 'Loading external' + await this.infoLog(`${newOrExternal} accessory: ${accessory.displayName} deviceId: ${device.deviceId}`) // create the accessory handler for the newly create accessory // this is imported from `platformAccessory.ts` - new Motion(this, accessory, device); - await this.debugLog(`${device.deviceType} uuid: ${device.deviceId}-${device.deviceType}, (${accessory.UUID})`); + new Motion(this, accessory, device) + await this.debugLog(`${device.deviceType} uuid: ${device.deviceId}-${device.deviceType}, (${accessory.UUID})`) // publish device externally or link the accessory to your platform - this.externalOrPlatform(device, accessory); - this.accessories.push(accessory); + this.externalOrPlatform(device, accessory) + this.accessories.push(accessory) } else { - await this.debugLog(`Device not registered: ${device.deviceName} ${device.deviceType} deviceId: ${device.deviceId}`); + await this.debugLog(`Device not registered: ${device.deviceName} ${device.deviceType} deviceId: ${device.deviceId}`) } } private async createContact(device: device & devicesConfig) { - const uuid = this.api.hap.uuid.generate(`${device.deviceId}-${device.deviceType}`); + const uuid = this.api.hap.uuid.generate(`${device.deviceId}-${device.deviceType}`) // see if an accessory with the same uuid has already been registered and restored from // the cached devices we stored in the `configureAccessory` method above - const existingAccessory = this.accessories.find((accessory) => accessory.UUID === uuid); + const existingAccessory = this.accessories.find(accessory => accessory.UUID === uuid) if (existingAccessory) { // the accessory already exists if (await this.registerDevice(device)) { // if you need to update the accessory.context then you should run `api.updatePlatformAccessories`. eg.: - existingAccessory.context.device = device; - existingAccessory.context.deviceId = device.deviceId; - existingAccessory.context.deviceType = device.deviceType; - existingAccessory.context.model = SwitchBotModel.ContactSensor; + existingAccessory.context.device = device + existingAccessory.context.deviceId = device.deviceId + existingAccessory.context.deviceType = device.deviceType + existingAccessory.context.model = SwitchBotModel.ContactSensor existingAccessory.displayName = device.configDeviceName ? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName) - : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName); - existingAccessory.context.connectionType = await this.connectionType(device); - existingAccessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0'; - this.infoLog(`Restoring existing accessory from cache: ${existingAccessory.displayName} deviceId: ${device.deviceId}`); - this.api.updatePlatformAccessories([existingAccessory]); + : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName) + existingAccessory.context.connectionType = await this.connectionType(device) + existingAccessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0' + this.infoLog(`Restoring existing accessory from cache: ${existingAccessory.displayName} deviceId: ${device.deviceId}`) + this.api.updatePlatformAccessories([existingAccessory]) // create the accessory handler for the restored accessory // this is imported from `platformAccessory.ts` - new Contact(this, existingAccessory, device); - await this.debugLog(`${device.deviceType} uuid: ${device.deviceId}-${device.deviceType}, (${existingAccessory.UUID})`); + new Contact(this, existingAccessory, device) + await this.debugLog(`${device.deviceType} uuid: ${device.deviceId}-${device.deviceType}, (${existingAccessory.UUID})`) } else { - this.unregisterPlatformAccessories(existingAccessory); + this.unregisterPlatformAccessories(existingAccessory) } } else if (await this.registerDevice(device)) { // create a new accessory const accessory = new this.api.platformAccessory(device.configDeviceName ? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName) - : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName), uuid); + : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName), uuid) // store a copy of the device object in the `accessory.context` // the `context` property can be used to store any data about the accessory you may need - accessory.context.device = device; - accessory.context.deviceId = device.deviceId; - accessory.context.deviceType = device.deviceType; - accessory.context.model = SwitchBotModel.ContactSensor; + accessory.context.device = device + accessory.context.deviceId = device.deviceId + accessory.context.deviceType = device.deviceType + accessory.context.model = SwitchBotModel.ContactSensor accessory.displayName = device.configDeviceName ? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName) - : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName); - accessory.context.connectionType = await this.connectionType(device); - accessory.context.connectionType = await this.connectionType(device); - accessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0'; - const newOrExternal = !device.external ? 'Adding new' : 'Loading external'; - await this.infoLog(`${newOrExternal} accessory: ${accessory.displayName} deviceId: ${device.deviceId}`); + : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName) + accessory.context.connectionType = await this.connectionType(device) + accessory.context.connectionType = await this.connectionType(device) + accessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0' + const newOrExternal = !device.external ? 'Adding new' : 'Loading external' + await this.infoLog(`${newOrExternal} accessory: ${accessory.displayName} deviceId: ${device.deviceId}`) // create the accessory handler for the newly create accessory // this is imported from `platformAccessory.ts` - new Contact(this, accessory, device); - await this.debugLog(`${device.deviceType} uuid: ${device.deviceId}-${device.deviceType}, (${accessory.UUID})`); + new Contact(this, accessory, device) + await this.debugLog(`${device.deviceType} uuid: ${device.deviceId}-${device.deviceType}, (${accessory.UUID})`) // publish device externally or link the accessory to your platform - this.externalOrPlatform(device, accessory); - this.accessories.push(accessory); + this.externalOrPlatform(device, accessory) + this.accessories.push(accessory) } else { - await this.debugLog(`Device not registered: ${device.deviceName} ${device.deviceType} deviceId: ${device.deviceId}`); + await this.debugLog(`Device not registered: ${device.deviceName} ${device.deviceType} deviceId: ${device.deviceId}`) } } private async createBlindTilt(device: device & devicesConfig) { - const uuid = this.api.hap.uuid.generate(`${device.deviceId}-${device.deviceType}`); + const uuid = this.api.hap.uuid.generate(`${device.deviceId}-${device.deviceType}`) // see if an accessory with the same uuid has already been registered and restored from // the cached devices we stored in the `configureAccessory` method above - const existingAccessory = this.accessories.find((accessory) => accessory.UUID === uuid); + const existingAccessory = this.accessories.find(accessory => accessory.UUID === uuid) if (existingAccessory) { // the accessory already exists if (await this.registerDevice(device)) { // if you need to update the accessory.context then you should run `api.updatePlatformAccessories`. eg.: - existingAccessory.context.device = device; - existingAccessory.context.deviceId = device.deviceId; - existingAccessory.context.deviceType = device.deviceType; - existingAccessory.context.model = SwitchBotModel.BlindTilt; + existingAccessory.context.device = device + existingAccessory.context.deviceId = device.deviceId + existingAccessory.context.deviceType = device.deviceType + existingAccessory.context.model = SwitchBotModel.BlindTilt existingAccessory.displayName = device.configDeviceName ? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName) - : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName); - existingAccessory.context.connectionType = await this.connectionType(device); - existingAccessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0'; - this.infoLog(`Restoring existing accessory from cache: ${existingAccessory.displayName} deviceId: ${device.deviceId}`); - this.api.updatePlatformAccessories([existingAccessory]); + : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName) + existingAccessory.context.connectionType = await this.connectionType(device) + existingAccessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0' + this.infoLog(`Restoring existing accessory from cache: ${existingAccessory.displayName} deviceId: ${device.deviceId}`) + this.api.updatePlatformAccessories([existingAccessory]) // create the accessory handler for the restored accessory // this is imported from `platformAccessory.ts` - new BlindTilt(this, existingAccessory, device); - await this.debugLog(`${device.deviceType} uuid: ${device.deviceId}-${device.deviceType}, (${existingAccessory.UUID})`); + new BlindTilt(this, existingAccessory, device) + await this.debugLog(`${device.deviceType} uuid: ${device.deviceId}-${device.deviceType}, (${existingAccessory.UUID})`) } else { - this.unregisterPlatformAccessories(existingAccessory); + this.unregisterPlatformAccessories(existingAccessory) } } else if (await this.registerDevice(device)) { if (isBlindTiltDevice(device)) { if (device.group && !device.curtain?.disable_group) { this.debugLog( - 'Your Curtains are grouped, ' + - `, Secondary curtain automatically hidden. Main Curtain: ${device.deviceName}, deviceId: ${device.deviceId}`); + 'Your Curtains are grouped, ' + + `, Secondary curtain automatically hidden. Main Curtain: ${device.deviceName}, deviceId: ${device.deviceId}`, + ) } else { if (device.master) { - this.warnLog(`Main Curtain: ${device.deviceName}, deviceId: ${device.deviceId}`); + this.warnLog(`Main Curtain: ${device.deviceName}, deviceId: ${device.deviceId}`) } else { - this.errorLog(`Secondary Curtain: ${device.deviceName}, deviceId: ${device.deviceId}`); + this.errorLog(`Secondary Curtain: ${device.deviceName}, deviceId: ${device.deviceId}`) } } } @@ -1472,75 +1373,76 @@ export class SwitchBotPlatform implements DynamicPlatformPlugin { // create a new accessory const accessory = new this.api.platformAccessory(device.configDeviceName ? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName) - : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName), uuid); + : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName), uuid) // store a copy of the device object in the `accessory.context` // the `context` property can be used to store any data about the accessory you may need - accessory.context.device = device;; - accessory.context.deviceId = device.deviceId; - accessory.context.deviceType = device.deviceType; - accessory.context.model = SwitchBotModel.BlindTilt; + accessory.context.device = device + accessory.context.deviceId = device.deviceId + accessory.context.deviceType = device.deviceType + accessory.context.model = SwitchBotModel.BlindTilt accessory.displayName = device.configDeviceName ? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName) - : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName); - accessory.context.connectionType = await this.connectionType(device); - accessory.context.connectionType = await this.connectionType(device); - accessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0'; - const newOrExternal = !device.external ? 'Adding new' : 'Loading external'; - await this.infoLog(`${newOrExternal} accessory: ${accessory.displayName} deviceId: ${device.deviceId}`); + : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName) + accessory.context.connectionType = await this.connectionType(device) + accessory.context.connectionType = await this.connectionType(device) + accessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0' + const newOrExternal = !device.external ? 'Adding new' : 'Loading external' + await this.infoLog(`${newOrExternal} accessory: ${accessory.displayName} deviceId: ${device.deviceId}`) // create the accessory handler for the newly create accessory // this is imported from `platformAccessory.ts` - new BlindTilt(this, accessory, device); - await this.debugLog(`${device.deviceType} uuid: ${device.deviceId}-${device.deviceType}, (${accessory.UUID})`); + new BlindTilt(this, accessory, device) + await this.debugLog(`${device.deviceType} uuid: ${device.deviceId}-${device.deviceType}, (${accessory.UUID})`) // publish device externally or link the accessory to your platform - this.externalOrPlatform(device, accessory); - this.accessories.push(accessory); + this.externalOrPlatform(device, accessory) + this.accessories.push(accessory) } else { - await this.debugLog(`Device not registered: ${device.deviceName} ${device.deviceType} deviceId: ${device.deviceId}`); + await this.debugLog(`Device not registered: ${device.deviceName} ${device.deviceType} deviceId: ${device.deviceId}`) } } private async createCurtain(device: device & devicesConfig) { - const uuid = this.api.hap.uuid.generate(`${device.deviceId}-${device.deviceType}`); + const uuid = this.api.hap.uuid.generate(`${device.deviceId}-${device.deviceType}`) // see if an accessory with the same uuid has already been registered and restored from // the cached devices we stored in the `configureAccessory` method above - const existingAccessory = this.accessories.find((accessory) => accessory.UUID === uuid); + const existingAccessory = this.accessories.find(accessory => accessory.UUID === uuid) if (existingAccessory) { // the accessory already exists if (await this.registerDevice(device)) { // if you need to update the accessory.context then you should run `api.updatePlatformAccessories`. eg.: - existingAccessory.context.device = device; - existingAccessory.context.deviceId = device.deviceId; - existingAccessory.context.deviceType = device.deviceType; - existingAccessory.context.model = device.deviceType === 'Curtain3' ? SwitchBotModel.Curtain3 : SwitchBotModel.Curtain; + existingAccessory.context.device = device + existingAccessory.context.deviceId = device.deviceId + existingAccessory.context.deviceType = device.deviceType + existingAccessory.context.model = device.deviceType === 'Curtain3' ? SwitchBotModel.Curtain3 : SwitchBotModel.Curtain existingAccessory.displayName = device.configDeviceName ? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName) - : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName); - existingAccessory.context.connectionType = await this.connectionType(device); - existingAccessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0'; - await this.infoLog(`Restoring existing accessory from cache: ${existingAccessory.displayName} deviceId: ${device.deviceId}`); - this.api.updatePlatformAccessories([existingAccessory]); + : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName) + existingAccessory.context.connectionType = await this.connectionType(device) + existingAccessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0' + await this.infoLog(`Restoring existing accessory from cache: ${existingAccessory.displayName} deviceId: ${device.deviceId}`) + this.api.updatePlatformAccessories([existingAccessory]) // create the accessory handler for the restored accessory // this is imported from `platformAccessory.ts` - new Curtain(this, existingAccessory, device); - await this.debugLog(`${device.deviceType} uuid: ${device.deviceId}-${device.deviceType}, (${existingAccessory.UUID})`); + new Curtain(this, existingAccessory, device) + await this.debugLog(`${device.deviceType} uuid: ${device.deviceId}-${device.deviceType}, (${existingAccessory.UUID})`) } else { - this.unregisterPlatformAccessories(existingAccessory); + this.unregisterPlatformAccessories(existingAccessory) } } else if (await this.registerDevice(device)) { if (isCurtainDevice(device)) { if (device.group && !device.curtain?.disable_group) { this.debugLog( - 'Your Curtains are grouped, ' + - `, Secondary curtain automatically hidden. Main Curtain: ${device.deviceName}, deviceId: ${device.deviceId}`); + 'Your Curtains are grouped, ' + + `, Secondary curtain automatically hidden. Main Curtain: ${device.deviceName}, deviceId: ${device.deviceId}`, + ) } else { if (device.master) { - this.warnLog(`Main Curtain: ${device.deviceName}, deviceId: ${device.deviceId}`); + this.warnLog(`Main Curtain: ${device.deviceName}, deviceId: ${device.deviceId}`) } else { - this.errorLog(`Secondary Curtain: ${device.deviceName}, deviceId: ${device.deviceId}`); + this.errorLog(`Secondary Curtain: ${device.deviceName}, deviceId: ${device.deviceId}`) } } } @@ -1548,1409 +1450,1317 @@ export class SwitchBotPlatform implements DynamicPlatformPlugin { // create a new accessory const accessory = new this.api.platformAccessory(device.configDeviceName ? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName) - : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName), uuid); + : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName), uuid) // store a copy of the device object in the `accessory.context` // the `context` property can be used to store any data about the accessory you may need - accessory.context.device = device; - accessory.context.deviceId = device.deviceId; - accessory.context.deviceType = device.deviceType; - accessory.context.model = device.deviceType === 'Curtain3' ? SwitchBotModel.Curtain3 : SwitchBotModel.Curtain; + accessory.context.device = device + accessory.context.deviceId = device.deviceId + accessory.context.deviceType = device.deviceType + accessory.context.model = device.deviceType === 'Curtain3' ? SwitchBotModel.Curtain3 : SwitchBotModel.Curtain accessory.displayName = device.configDeviceName ? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName) - : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName); - accessory.context.connectionType = await this.connectionType(device); - accessory.context.connectionType = await this.connectionType(device); - accessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0'; - const newOrExternal = !device.external ? 'Adding new' : 'Loading external'; - await this.infoLog(`${newOrExternal} accessory: ${accessory.displayName} deviceId: ${device.deviceId}`); + : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName) + accessory.context.connectionType = await this.connectionType(device) + accessory.context.connectionType = await this.connectionType(device) + accessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0' + const newOrExternal = !device.external ? 'Adding new' : 'Loading external' + await this.infoLog(`${newOrExternal} accessory: ${accessory.displayName} deviceId: ${device.deviceId}`) // create the accessory handler for the newly create accessory // this is imported from `platformAccessory.ts` - new Curtain(this, accessory, device); - await this.debugLog(`${device.deviceType} uuid: ${device.deviceId}-${device.deviceType}, (${accessory.UUID})`); + new Curtain(this, accessory, device) + await this.debugLog(`${device.deviceType} uuid: ${device.deviceId}-${device.deviceType}, (${accessory.UUID})`) // publish device externally or link the accessory to your platform - this.externalOrPlatform(device, accessory); - this.accessories.push(accessory); + this.externalOrPlatform(device, accessory) + this.accessories.push(accessory) } else { - await this.debugLog(`Device not registered: ${device.deviceName} ${device.deviceType} deviceId: ${device.deviceId}`); + await this.debugLog(`Device not registered: ${device.deviceName} ${device.deviceType} deviceId: ${device.deviceId}`) } } private async createPlug(device: device & devicesConfig) { - const uuid = this.api.hap.uuid.generate(`${device.deviceId}-${device.deviceType}`); + const uuid = this.api.hap.uuid.generate(`${device.deviceId}-${device.deviceType}`) // see if an accessory with the same uuid has already been registered and restored from // the cached devices we stored in the `configureAccessory` method above - const existingAccessory = this.accessories.find((accessory) => accessory.UUID === uuid); + const existingAccessory = this.accessories.find(accessory => accessory.UUID === uuid) if (existingAccessory) { // the accessory already exists if (await this.registerDevice(device)) { // if you need to update the accessory.context then you should run `api.updatePlatformAccessories`. eg.: - existingAccessory.context.device = device; - existingAccessory.context.deviceId = device.deviceId; - existingAccessory.context.deviceType = device.deviceType; - existingAccessory.context.model = device.deviceType === 'Plug Mini (US)' ? SwitchBotModel.PlugMiniUS : device.deviceType === 'Plug Mini (JP)' - ? SwitchBotModel.PlugMiniJP : SwitchBotModel.Plug; + existingAccessory.context.device = device + existingAccessory.context.deviceId = device.deviceId + existingAccessory.context.deviceType = device.deviceType + existingAccessory.context.model = device.deviceType === 'Plug Mini (US)' + ? SwitchBotModel.PlugMiniUS + : device.deviceType === 'Plug Mini (JP)' + ? SwitchBotModel.PlugMiniJP + : SwitchBotModel.Plug existingAccessory.displayName = device.configDeviceName ? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName) - : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName); - existingAccessory.context.connectionType = await this.connectionType(device); - existingAccessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0'; - await this.infoLog(`Restoring existing accessory from cache: ${existingAccessory.displayName} deviceId: ${device.deviceId}`); - this.api.updatePlatformAccessories([existingAccessory]); + : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName) + existingAccessory.context.connectionType = await this.connectionType(device) + existingAccessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0' + await this.infoLog(`Restoring existing accessory from cache: ${existingAccessory.displayName} deviceId: ${device.deviceId}`) + this.api.updatePlatformAccessories([existingAccessory]) // create the accessory handler for the restored accessory // this is imported from `platformAccessory.ts` - new Plug(this, existingAccessory, device); - await this.debugLog(`${device.deviceType} uuid: ${device.deviceId}-${device.deviceType}, (${existingAccessory.UUID})`); + new Plug(this, existingAccessory, device) + await this.debugLog(`${device.deviceType} uuid: ${device.deviceId}-${device.deviceType}, (${existingAccessory.UUID})`) } else { - this.unregisterPlatformAccessories(existingAccessory); + this.unregisterPlatformAccessories(existingAccessory) } } else if (await this.registerDevice(device)) { // create a new accessory const accessory = new this.api.platformAccessory(device.configDeviceName ? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName) - : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName), uuid); + : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName), uuid) // store a copy of the device object in the `accessory.context` // the `context` property can be used to store any data about the accessory you may need - accessory.context.device = device; - accessory.context.deviceId = device.deviceId; - accessory.context.deviceType = device.deviceType; - accessory.context.model = device.deviceType === 'Plug Mini (US)' ? SwitchBotModel.PlugMiniUS : device.deviceType === 'Plug Mini (JP)' - ? SwitchBotModel.PlugMiniJP : SwitchBotModel.Plug; + accessory.context.device = device + accessory.context.deviceId = device.deviceId + accessory.context.deviceType = device.deviceType + accessory.context.model = device.deviceType === 'Plug Mini (US)' + ? SwitchBotModel.PlugMiniUS + : device.deviceType === 'Plug Mini (JP)' + ? SwitchBotModel.PlugMiniJP + : SwitchBotModel.Plug accessory.displayName = device.configDeviceName ? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName) - : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName); - accessory.context.connectionType = await this.connectionType(device); - accessory.context.connectionType = await this.connectionType(device); - accessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0'; - const newOrExternal = !device.external ? 'Adding new' : 'Loading external'; - await this.infoLog(`${newOrExternal} accessory: ${accessory.displayName} deviceId: ${device.deviceId}`); + : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName) + accessory.context.connectionType = await this.connectionType(device) + accessory.context.connectionType = await this.connectionType(device) + accessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0' + const newOrExternal = !device.external ? 'Adding new' : 'Loading external' + await this.infoLog(`${newOrExternal} accessory: ${accessory.displayName} deviceId: ${device.deviceId}`) // create the accessory handler for the newly create accessory // this is imported from `platformAccessory.ts` - new Plug(this, accessory, device); - await this.debugLog(`${device.deviceType} uuid: ${device.deviceId}-${device.deviceType}, (${accessory.UUID})`); + new Plug(this, accessory, device) + await this.debugLog(`${device.deviceType} uuid: ${device.deviceId}-${device.deviceType}, (${accessory.UUID})`) // publish device externally or link the accessory to your platform - this.externalOrPlatform(device, accessory); - this.accessories.push(accessory); + this.externalOrPlatform(device, accessory) + this.accessories.push(accessory) } else { - await this.debugLog(`Device not registered: ${device.deviceName} ${device.deviceType} deviceId: ${device.deviceId}`); + await this.debugLog(`Device not registered: ${device.deviceName} ${device.deviceType} deviceId: ${device.deviceId}`) } } private async createLock(device: device & devicesConfig) { - const uuid = this.api.hap.uuid.generate(`${device.deviceId}-${device.deviceType}`); + const uuid = this.api.hap.uuid.generate(`${device.deviceId}-${device.deviceType}`) // see if an accessory with the same uuid has already been registered and restored from // the cached devices we stored in the `configureAccessory` method above - const existingAccessory = this.accessories.find((accessory) => accessory.UUID === uuid); + const existingAccessory = this.accessories.find(accessory => accessory.UUID === uuid) if (existingAccessory) { // the accessory already exists if (await this.registerDevice(device)) { // if you need to update the accessory.context then you should run `api.updatePlatformAccessories`. eg.: - existingAccessory.context.device = device; - existingAccessory.context.deviceId = device.deviceId; - existingAccessory.context.deviceType = device.deviceType; - existingAccessory.context.model = device.deviceType === 'Smart Lock Pro' ? SwitchBotModel.LockPro : SwitchBotModel.Lock; + existingAccessory.context.device = device + existingAccessory.context.deviceId = device.deviceId + existingAccessory.context.deviceType = device.deviceType + existingAccessory.context.model = device.deviceType === 'Smart Lock Pro' ? SwitchBotModel.LockPro : SwitchBotModel.Lock existingAccessory.displayName = device.configDeviceName ? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName) - : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName); - existingAccessory.context.connectionType = await this.connectionType(device); - existingAccessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0'; - this.infoLog(`Restoring existing accessory from cache: ${existingAccessory.displayName} deviceId: ${device.deviceId}`); - this.api.updatePlatformAccessories([existingAccessory]); + : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName) + existingAccessory.context.connectionType = await this.connectionType(device) + existingAccessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0' + this.infoLog(`Restoring existing accessory from cache: ${existingAccessory.displayName} deviceId: ${device.deviceId}`) + this.api.updatePlatformAccessories([existingAccessory]) // create the accessory handler for the restored accessory // this is imported from `platformAccessory.ts` - new Lock(this, existingAccessory, device); - await this.debugLog(`${device.deviceType} uuid: ${device.deviceId}-${device.deviceType}, (${existingAccessory.UUID})`); + new Lock(this, existingAccessory, device) + await this.debugLog(`${device.deviceType} uuid: ${device.deviceId}-${device.deviceType}, (${existingAccessory.UUID})`) } else { - this.unregisterPlatformAccessories(existingAccessory); + this.unregisterPlatformAccessories(existingAccessory) } } else if (await this.registerDevice(device)) { // create a new accessory const accessory = new this.api.platformAccessory(device.configDeviceName ? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName) - : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName), uuid); + : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName), uuid) // store a copy of the device object in the `accessory.context` // the `context` property can be used to store any data about the accessory you may need - accessory.context.device = device; - accessory.context.deviceId = device.deviceId; - accessory.context.deviceType = device.deviceType; - accessory.context.model = device.deviceType === 'Smart Lock Pro' ? SwitchBotModel.LockPro : SwitchBotModel.Lock; + accessory.context.device = device + accessory.context.deviceId = device.deviceId + accessory.context.deviceType = device.deviceType + accessory.context.model = device.deviceType === 'Smart Lock Pro' ? SwitchBotModel.LockPro : SwitchBotModel.Lock accessory.displayName = device.configDeviceName ? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName) - : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName); - accessory.context.connectionType = await this.connectionType(device); - accessory.context.connectionType = await this.connectionType(device); - accessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0'; - const newOrExternal = !device.external ? 'Adding new' : 'Loading external'; - await this.infoLog(`${newOrExternal} accessory: ${accessory.displayName} deviceId: ${device.deviceId}`); + : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName) + accessory.context.connectionType = await this.connectionType(device) + accessory.context.connectionType = await this.connectionType(device) + accessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0' + const newOrExternal = !device.external ? 'Adding new' : 'Loading external' + await this.infoLog(`${newOrExternal} accessory: ${accessory.displayName} deviceId: ${device.deviceId}`) // create the accessory handler for the newly create accessory // this is imported from `platformAccessory.ts` - new Lock(this, accessory, device); - await this.debugLog(`${device.deviceType} uuid: ${device.deviceId}-${device.deviceType}, (${accessory.UUID})`); + new Lock(this, accessory, device) + await this.debugLog(`${device.deviceType} uuid: ${device.deviceId}-${device.deviceType}, (${accessory.UUID})`) // publish device externally or link the accessory to your platform - this.externalOrPlatform(device, accessory); - this.accessories.push(accessory); + this.externalOrPlatform(device, accessory) + this.accessories.push(accessory) } else { - await this.debugLog(`Device not registered: ${device.deviceName} ${device.deviceType} deviceId: ${device.deviceId}`); + await this.debugLog(`Device not registered: ${device.deviceName} ${device.deviceType} deviceId: ${device.deviceId}`) } } private async createColorBulb(device: device & devicesConfig) { - const uuid = this.api.hap.uuid.generate(`${device.deviceId}-${device.deviceType}`); + const uuid = this.api.hap.uuid.generate(`${device.deviceId}-${device.deviceType}`) // see if an accessory with the same uuid has already been registered and restored from // the cached devices we stored in the `configureAccessory` method above - const existingAccessory = this.accessories.find((accessory) => accessory.UUID === uuid); + const existingAccessory = this.accessories.find(accessory => accessory.UUID === uuid) if (existingAccessory) { // the accessory already exists if (await this.registerDevice(device)) { // if you need to update the accessory.context then you should run `api.updatePlatformAccessories`. eg.: - existingAccessory.context.device = device; - existingAccessory.context.deviceId = device.deviceId; - existingAccessory.context.deviceType = device.deviceType; - existingAccessory.context.model = SwitchBotModel.ColorBulb; + existingAccessory.context.device = device + existingAccessory.context.deviceId = device.deviceId + existingAccessory.context.deviceType = device.deviceType + existingAccessory.context.model = SwitchBotModel.ColorBulb existingAccessory.displayName = device.configDeviceName ? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName) - : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName); - existingAccessory.context.connectionType = await this.connectionType(device); - existingAccessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0'; - await this.infoLog(`Restoring existing accessory from cache: ${existingAccessory.displayName} deviceId: ${device.deviceId}`); - this.api.updatePlatformAccessories([existingAccessory]); + : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName) + existingAccessory.context.connectionType = await this.connectionType(device) + existingAccessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0' + await this.infoLog(`Restoring existing accessory from cache: ${existingAccessory.displayName} deviceId: ${device.deviceId}`) + this.api.updatePlatformAccessories([existingAccessory]) // create the accessory handler for the restored accessory // this is imported from `platformAccessory.ts` - new ColorBulb(this, existingAccessory, device); - await this.debugLog(`${device.deviceType} uuid: ${device.deviceId}-${device.deviceType}, (${existingAccessory.UUID})`); + new ColorBulb(this, existingAccessory, device) + await this.debugLog(`${device.deviceType} uuid: ${device.deviceId}-${device.deviceType}, (${existingAccessory.UUID})`) } else { - this.unregisterPlatformAccessories(existingAccessory); + this.unregisterPlatformAccessories(existingAccessory) } } else if (await this.registerDevice(device)) { // create a new accessory const accessory = new this.api.platformAccessory(device.configDeviceName ? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName) - : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName), uuid); + : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName), uuid) // store a copy of the device object in the `accessory.context` // the `context` property can be used to store any data about the accessory you may need - accessory.context.device = device; - accessory.context.deviceId = device.deviceId; - accessory.context.deviceType = device.deviceType; - accessory.context.model = SwitchBotModel.ColorBulb; + accessory.context.device = device + accessory.context.deviceId = device.deviceId + accessory.context.deviceType = device.deviceType + accessory.context.model = SwitchBotModel.ColorBulb accessory.displayName = device.configDeviceName ? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName) - : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName); - accessory.context.connectionType = await this.connectionType(device); - accessory.context.connectionType = await this.connectionType(device); - accessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0'; - const newOrExternal = !device.external ? 'Adding new' : 'Loading external'; - await this.infoLog(`${newOrExternal} accessory: ${accessory.displayName} deviceId: ${device.deviceId}`); + : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName) + accessory.context.connectionType = await this.connectionType(device) + accessory.context.connectionType = await this.connectionType(device) + accessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0' + const newOrExternal = !device.external ? 'Adding new' : 'Loading external' + await this.infoLog(`${newOrExternal} accessory: ${accessory.displayName} deviceId: ${device.deviceId}`) // create the accessory handler for the newly create accessory // this is imported from `platformAccessory.ts` - new ColorBulb(this, accessory, device); - await this.debugLog(`${device.deviceType} uuid: ${device.deviceId}-${device.deviceType}, (${accessory.UUID})`); + new ColorBulb(this, accessory, device) + await this.debugLog(`${device.deviceType} uuid: ${device.deviceId}-${device.deviceType}, (${accessory.UUID})`) // publish device externally or link the accessory to your platform - this.externalOrPlatform(device, accessory); - this.accessories.push(accessory); + this.externalOrPlatform(device, accessory) + this.accessories.push(accessory) } else { - await this.debugLog(`Device not registered: ${device.deviceName} ${device.deviceType} deviceId: ${device.deviceId}`); + await this.debugLog(`Device not registered: ${device.deviceName} ${device.deviceType} deviceId: ${device.deviceId}`) } } private async createCeilingLight(device: device & devicesConfig) { - const uuid = this.api.hap.uuid.generate(`${device.deviceId}-${device.deviceType}`); + const uuid = this.api.hap.uuid.generate(`${device.deviceId}-${device.deviceType}`) // see if an accessory with the same uuid has already been registered and restored from // the cached devices we stored in the `configureAccessory` method above - const existingAccessory = this.accessories.find((accessory) => accessory.UUID === uuid); + const existingAccessory = this.accessories.find(accessory => accessory.UUID === uuid) if (existingAccessory) { // the accessory already exists if (await this.registerDevice(device)) { // if you need to update the accessory.context then you should run `api.updatePlatformAccessories`. eg.: - existingAccessory.context.device = device; - existingAccessory.context.deviceId = device.deviceId; - existingAccessory.context.deviceType = device.deviceType; - existingAccessory.context.model = device.deviceType === 'Ceiling Light Pro' ? SwitchBotModel.CeilingLightPro : SwitchBotModel.CeilingLight; + existingAccessory.context.device = device + existingAccessory.context.deviceId = device.deviceId + existingAccessory.context.deviceType = device.deviceType + existingAccessory.context.model = device.deviceType === 'Ceiling Light Pro' ? SwitchBotModel.CeilingLightPro : SwitchBotModel.CeilingLight existingAccessory.displayName = device.configDeviceName ? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName) - : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName); - existingAccessory.context.connectionType = await this.connectionType(device); - existingAccessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0'; - await this.infoLog(`Restoring existing accessory from cache: ${existingAccessory.displayName} deviceId: ${device.deviceId}`); - this.api.updatePlatformAccessories([existingAccessory]); + : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName) + existingAccessory.context.connectionType = await this.connectionType(device) + existingAccessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0' + await this.infoLog(`Restoring existing accessory from cache: ${existingAccessory.displayName} deviceId: ${device.deviceId}`) + this.api.updatePlatformAccessories([existingAccessory]) // create the accessory handler for the restored accessory // this is imported from `platformAccessory.ts` - new CeilingLight(this, existingAccessory, device); - await this.debugLog(`${device.deviceType} uuid: ${device.deviceId}-${device.deviceType}, (${existingAccessory.UUID})`); + new CeilingLight(this, existingAccessory, device) + await this.debugLog(`${device.deviceType} uuid: ${device.deviceId}-${device.deviceType}, (${existingAccessory.UUID})`) } else { - this.unregisterPlatformAccessories(existingAccessory); + this.unregisterPlatformAccessories(existingAccessory) } } else if (await this.registerDevice(device)) { // create a new accessory const accessory = new this.api.platformAccessory(device.configDeviceName ? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName) - : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName), uuid); + : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName), uuid) // store a copy of the device object in the `accessory.context` // the `context` property can be used to store any data about the accessory you may need - accessory.context.device = device; - accessory.context.deviceId = device.deviceId; - accessory.context.deviceType = device.deviceType; - accessory.context.model = device.deviceType === 'Ceiling Light Pro' ? SwitchBotModel.CeilingLightPro : SwitchBotModel.CeilingLight; + accessory.context.device = device + accessory.context.deviceId = device.deviceId + accessory.context.deviceType = device.deviceType + accessory.context.model = device.deviceType === 'Ceiling Light Pro' ? SwitchBotModel.CeilingLightPro : SwitchBotModel.CeilingLight accessory.displayName = device.configDeviceName ? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName) - : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName); - accessory.context.connectionType = await this.connectionType(device); - accessory.context.connectionType = await this.connectionType(device); - accessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0'; - const newOrExternal = !device.external ? 'Adding new' : 'Loading external'; - await this.infoLog(`${newOrExternal} accessory: ${accessory.displayName} deviceId: ${device.deviceId}`); + : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName) + accessory.context.connectionType = await this.connectionType(device) + accessory.context.connectionType = await this.connectionType(device) + accessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0' + const newOrExternal = !device.external ? 'Adding new' : 'Loading external' + await this.infoLog(`${newOrExternal} accessory: ${accessory.displayName} deviceId: ${device.deviceId}`) // create the accessory handler for the newly create accessory // this is imported from `platformAccessory.ts` - new CeilingLight(this, accessory, device); - await this.debugLog(`${device.deviceType} uuid: ${device.deviceId}-${device.deviceType}, (${accessory.UUID})`); + new CeilingLight(this, accessory, device) + await this.debugLog(`${device.deviceType} uuid: ${device.deviceId}-${device.deviceType}, (${accessory.UUID})`) // publish device externally or link the accessory to your platform - this.externalOrPlatform(device, accessory); - this.accessories.push(accessory); + this.externalOrPlatform(device, accessory) + this.accessories.push(accessory) } else { - await this.debugLog(`Device not registered: ${device.deviceName} ${device.deviceType} deviceId: ${device.deviceId}`); + await this.debugLog(`Device not registered: ${device.deviceName} ${device.deviceType} deviceId: ${device.deviceId}`) } } private async createStripLight(device: device & devicesConfig) { - const uuid = this.api.hap.uuid.generate(`${device.deviceId}-${device.deviceType}`); + const uuid = this.api.hap.uuid.generate(`${device.deviceId}-${device.deviceType}`) // see if an accessory with the same uuid has already been registered and restored from // the cached devices we stored in the `configureAccessory` method above - const existingAccessory = this.accessories.find((accessory) => accessory.UUID === uuid); + const existingAccessory = this.accessories.find(accessory => accessory.UUID === uuid) if (existingAccessory) { // the accessory already exists if (await this.registerDevice(device)) { // if you need to update the accessory.context then you should run `api.updatePlatformAccessories`. eg.: - existingAccessory.context.device = device; - existingAccessory.context.deviceId = device.deviceId; - existingAccessory.context.deviceType = device.deviceType; - existingAccessory.context.model = SwitchBotModel.StripLight; + existingAccessory.context.device = device + existingAccessory.context.deviceId = device.deviceId + existingAccessory.context.deviceType = device.deviceType + existingAccessory.context.model = SwitchBotModel.StripLight existingAccessory.displayName = device.configDeviceName ? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName) - : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName); - existingAccessory.context.connectionType = await this.connectionType(device); - existingAccessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0'; - await this.infoLog(`Restoring existing accessory from cache: ${existingAccessory.displayName} deviceId: ${device.deviceId}`); - this.api.updatePlatformAccessories([existingAccessory]); + : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName) + existingAccessory.context.connectionType = await this.connectionType(device) + existingAccessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0' + await this.infoLog(`Restoring existing accessory from cache: ${existingAccessory.displayName} deviceId: ${device.deviceId}`) + this.api.updatePlatformAccessories([existingAccessory]) // create the accessory handler for the restored accessory // this is imported from `platformAccessory.ts` - new StripLight(this, existingAccessory, device); - await this.debugLog(`${device.deviceType} uuid: ${device.deviceId}-${device.deviceType}, (${existingAccessory.UUID})`); + new StripLight(this, existingAccessory, device) + await this.debugLog(`${device.deviceType} uuid: ${device.deviceId}-${device.deviceType}, (${existingAccessory.UUID})`) } else { - this.unregisterPlatformAccessories(existingAccessory); + this.unregisterPlatformAccessories(existingAccessory) } } else if (await this.registerDevice(device)) { // create a new accessory const accessory = new this.api.platformAccessory(device.configDeviceName ? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName) - : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName), uuid); + : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName), uuid) // store a copy of the device object in the `accessory.context` // the `context` property can be used to store any data about the accessory you may need - accessory.context.device = device; - accessory.context.deviceId = device.deviceId; - accessory.context.deviceType = device.deviceType; - accessory.context.model = SwitchBotModel.StripLight; + accessory.context.device = device + accessory.context.deviceId = device.deviceId + accessory.context.deviceType = device.deviceType + accessory.context.model = SwitchBotModel.StripLight accessory.displayName = device.configDeviceName ? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName) - : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName); - accessory.context.connectionType = await this.connectionType(device); - accessory.context.connectionType = await this.connectionType(device); - accessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0'; - const newOrExternal = !device.external ? 'Adding new' : 'Loading external'; - await this.infoLog(`${newOrExternal} accessory: ${accessory.displayName} deviceId: ${device.deviceId}`); + : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName) + accessory.context.connectionType = await this.connectionType(device) + accessory.context.connectionType = await this.connectionType(device) + accessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0' + const newOrExternal = !device.external ? 'Adding new' : 'Loading external' + await this.infoLog(`${newOrExternal} accessory: ${accessory.displayName} deviceId: ${device.deviceId}`) // create the accessory handler for the newly create accessory // this is imported from `platformAccessory.ts` - new StripLight(this, accessory, device); - await this.debugLog(`${device.deviceType} uuid: ${device.deviceId}-${device.deviceType}, (${accessory.UUID})`); + new StripLight(this, accessory, device) + await this.debugLog(`${device.deviceType} uuid: ${device.deviceId}-${device.deviceType}, (${accessory.UUID})`) // publish device externally or link the accessory to your platform - this.externalOrPlatform(device, accessory); - this.accessories.push(accessory); + this.externalOrPlatform(device, accessory) + this.accessories.push(accessory) } else { - await this.debugLog(`Device not registered: ${device.deviceName} ${device.deviceType} deviceId: ${device.deviceId}`); + await this.debugLog(`Device not registered: ${device.deviceName} ${device.deviceType} deviceId: ${device.deviceId}`) } } private async createFan(device: device & devicesConfig) { - const uuid = this.api.hap.uuid.generate(`${device.deviceId}-${device.deviceType}`); + const uuid = this.api.hap.uuid.generate(`${device.deviceId}-${device.deviceType}`) // see if an accessory with the same uuid has already been registered and restored from // the cached devices we stored in the `configureAccessory` method above - const existingAccessory = this.accessories.find((accessory) => accessory.UUID === uuid); + const existingAccessory = this.accessories.find(accessory => accessory.UUID === uuid) if (existingAccessory) { // the accessory already exists if (await this.registerDevice(device)) { // if you need to update the accessory.context then you should run `api.updatePlatformAccessories`. eg.: - existingAccessory.context.device = device; - existingAccessory.context.deviceId = device.deviceId; - existingAccessory.context.deviceType = device.deviceType; - existingAccessory.context.model = SwitchBotModel.BatteryCirculatorFan; + existingAccessory.context.device = device + existingAccessory.context.deviceId = device.deviceId + existingAccessory.context.deviceType = device.deviceType + existingAccessory.context.model = SwitchBotModel.BatteryCirculatorFan existingAccessory.displayName = device.configDeviceName ? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName) - : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName); - existingAccessory.context.connectionType = await this.connectionType(device); - existingAccessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0'; - await this.infoLog(`Restoring existing accessory from cache: ${existingAccessory.displayName} deviceId: ${device.deviceId}`); - this.api.updatePlatformAccessories([existingAccessory]); + : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName) + existingAccessory.context.connectionType = await this.connectionType(device) + existingAccessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0' + await this.infoLog(`Restoring existing accessory from cache: ${existingAccessory.displayName} deviceId: ${device.deviceId}`) + this.api.updatePlatformAccessories([existingAccessory]) // create the accessory handler for the restored accessory // this is imported from `platformAccessory.ts` - new Fan(this, existingAccessory, device); - await this.debugLog(`${device.deviceType} uuid: ${device.deviceId}-${device.deviceType}, (${existingAccessory.UUID})`); + new Fan(this, existingAccessory, device) + await this.debugLog(`${device.deviceType} uuid: ${device.deviceId}-${device.deviceType}, (${existingAccessory.UUID})`) } else { - this.unregisterPlatformAccessories(existingAccessory); + this.unregisterPlatformAccessories(existingAccessory) } } else if (await this.registerDevice(device)) { // create a new accessory const accessory = new this.api.platformAccessory(device.configDeviceName ? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName) - : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName), uuid); + : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName), uuid) // store a copy of the device object in the `accessory.context` // the `context` property can be used to store any data about the accessory you may need - accessory.context.device = device; - accessory.context.deviceId = device.deviceId; - accessory.context.deviceType = device.deviceType; - accessory.context.model = SwitchBotModel.BatteryCirculatorFan; + accessory.context.device = device + accessory.context.deviceId = device.deviceId + accessory.context.deviceType = device.deviceType + accessory.context.model = SwitchBotModel.BatteryCirculatorFan accessory.displayName = device.configDeviceName ? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName) - : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName); - accessory.context.connectionType = await this.connectionType(device); - accessory.context.connectionType = await this.connectionType(device); - accessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0'; - const newOrExternal = !device.external ? 'Adding new' : 'Loading external'; - await this.infoLog(`${newOrExternal} accessory: ${accessory.displayName} deviceId: ${device.deviceId}`); + : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName) + accessory.context.connectionType = await this.connectionType(device) + accessory.context.connectionType = await this.connectionType(device) + accessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0' + const newOrExternal = !device.external ? 'Adding new' : 'Loading external' + await this.infoLog(`${newOrExternal} accessory: ${accessory.displayName} deviceId: ${device.deviceId}`) // create the accessory handler for the newly create accessory // this is imported from `platformAccessory.ts` - new Fan(this, accessory, device); - await this.debugLog(`${device.deviceType} uuid: ${device.deviceId}-${device.deviceType}, (${accessory.UUID})`); + new Fan(this, accessory, device) + await this.debugLog(`${device.deviceType} uuid: ${device.deviceId}-${device.deviceType}, (${accessory.UUID})`) // publish device externally or link the accessory to your platform - this.externalOrPlatform(device, accessory); - this.accessories.push(accessory); + this.externalOrPlatform(device, accessory) + this.accessories.push(accessory) } else { - await this.debugLog(`Device not registered: ${device.deviceName} ${device.deviceType} deviceId: ${device.deviceId}`); + await this.debugLog(`Device not registered: ${device.deviceName} ${device.deviceType} deviceId: ${device.deviceId}`) } } private async createRobotVacuumCleaner(device: device & devicesConfig) { - const uuid = this.api.hap.uuid.generate(`${device.deviceId}-${device.deviceType}`); + const uuid = this.api.hap.uuid.generate(`${device.deviceId}-${device.deviceType}`) // see if an accessory with the same uuid has already been registered and restored from // the cached devices we stored in the `configureAccessory` method above - const existingAccessory = this.accessories.find((accessory) => accessory.UUID === uuid); + const existingAccessory = this.accessories.find(accessory => accessory.UUID === uuid) if (existingAccessory) { // the accessory already exists if (await this.registerDevice(device)) { // if you need to update the accessory.context then you should run `api.updatePlatformAccessories`. eg.: - existingAccessory.context.device = device; - existingAccessory.context.deviceId = device.deviceId; - existingAccessory.context.deviceType = device.deviceType; - existingAccessory.context.model = device.deviceType === 'Robot Vacuum Cleaner S1' ? SwitchBotModel.RobotVacuumCleanerS1 - : device.deviceType === 'Robot Vacuum Cleaner S1 Plus' ? SwitchBotModel.RobotVacuumCleanerS1Plus - : device.deviceType === 'Robot Vacuum Cleaner S10' ? SwitchBotModel.RobotVacuumCleanerS10 - : device.deviceType === 'WoSweeper' ? SwitchBotModel.WoSweeper : device.deviceType === 'WoSweeperMini' ? SwitchBotModel.WoSweeperMini - : SwitchBotModel.Unknown; + existingAccessory.context.device = device + existingAccessory.context.deviceId = device.deviceId + existingAccessory.context.deviceType = device.deviceType + existingAccessory.context.model = device.deviceType === 'Robot Vacuum Cleaner S1' + ? SwitchBotModel.RobotVacuumCleanerS1 + : device.deviceType === 'Robot Vacuum Cleaner S1 Plus' + ? SwitchBotModel.RobotVacuumCleanerS1Plus + : device.deviceType === 'Robot Vacuum Cleaner S10' + ? SwitchBotModel.RobotVacuumCleanerS10 + : device.deviceType === 'WoSweeper' + ? SwitchBotModel.WoSweeper + : device.deviceType === 'WoSweeperMini' + ? SwitchBotModel.WoSweeperMini + : SwitchBotModel.Unknown existingAccessory.displayName = device.configDeviceName ? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName) - : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName); - existingAccessory.context.connectionType = await this.connectionType(device); - existingAccessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0'; - await this.infoLog(`Restoring existing accessory from cache: ${existingAccessory.displayName} deviceId: ${device.deviceId}`); - this.api.updatePlatformAccessories([existingAccessory]); + : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName) + existingAccessory.context.connectionType = await this.connectionType(device) + existingAccessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0' + await this.infoLog(`Restoring existing accessory from cache: ${existingAccessory.displayName} deviceId: ${device.deviceId}`) + this.api.updatePlatformAccessories([existingAccessory]) // create the accessory handler for the restored accessory // this is imported from `platformAccessory.ts` - new RobotVacuumCleaner(this, existingAccessory, device); - await this.debugLog(`${device.deviceType} uuid: ${device.deviceId}-${device.deviceType}, (${existingAccessory.UUID})`); + new RobotVacuumCleaner(this, existingAccessory, device) + await this.debugLog(`${device.deviceType} uuid: ${device.deviceId}-${device.deviceType}, (${existingAccessory.UUID})`) } else { - this.unregisterPlatformAccessories(existingAccessory); + this.unregisterPlatformAccessories(existingAccessory) } } else if (await this.registerDevice(device)) { // create a new accessory const accessory = new this.api.platformAccessory(device.configDeviceName ? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName) - : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName), uuid); + : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName), uuid) // store a copy of the device object in the `accessory.context` // the `context` property can be used to store any data about the accessory you may need - accessory.context.device = device; - accessory.context.deviceId = device.deviceId; - accessory.context.deviceType = device.deviceType; - accessory.context.model = device.deviceType === 'Robot Vacuum Cleaner S1' ? SwitchBotModel.RobotVacuumCleanerS1 - : device.deviceType === 'Robot Vacuum Cleaner S1 Plus' ? SwitchBotModel.RobotVacuumCleanerS1Plus - : device.deviceType === 'Robot Vacuum Cleaner S10' ? SwitchBotModel.RobotVacuumCleanerS10 - : device.deviceType === 'WoSweeper' ? SwitchBotModel.WoSweeper : device.deviceType === 'WoSweeperMini' ? SwitchBotModel.WoSweeperMini - : SwitchBotModel.Unknown; + accessory.context.device = device + accessory.context.deviceId = device.deviceId + accessory.context.deviceType = device.deviceType + accessory.context.model = device.deviceType === 'Robot Vacuum Cleaner S1' + ? SwitchBotModel.RobotVacuumCleanerS1 + : device.deviceType === 'Robot Vacuum Cleaner S1 Plus' + ? SwitchBotModel.RobotVacuumCleanerS1Plus + : device.deviceType === 'Robot Vacuum Cleaner S10' + ? SwitchBotModel.RobotVacuumCleanerS10 + : device.deviceType === 'WoSweeper' + ? SwitchBotModel.WoSweeper + : device.deviceType === 'WoSweeperMini' + ? SwitchBotModel.WoSweeperMini + : SwitchBotModel.Unknown accessory.displayName = device.configDeviceName ? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName) - : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName); - accessory.context.connectionType = await this.connectionType(device); - accessory.context.connectionType = await this.connectionType(device); - accessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0'; - const newOrExternal = !device.external ? 'Adding new' : 'Loading external'; - await this.infoLog(`${newOrExternal} accessory: ${accessory.displayName} deviceId: ${device.deviceId}`); + : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName) + accessory.context.connectionType = await this.connectionType(device) + accessory.context.connectionType = await this.connectionType(device) + accessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0' + const newOrExternal = !device.external ? 'Adding new' : 'Loading external' + await this.infoLog(`${newOrExternal} accessory: ${accessory.displayName} deviceId: ${device.deviceId}`) // create the accessory handler for the newly create accessory // this is imported from `platformAccessory.ts` - new RobotVacuumCleaner(this, accessory, device); - await this.debugLog(`${device.deviceType} uuid: ${device.deviceId}-${device.deviceType}, (${accessory.UUID})`); + new RobotVacuumCleaner(this, accessory, device) + await this.debugLog(`${device.deviceType} uuid: ${device.deviceId}-${device.deviceType}, (${accessory.UUID})`) // publish device externally or link the accessory to your platform - this.externalOrPlatform(device, accessory); - this.accessories.push(accessory); + this.externalOrPlatform(device, accessory) + this.accessories.push(accessory) } else { - await this.debugLog(`Device not registered: ${device.deviceName} ${device.deviceType} deviceId: ${device.deviceId}`); + await this.debugLog(`Device not registered: ${device.deviceName} ${device.deviceType} deviceId: ${device.deviceId}`) } } private async createTV(device: irdevice & devicesConfig) { - const uuid = this.api.hap.uuid.generate(`${device.deviceId}-${device.remoteType}`); + const uuid = this.api.hap.uuid.generate(`${device.deviceId}-${device.remoteType}`) // see if an accessory with the same uuid has already been registered and restored from // the cached devices we stored in the `configureAccessory` method above - const existingAccessory = this.accessories.find((accessory) => accessory.UUID === uuid); + const existingAccessory = this.accessories.find(accessory => accessory.UUID === uuid) if (!device.hide_device && existingAccessory) { // if you need to update the accessory.context then you should run `api.updatePlatformAccessories`. eg.: - existingAccessory.context.device = device; - existingAccessory.context.deviceId = device.deviceId; - existingAccessory.context.deviceType = `IR: ${device.remoteType}`; - existingAccessory.context.model = device.remoteType; + existingAccessory.context.device = device + existingAccessory.context.deviceId = device.deviceId + existingAccessory.context.deviceType = `IR: ${device.remoteType}` + existingAccessory.context.model = device.remoteType existingAccessory.displayName = device.configDeviceName ? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName) - : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName); - await this.infoLog(`Restoring existing accessory from cache: ${existingAccessory.displayName} deviceId: ${device.deviceId}`); - existingAccessory.context.connectionType = device.connectionType; - this.api.updatePlatformAccessories([existingAccessory]); + : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName) + await this.infoLog(`Restoring existing accessory from cache: ${existingAccessory.displayName} deviceId: ${device.deviceId}`) + existingAccessory.context.connectionType = device.connectionType + this.api.updatePlatformAccessories([existingAccessory]) // create the accessory handler for the restored accessory // this is imported from `platformAccessory.ts` - new TV(this, existingAccessory, device); - await this.debugLog(`${device.remoteType} uuid: ${device.deviceId}-${device.remoteType}, (${existingAccessory.UUID})`); + new TV(this, existingAccessory, device) + await this.debugLog(`${device.remoteType} uuid: ${device.deviceId}-${device.remoteType}, (${existingAccessory.UUID})`) } else if (!device.hide_device && device.hubDeviceId) { // create a new accessory const accessory = new this.api.platformAccessory(device.configDeviceName ? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName) - : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName), uuid); + : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName), uuid) // store a copy of the device object in the `accessory.context` // the `context` property can be used to store any data about the accessory you may need - accessory.context.device = device; - accessory.context.deviceId = device.deviceId; - accessory.context.deviceType = `IR: ${device.remoteType}`; - accessory.context.model = device.remoteType; + accessory.context.device = device + accessory.context.deviceId = device.deviceId + accessory.context.deviceType = `IR: ${device.remoteType}` + accessory.context.model = device.remoteType accessory.displayName = device.configDeviceName ? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName) - : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName); - accessory.context.connectionType = await this.connectionType(device); - accessory.context.connectionType = device.connectionType; - accessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0'; - const newOrExternal = !device.external ? 'Adding new' : 'Loading external'; - await this.infoLog(`${newOrExternal} accessory: ${accessory.displayName} deviceId: ${device.deviceId}`); + : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName) + accessory.context.connectionType = await this.connectionType(device) + accessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0' + const newOrExternal = !device.external ? 'Adding new' : 'Loading external' + await this.infoLog(`${newOrExternal} accessory: ${accessory.displayName} deviceId: ${device.deviceId}`) // create the accessory handler for the newly create accessory // this is imported from `platformAccessory.ts` - new TV(this, accessory, device); - await this.debugLog(`${device.remoteType} uuid: ${device.deviceId}-${device.remoteType}, (${accessory.UUID})`); + new TV(this, accessory, device) + await this.debugLog(`${device.remoteType} uuid: ${device.deviceId}-${device.remoteType}, (${accessory.UUID})`) - this.externalOrPlatformIR(device, accessory); - this.accessories.push(accessory); + this.externalOrPlatform(device, accessory) + this.accessories.push(accessory) } else { - await this.debugLog(`Device not registered: ${device.deviceName} ${device.remoteType} deviceId: ${device.deviceId}`); + await this.debugLog(`Device not registered: ${device.deviceName} ${device.remoteType} deviceId: ${device.deviceId}`) } } private async createIRFan(device: irdevice & devicesConfig) { - const uuid = this.api.hap.uuid.generate(`${device.deviceId}-${device.remoteType}`); + const uuid = this.api.hap.uuid.generate(`${device.deviceId}-${device.remoteType}`) // see if an accessory with the same uuid has already been registered and restored from // the cached devices we stored in the `configureAccessory` method above - const existingAccessory = this.accessories.find((accessory) => accessory.UUID === uuid); + const existingAccessory = this.accessories.find(accessory => accessory.UUID === uuid) if (existingAccessory) { // the accessory already exists if (!device.hide_device && device.hubDeviceId) { // if you need to update the accessory.context then you should run `api.updatePlatformAccessories`. eg.: - existingAccessory.context.device = device; - existingAccessory.context.deviceId = device.deviceId; - existingAccessory.context.deviceType = `IR: ${device.remoteType}`; - existingAccessory.context.model = device.remoteType; + existingAccessory.context.device = device + existingAccessory.context.deviceId = device.deviceId + existingAccessory.context.deviceType = `IR: ${device.remoteType}` + existingAccessory.context.model = device.remoteType existingAccessory.displayName = device.configDeviceName ? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName) - : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName); - await this.infoLog(`Restoring existing accessory from cache: ${existingAccessory.displayName} deviceId: ${device.deviceId}`); - existingAccessory.context.connectionType = device.connectionType; - this.api.updatePlatformAccessories([existingAccessory]); + : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName) + await this.infoLog(`Restoring existing accessory from cache: ${existingAccessory.displayName} deviceId: ${device.deviceId}`) + existingAccessory.context.connectionType = device.connectionType + this.api.updatePlatformAccessories([existingAccessory]) // create the accessory handler for the restored accessory // this is imported from `platformAccessory.ts` - new IRFan(this, existingAccessory, device); - await this.debugLog(`${device.remoteType} uuid: ${device.deviceId}-${device.remoteType}, (${existingAccessory.UUID})`); + new IRFan(this, existingAccessory, device) + await this.debugLog(`${device.remoteType} uuid: ${device.deviceId}-${device.remoteType}, (${existingAccessory.UUID})`) } else { - this.unregisterPlatformAccessories(existingAccessory); + this.unregisterPlatformAccessories(existingAccessory) } } else if (!device.hide_device && device.hubDeviceId) { // create a new accessory const accessory = new this.api.platformAccessory(device.configDeviceName ? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName) - : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName), uuid); + : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName), uuid) // store a copy of the device object in the `accessory.context` // the `context` property can be used to store any data about the accessory you may need - accessory.context.device = device; - accessory.context.deviceId = device.deviceId; - accessory.context.deviceType = `IR: ${device.remoteType}`; - accessory.context.model = device.remoteType; + accessory.context.device = device + accessory.context.deviceId = device.deviceId + accessory.context.deviceType = `IR: ${device.remoteType}` + accessory.context.model = device.remoteType accessory.displayName = device.configDeviceName ? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName) - : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName); - accessory.context.connectionType = await this.connectionType(device); - accessory.context.connectionType = device.connectionType; - accessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0'; - const newOrExternal = !device.external ? 'Adding new' : 'Loading external'; - await this.infoLog(`${newOrExternal} accessory: ${accessory.displayName} deviceId: ${device.deviceId}`); + : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName) + accessory.context.connectionType = await this.connectionType(device) + accessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0' + const newOrExternal = !device.external ? 'Adding new' : 'Loading external' + await this.infoLog(`${newOrExternal} accessory: ${accessory.displayName} deviceId: ${device.deviceId}`) // create the accessory handler for the newly create accessory // this is imported from `platformAccessory.ts` - new IRFan(this, accessory, device); - await this.debugLog(`${device.remoteType} uuid: ${device.deviceId}-${device.remoteType}, (${accessory.UUID})`); + new IRFan(this, accessory, device) + await this.debugLog(`${device.remoteType} uuid: ${device.deviceId}-${device.remoteType}, (${accessory.UUID})`) // publish device externally or link the accessory to your platform - this.externalOrPlatformIR(device, accessory); - this.accessories.push(accessory); + this.externalOrPlatform(device, accessory) + this.accessories.push(accessory) } else { - await this.debugLog(`Device not registered: ${device.deviceName} ${device.remoteType} deviceId: ${device.deviceId}`); + await this.debugLog(`Device not registered: ${device.deviceName} ${device.remoteType} deviceId: ${device.deviceId}`) } } private async createLight(device: irdevice & devicesConfig) { - const uuid = this.api.hap.uuid.generate(`${device.deviceId}-${device.remoteType}`); + const uuid = this.api.hap.uuid.generate(`${device.deviceId}-${device.remoteType}`) // see if an accessory with the same uuid has already been registered and restored from // the cached devices we stored in the `configureAccessory` method above - const existingAccessory = this.accessories.find((accessory) => accessory.UUID === uuid); + const existingAccessory = this.accessories.find(accessory => accessory.UUID === uuid) if (existingAccessory) { // the accessory already exists if (!device.hide_device && device.hubDeviceId) { // if you need to update the accessory.context then you should run `api.updatePlatformAccessories`. eg.: - existingAccessory.context.device = device; - existingAccessory.context.deviceId = device.deviceId; - existingAccessory.context.deviceType = `IR: ${device.remoteType}`; - existingAccessory.context.model = device.remoteType; + existingAccessory.context.device = device + existingAccessory.context.deviceId = device.deviceId + existingAccessory.context.deviceType = `IR: ${device.remoteType}` + existingAccessory.context.model = device.remoteType existingAccessory.displayName = device.configDeviceName ? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName) - : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName); - await this.infoLog(`Restoring existing accessory from cache: ${existingAccessory.displayName} deviceId: ${device.deviceId}`); - existingAccessory.context.connectionType = device.connectionType; - this.api.updatePlatformAccessories([existingAccessory]); + : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName) + await this.infoLog(`Restoring existing accessory from cache: ${existingAccessory.displayName} deviceId: ${device.deviceId}`) + existingAccessory.context.connectionType = device.connectionType + this.api.updatePlatformAccessories([existingAccessory]) // create the accessory handler for the restored accessory // this is imported from `platformAccessory.ts` - new Light(this, existingAccessory, device); - await this.debugLog(`${device.remoteType} uuid: ${device.deviceId}-${device.remoteType}, (${existingAccessory.UUID})`); + new Light(this, existingAccessory, device) + await this.debugLog(`${device.remoteType} uuid: ${device.deviceId}-${device.remoteType}, (${existingAccessory.UUID})`) } else { - this.unregisterPlatformAccessories(existingAccessory); + this.unregisterPlatformAccessories(existingAccessory) } } else if (!device.hide_device && device.hubDeviceId) { // create a new accessory const accessory = new this.api.platformAccessory(device.configDeviceName ? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName) - : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName), uuid); + : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName), uuid) // store a copy of the device object in the `accessory.context` // the `context` property can be used to store any data about the accessory you may need - accessory.context.device = device; - accessory.context.deviceId = device.deviceId; - accessory.context.deviceType = `IR: ${device.remoteType}`; - accessory.context.model = device.remoteType; + accessory.context.device = device + accessory.context.deviceId = device.deviceId + accessory.context.deviceType = `IR: ${device.remoteType}` + accessory.context.model = device.remoteType accessory.displayName = device.configDeviceName ? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName) - : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName); - accessory.context.connectionType = await this.connectionType(device); - accessory.context.connectionType = device.connectionType; - accessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0'; - const newOrExternal = !device.external ? 'Adding new' : 'Loading external'; - await this.infoLog(`${newOrExternal} accessory: ${accessory.displayName} deviceId: ${device.deviceId}`); + : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName) + accessory.context.connectionType = await this.connectionType(device) + accessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0' + const newOrExternal = !device.external ? 'Adding new' : 'Loading external' + await this.infoLog(`${newOrExternal} accessory: ${accessory.displayName} deviceId: ${device.deviceId}`) // create the accessory handler for the newly create accessory // this is imported from `platformAccessory.ts` - new Light(this, accessory, device); - await this.debugLog(`${device.remoteType} uuid: ${device.deviceId}-${device.remoteType}, (${accessory.UUID})`); + new Light(this, accessory, device) + await this.debugLog(`${device.remoteType} uuid: ${device.deviceId}-${device.remoteType}, (${accessory.UUID})`) // publish device externally or link the accessory to your platform - this.externalOrPlatformIR(device, accessory); - this.accessories.push(accessory); + this.externalOrPlatform(device, accessory) + this.accessories.push(accessory) } else { - await this.debugLog(`Device not registered: ${device.deviceName} ${device.remoteType} deviceId: ${device.deviceId}`); + await this.debugLog(`Device not registered: ${device.deviceName} ${device.remoteType} deviceId: ${device.deviceId}`) } } private async createAirConditioner(device: irdevice & devicesConfig & irDevicesConfig) { - const uuid = this.api.hap.uuid.generate(`${device.deviceId}-${device.remoteType}`); + const uuid = this.api.hap.uuid.generate(`${device.deviceId}-${device.remoteType}`) // see if an accessory with the same uuid has already been registered and restored from // the cached devices we stored in the `configureAccessory` method above - const existingAccessory = this.accessories.find((accessory) => accessory.UUID === uuid); + const existingAccessory = this.accessories.find(accessory => accessory.UUID === uuid) if (existingAccessory) { // the accessory already exists if (!device.hide_device && device.hubDeviceId) { // if you need to update the accessory.context then you should run `api.updatePlatformAccessories`. eg.: - existingAccessory.context.device = device; - existingAccessory.context.deviceId = device.deviceId; - existingAccessory.context.deviceType = `IR: ${device.remoteType}`; - existingAccessory.context.model = device.remoteType; + existingAccessory.context.device = device + existingAccessory.context.deviceId = device.deviceId + existingAccessory.context.deviceType = `IR: ${device.remoteType}` + existingAccessory.context.model = device.remoteType existingAccessory.displayName = device.configDeviceName ? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName) - : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName); - await this.infoLog(`Restoring existing accessory from cache: ${existingAccessory.displayName} deviceId: ${device.deviceId}`); - existingAccessory.context.connectionType = device.connectionType; - this.api.updatePlatformAccessories([existingAccessory]); + : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName) + await this.infoLog(`Restoring existing accessory from cache: ${existingAccessory.displayName} deviceId: ${device.deviceId}`) + existingAccessory.context.connectionType = device.connectionType + this.api.updatePlatformAccessories([existingAccessory]) // create the accessory handler for the restored accessory // this is imported from `platformAccessory.ts` - new AirConditioner(this, existingAccessory, device); - await this.debugLog(`${device.remoteType} uuid: ${device.deviceId}-${device.remoteType}, (${existingAccessory.UUID})`); + new AirConditioner(this, existingAccessory, device) + await this.debugLog(`${device.remoteType} uuid: ${device.deviceId}-${device.remoteType}, (${existingAccessory.UUID})`) } else { - this.unregisterPlatformAccessories(existingAccessory); + this.unregisterPlatformAccessories(existingAccessory) } } else if (!device.hide_device && device.hubDeviceId) { // create a new accessory const accessory = new this.api.platformAccessory(device.configDeviceName ? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName) - : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName), uuid); + : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName), uuid) // store a copy of the device object in the `accessory.context` // the `context` property can be used to store any data about the accessory you may need - accessory.context.device = device; - accessory.context.deviceId = device.deviceId; - accessory.context.deviceType = `IR: ${device.remoteType}`; - accessory.context.model = device.remoteType; + accessory.context.device = device + accessory.context.deviceId = device.deviceId + accessory.context.deviceType = `IR: ${device.remoteType}` + accessory.context.model = device.remoteType accessory.displayName = device.configDeviceName ? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName) - : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName); - accessory.context.connectionType = await this.connectionType(device); - accessory.context.connectionType = device.connectionType; - accessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0'; - const newOrExternal = !device.external ? 'Adding new' : 'Loading external'; - await this.infoLog(`${newOrExternal} accessory: ${accessory.displayName} deviceId: ${device.deviceId}`); + : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName) + accessory.context.connectionType = await this.connectionType(device) + accessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0' + const newOrExternal = !device.external ? 'Adding new' : 'Loading external' + await this.infoLog(`${newOrExternal} accessory: ${accessory.displayName} deviceId: ${device.deviceId}`) // create the accessory handler for the newly create accessory // this is imported from `platformAccessory.ts` - new AirConditioner(this, accessory, device); - await this.debugLog(`${device.remoteType} uuid: ${device.deviceId}-${device.remoteType}, (${accessory.UUID})`); + new AirConditioner(this, accessory, device) + await this.debugLog(`${device.remoteType} uuid: ${device.deviceId}-${device.remoteType}, (${accessory.UUID})`) // publish device externally or link the accessory to your platform - this.externalOrPlatformIR(device, accessory); - this.accessories.push(accessory); + this.externalOrPlatform(device, accessory) + this.accessories.push(accessory) } else { - await this.debugLog(`Device not registered: ${device.deviceName} ${device.remoteType} deviceId: ${device.deviceId}`); + await this.debugLog(`Device not registered: ${device.deviceName} ${device.remoteType} deviceId: ${device.deviceId}`) } } private async createAirPurifier(device: irdevice & devicesConfig) { - const uuid = this.api.hap.uuid.generate(`${device.deviceId}-${device.remoteType}`); + const uuid = this.api.hap.uuid.generate(`${device.deviceId}-${device.remoteType}`) // see if an accessory with the same uuid has already been registered and restored from // the cached devices we stored in the `configureAccessory` method above - const existingAccessory = this.accessories.find((accessory) => accessory.UUID === uuid); + const existingAccessory = this.accessories.find(accessory => accessory.UUID === uuid) if (existingAccessory) { // the accessory already exists if (!device.hide_device && device.hubDeviceId) { // if you need to update the accessory.context then you should run `api.updatePlatformAccessories`. eg.: - existingAccessory.context.device = device; - existingAccessory.context.deviceId = device.deviceId; - existingAccessory.context.deviceType = `IR: ${device.remoteType}`; - existingAccessory.context.model = device.remoteType; + existingAccessory.context.device = device + existingAccessory.context.deviceId = device.deviceId + existingAccessory.context.deviceType = `IR: ${device.remoteType}` + existingAccessory.context.model = device.remoteType existingAccessory.displayName = device.configDeviceName ? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName) - : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName); - await this.infoLog(`Restoring existing accessory from cache: ${existingAccessory.displayName} deviceId: ${device.deviceId}`); - existingAccessory.context.connectionType = device.connectionType; - this.api.updatePlatformAccessories([existingAccessory]); + : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName) + await this.infoLog(`Restoring existing accessory from cache: ${existingAccessory.displayName} deviceId: ${device.deviceId}`) + existingAccessory.context.connectionType = device.connectionType + this.api.updatePlatformAccessories([existingAccessory]) // create the accessory handler for the restored accessory // this is imported from `platformAccessory.ts` - new AirPurifier(this, existingAccessory, device); - await this.debugLog(`${device.remoteType} uuid: ${device.deviceId}-${device.remoteType}, (${existingAccessory.UUID})`); + new AirPurifier(this, existingAccessory, device) + await this.debugLog(`${device.remoteType} uuid: ${device.deviceId}-${device.remoteType}, (${existingAccessory.UUID})`) } else { - this.unregisterPlatformAccessories(existingAccessory); + this.unregisterPlatformAccessories(existingAccessory) } } else if (!device.hide_device && device.hubDeviceId) { // create a new accessory const accessory = new this.api.platformAccessory(device.configDeviceName ? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName) - : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName), uuid); + : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName), uuid) // store a copy of the device object in the `accessory.context` // the `context` property can be used to store any data about the accessory you may need - accessory.context.device = device; - accessory.context.deviceId = device.deviceId; - accessory.context.deviceType = `IR: ${device.remoteType}`; - accessory.context.model = device.remoteType; + accessory.context.device = device + accessory.context.deviceId = device.deviceId + accessory.context.deviceType = `IR: ${device.remoteType}` + accessory.context.model = device.remoteType accessory.displayName = device.configDeviceName ? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName) - : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName); - accessory.context.connectionType = await this.connectionType(device); - accessory.context.connectionType = device.connectionType; - accessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0'; - const newOrExternal = !device.external ? 'Adding new' : 'Loading external'; - await this.infoLog(`${newOrExternal} accessory: ${accessory.displayName} deviceId: ${device.deviceId}`); + : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName) + accessory.context.connectionType = await this.connectionType(device) + accessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0' + const newOrExternal = !device.external ? 'Adding new' : 'Loading external' + await this.infoLog(`${newOrExternal} accessory: ${accessory.displayName} deviceId: ${device.deviceId}`) // create the accessory handler for the newly create accessory // this is imported from `platformAccessory.ts` - new AirPurifier(this, accessory, device); - await this.debugLog(`${device.remoteType} uuid: ${device.deviceId}-${device.remoteType}, (${accessory.UUID})`); + new AirPurifier(this, accessory, device) + await this.debugLog(`${device.remoteType} uuid: ${device.deviceId}-${device.remoteType}, (${accessory.UUID})`) // publish device externally or link the accessory to your platform - this.externalOrPlatformIR(device, accessory); - this.accessories.push(accessory); + this.externalOrPlatform(device, accessory) + this.accessories.push(accessory) } else { - await this.debugLog(`Device not registered: ${device.deviceName} ${device.remoteType} deviceId: ${device.deviceId}`); + await this.debugLog(`Device not registered: ${device.deviceName} ${device.remoteType} deviceId: ${device.deviceId}`) } } private async createWaterHeater(device: irdevice & devicesConfig) { - const uuid = this.api.hap.uuid.generate(`${device.deviceId}-${device.remoteType}`); + const uuid = this.api.hap.uuid.generate(`${device.deviceId}-${device.remoteType}`) // see if an accessory with the same uuid has already been registered and restored from // the cached devices we stored in the `configureAccessory` method above - const existingAccessory = this.accessories.find((accessory) => accessory.UUID === uuid); + const existingAccessory = this.accessories.find(accessory => accessory.UUID === uuid) if (existingAccessory) { // the accessory already exists if (!device.hide_device && device.hubDeviceId) { // if you need to update the accessory.context then you should run `api.updatePlatformAccessories`. eg.: - existingAccessory.context.device = device; - existingAccessory.context.deviceId = device.deviceId; - existingAccessory.context.deviceType = `IR: ${device.remoteType}`; - existingAccessory.context.model = device.remoteType; + existingAccessory.context.device = device + existingAccessory.context.deviceId = device.deviceId + existingAccessory.context.deviceType = `IR: ${device.remoteType}` + existingAccessory.context.model = device.remoteType existingAccessory.displayName = device.configDeviceName ? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName) - : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName); - await this.infoLog(`Restoring existing accessory from cache: ${existingAccessory.displayName} deviceId: ${device.deviceId}`); - existingAccessory.context.connectionType = device.connectionType; - this.api.updatePlatformAccessories([existingAccessory]); + : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName) + await this.infoLog(`Restoring existing accessory from cache: ${existingAccessory.displayName} deviceId: ${device.deviceId}`) + existingAccessory.context.connectionType = device.connectionType + this.api.updatePlatformAccessories([existingAccessory]) // create the accessory handler for the restored accessory // this is imported from `platformAccessory.ts` - new WaterHeater(this, existingAccessory, device); - await this.debugLog(`${device.remoteType} uuid: ${device.deviceId}-${device.remoteType}, (${existingAccessory.UUID})`); + new WaterHeater(this, existingAccessory, device) + await this.debugLog(`${device.remoteType} uuid: ${device.deviceId}-${device.remoteType}, (${existingAccessory.UUID})`) } else { - this.unregisterPlatformAccessories(existingAccessory); + this.unregisterPlatformAccessories(existingAccessory) } } else if (!device.hide_device && device.hubDeviceId) { // create a new accessory const accessory = new this.api.platformAccessory(device.configDeviceName ? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName) - : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName), uuid); + : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName), uuid) // store a copy of the device object in the `accessory.context` // the `context` property can be used to store any data about the accessory you may need - accessory.context.device = device; - accessory.context.deviceId = device.deviceId; - accessory.context.deviceType = `IR: ${device.remoteType}`; - accessory.context.model = device.remoteType; + accessory.context.device = device + accessory.context.deviceId = device.deviceId + accessory.context.deviceType = `IR: ${device.remoteType}` + accessory.context.model = device.remoteType accessory.displayName = device.configDeviceName ? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName) - : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName); - accessory.context.connectionType = await this.connectionType(device); - accessory.context.connectionType = device.connectionType; - accessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0'; - const newOrExternal = !device.external ? 'Adding new' : 'Loading external'; - await this.infoLog(`${newOrExternal} accessory: ${accessory.displayName} deviceId: ${device.deviceId}`); + : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName) + accessory.context.connectionType = await this.connectionType(device) + accessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0' + const newOrExternal = !device.external ? 'Adding new' : 'Loading external' + await this.infoLog(`${newOrExternal} accessory: ${accessory.displayName} deviceId: ${device.deviceId}`) // create the accessory handler for the newly create accessory // this is imported from `platformAccessory.ts` - new WaterHeater(this, accessory, device); - await this.debugLog(`${device.remoteType} uuid: ${device.deviceId}-${device.remoteType}, (${accessory.UUID})`); + new WaterHeater(this, accessory, device) + await this.debugLog(`${device.remoteType} uuid: ${device.deviceId}-${device.remoteType}, (${accessory.UUID})`) // publish device externally or link the accessory to your platform - this.externalOrPlatformIR(device, accessory); - this.accessories.push(accessory); + this.externalOrPlatform(device, accessory) + this.accessories.push(accessory) } else { - await this.debugLog(`Device not registered: ${device.deviceName} ${device.remoteType} deviceId: ${device.deviceId}`); + await this.debugLog(`Device not registered: ${device.deviceName} ${device.remoteType} deviceId: ${device.deviceId}`) } } private async createVacuumCleaner(device: irdevice & devicesConfig) { - const uuid = this.api.hap.uuid.generate(`${device.deviceId}-${device.remoteType}`); + const uuid = this.api.hap.uuid.generate(`${device.deviceId}-${device.remoteType}`) // see if an accessory with the same uuid has already been registered and restored from // the cached devices we stored in the `configureAccessory` method above - const existingAccessory = this.accessories.find((accessory) => accessory.UUID === uuid); + const existingAccessory = this.accessories.find(accessory => accessory.UUID === uuid) if (existingAccessory) { // the accessory already exists if (!device.hide_device && device.hubDeviceId) { // if you need to update the accessory.context then you should run `api.updatePlatformAccessories`. eg.: - existingAccessory.context.device = device; - existingAccessory.context.deviceId = device.deviceId; - existingAccessory.context.deviceType = `IR: ${device.remoteType}`; - existingAccessory.context.model = device.remoteType; + existingAccessory.context.device = device + existingAccessory.context.deviceId = device.deviceId + existingAccessory.context.deviceType = `IR: ${device.remoteType}` + existingAccessory.context.model = device.remoteType existingAccessory.displayName = device.configDeviceName ? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName) - : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName); - await this.infoLog(`Restoring existing accessory from cache: ${existingAccessory.displayName} deviceId: ${device.deviceId}`); - existingAccessory.context.connectionType = device.connectionType; - this.api.updatePlatformAccessories([existingAccessory]); + : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName) + await this.infoLog(`Restoring existing accessory from cache: ${existingAccessory.displayName} deviceId: ${device.deviceId}`) + existingAccessory.context.connectionType = device.connectionType + this.api.updatePlatformAccessories([existingAccessory]) // create the accessory handler for the restored accessory // this is imported from `platformAccessory.ts` - new VacuumCleaner(this, existingAccessory, device); - await this.debugLog(`${device.remoteType} uuid: ${device.deviceId}-${device.remoteType}, (${existingAccessory.UUID})`); + new VacuumCleaner(this, existingAccessory, device) + await this.debugLog(`${device.remoteType} uuid: ${device.deviceId}-${device.remoteType}, (${existingAccessory.UUID})`) } else { - this.unregisterPlatformAccessories(existingAccessory); + this.unregisterPlatformAccessories(existingAccessory) } } else if (!device.hide_device && device.hubDeviceId) { // create a new accessory const accessory = new this.api.platformAccessory(device.configDeviceName ? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName) - : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName), uuid); + : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName), uuid) // store a copy of the device object in the `accessory.context` // the `context` property can be used to store any data about the accessory you may need - accessory.context.device = device; - accessory.context.deviceId = device.deviceId; - accessory.context.deviceType = `IR: ${device.remoteType}`; - accessory.context.model = device.remoteType; + accessory.context.device = device + accessory.context.deviceId = device.deviceId + accessory.context.deviceType = `IR: ${device.remoteType}` + accessory.context.model = device.remoteType accessory.displayName = device.configDeviceName ? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName) - : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName); - accessory.context.connectionType = await this.connectionType(device); - accessory.context.connectionType = device.connectionType; - accessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0'; - const newOrExternal = !device.external ? 'Adding new' : 'Loading external'; - await this.infoLog(`${newOrExternal} accessory: ${accessory.displayName} deviceId: ${device.deviceId}`); + : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName) + accessory.context.connectionType = await this.connectionType(device) + accessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0' + const newOrExternal = !device.external ? 'Adding new' : 'Loading external' + await this.infoLog(`${newOrExternal} accessory: ${accessory.displayName} deviceId: ${device.deviceId}`) // create the accessory handler for the newly create accessory // this is imported from `platformAccessory.ts` - new VacuumCleaner(this, accessory, device); - await this.debugLog(`${device.remoteType} uuid: ${device.deviceId}-${device.remoteType}, (${accessory.UUID})`); + new VacuumCleaner(this, accessory, device) + await this.debugLog(`${device.remoteType} uuid: ${device.deviceId}-${device.remoteType}, (${accessory.UUID})`) // publish device externally or link the accessory to your platform - this.externalOrPlatformIR(device, accessory); - this.accessories.push(accessory); + this.externalOrPlatform(device, accessory) + this.accessories.push(accessory) } else { - await this.debugLog(`Device not registered: ${device.deviceName} ${device.remoteType} deviceId: ${device.deviceId}`); + await this.debugLog(`Device not registered: ${device.deviceName} ${device.remoteType} deviceId: ${device.deviceId}`) } } private async createCamera(device: irdevice & devicesConfig) { - const uuid = this.api.hap.uuid.generate(`${device.deviceId}-${device.remoteType}`); + const uuid = this.api.hap.uuid.generate(`${device.deviceId}-${device.remoteType}`) // see if an accessory with the same uuid has already been registered and restored from // the cached devices we stored in the `configureAccessory` method above - const existingAccessory = this.accessories.find((accessory) => accessory.UUID === uuid); + const existingAccessory = this.accessories.find(accessory => accessory.UUID === uuid) if (existingAccessory) { // the accessory already exists if (!device.hide_device && device.hubDeviceId) { // if you need to update the accessory.context then you should run `api.updatePlatformAccessories`. eg.: - existingAccessory.context.device = device; - existingAccessory.context.deviceId = device.deviceId; - existingAccessory.context.deviceType = `IR: ${device.remoteType}`; - existingAccessory.context.model = device.remoteType; + existingAccessory.context.device = device + existingAccessory.context.deviceId = device.deviceId + existingAccessory.context.deviceType = `IR: ${device.remoteType}` + existingAccessory.context.model = device.remoteType existingAccessory.displayName = device.configDeviceName ? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName) - : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName); - await this.infoLog(`Restoring existing accessory from cache: ${existingAccessory.displayName} deviceId: ${device.deviceId}`); - existingAccessory.context.connectionType = device.connectionType; - this.api.updatePlatformAccessories([existingAccessory]); + : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName) + await this.infoLog(`Restoring existing accessory from cache: ${existingAccessory.displayName} deviceId: ${device.deviceId}`) + existingAccessory.context.connectionType = device.connectionType + this.api.updatePlatformAccessories([existingAccessory]) // create the accessory handler for the restored accessory // this is imported from `platformAccessory.ts` - new Camera(this, existingAccessory, device); - await this.debugLog(`${device.remoteType} uuid: ${device.deviceId}-${device.remoteType}, (${existingAccessory.UUID})`); + new Camera(this, existingAccessory, device) + await this.debugLog(`${device.remoteType} uuid: ${device.deviceId}-${device.remoteType}, (${existingAccessory.UUID})`) } else { - this.unregisterPlatformAccessories(existingAccessory); + this.unregisterPlatformAccessories(existingAccessory) } } else if (!device.hide_device && device.hubDeviceId) { // create a new accessory const accessory = new this.api.platformAccessory(device.configDeviceName ? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName) - : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName), uuid); + : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName), uuid) // store a copy of the device object in the `accessory.context` // the `context` property can be used to store any data about the accessory you may need - accessory.context.device = device; - accessory.context.deviceId = device.deviceId; - accessory.context.deviceType = `IR: ${device.remoteType}`; - accessory.context.model = device.remoteType; + accessory.context.device = device + accessory.context.deviceId = device.deviceId + accessory.context.deviceType = `IR: ${device.remoteType}` + accessory.context.model = device.remoteType accessory.displayName = device.configDeviceName ? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName) - : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName); - accessory.context.connectionType = await this.connectionType(device); - accessory.context.connectionType = device.connectionType; - accessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0'; - const newOrExternal = !device.external ? 'Adding new' : 'Loading external'; - await this.infoLog(`${newOrExternal} accessory: ${accessory.displayName} deviceId: ${device.deviceId}`); + : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName) + accessory.context.connectionType = await this.connectionType(device) + accessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0' + const newOrExternal = !device.external ? 'Adding new' : 'Loading external' + await this.infoLog(`${newOrExternal} accessory: ${accessory.displayName} deviceId: ${device.deviceId}`) // create the accessory handler for the newly create accessory // this is imported from `platformAccessory.ts` - new Camera(this, accessory, device); - await this.debugLog(`${device.remoteType} uuid: ${device.deviceId}-${device.remoteType}, (${accessory.UUID})`); + new Camera(this, accessory, device) + await this.debugLog(`${device.remoteType} uuid: ${device.deviceId}-${device.remoteType}, (${accessory.UUID})`) // publish device externally or link the accessory to your platform - this.externalOrPlatformIR(device, accessory); - this.accessories.push(accessory); + this.externalOrPlatform(device, accessory) + this.accessories.push(accessory) } else { - await this.debugLog(`Device not registered: ${device.deviceName} ${device.remoteType} deviceId: ${device.deviceId}`); + await this.debugLog(`Device not registered: ${device.deviceName} ${device.remoteType} deviceId: ${device.deviceId}`) } } private async createOthers(device: irdevice & devicesConfig) { - const uuid = this.api.hap.uuid.generate(`${device.deviceId}-${device.remoteType}`); + const uuid = this.api.hap.uuid.generate(`${device.deviceId}-${device.remoteType}`) // see if an accessory with the same uuid has already been registered and restored from // the cached devices we stored in the `configureAccessory` method above - const existingAccessory = this.accessories.find((accessory) => accessory.UUID === uuid); + const existingAccessory = this.accessories.find(accessory => accessory.UUID === uuid) if (existingAccessory) { // the accessory already exists if (!device.hide_device && device.hubDeviceId) { // if you need to update the accessory.context then you should run `api.updatePlatformAccessories`. eg.: - existingAccessory.context.device = device; - existingAccessory.context.deviceId = device.deviceId; - existingAccessory.context.deviceType = `IR: ${device.remoteType}`; - existingAccessory.context.model = device.remoteType; + existingAccessory.context.device = device + existingAccessory.context.deviceId = device.deviceId + existingAccessory.context.deviceType = `IR: ${device.remoteType}` + existingAccessory.context.model = device.remoteType existingAccessory.displayName = device.configDeviceName ? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName) - : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName); - await this.infoLog(`Restoring existing accessory from cache: ${existingAccessory.displayName} deviceId: ${device.deviceId}`); - existingAccessory.context.connectionType = device.connectionType; - this.api.updatePlatformAccessories([existingAccessory]); + : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName) + await this.infoLog(`Restoring existing accessory from cache: ${existingAccessory.displayName} deviceId: ${device.deviceId}`) + existingAccessory.context.connectionType = device.connectionType + this.api.updatePlatformAccessories([existingAccessory]) // create the accessory handler for the restored accessory // this is imported from `platformAccessory.ts` - new Others(this, existingAccessory, device); - await this.debugLog(`${device.remoteType} uuid: ${device.deviceId}-${device.remoteType}, (${existingAccessory.UUID})`); + new Others(this, existingAccessory, device) + await this.debugLog(`${device.remoteType} uuid: ${device.deviceId}-${device.remoteType}, (${existingAccessory.UUID})`) } else { - this.unregisterPlatformAccessories(existingAccessory); + this.unregisterPlatformAccessories(existingAccessory) } } else if (!device.hide_device && device.hubDeviceId) { // create a new accessory const accessory = new this.api.platformAccessory(device.configDeviceName ? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName) - : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName), uuid); + : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName), uuid) // store a copy of the device object in the `accessory.context` // the `context` property can be used to store any data about the accessory you may need - accessory.context.device = device; - accessory.context.deviceId = device.deviceId; - accessory.context.deviceType = `IR: ${device.remoteType}`; - accessory.context.model = device.remoteType; + accessory.context.device = device + accessory.context.deviceId = device.deviceId + accessory.context.deviceType = `IR: ${device.remoteType}` + accessory.context.model = device.remoteType accessory.displayName = device.configDeviceName ? await this.validateAndCleanDisplayName(device.configDeviceName, 'configDeviceName', device.configDeviceName) - : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName); - accessory.context.connectionType = await this.connectionType(device); - accessory.context.connectionType = device.connectionType; - accessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0'; - const newOrExternal = !device.external ? 'Adding new' : 'Loading external'; - await this.infoLog(`${newOrExternal} accessory: ${accessory.displayName} deviceId: ${device.deviceId}`); + : await this.validateAndCleanDisplayName(device.deviceName, 'deviceName', device.deviceName) + accessory.context.connectionType = await this.connectionType(device) + accessory.context.version = device.firmware ?? device.version ?? this.version ?? '0.0.0' + const newOrExternal = !device.external ? 'Adding new' : 'Loading external' + await this.infoLog(`${newOrExternal} accessory: ${accessory.displayName} deviceId: ${device.deviceId}`) // create the accessory handler for the newly create accessory // this is imported from `platformAccessory.ts` - new Others(this, accessory, device); - await this.debugLog(`${device.remoteType} uuid: ${device.deviceId}-${device.remoteType}, (${accessory.UUID})`); + new Others(this, accessory, device) + await this.debugLog(`${device.remoteType} uuid: ${device.deviceId}-${device.remoteType}, (${accessory.UUID})`) // publish device externally or link the accessory to your platform - this.externalOrPlatformIR(device, accessory); - this.accessories.push(accessory); + this.externalOrPlatform(device, accessory) + this.accessories.push(accessory) } else { - await this.debugLog(`Device not registered: ${device.deviceName} ${device.remoteType} deviceId: ${device.deviceId}`); + await this.debugLog(`Device not registered: ${device.deviceName} ${device.remoteType} deviceId: ${device.deviceId}`) } } async registerCurtains(device: device & devicesConfig): Promise { - /*function isCurtainDevice(device: device & devicesConfig): device is (curtain | curtain3) & devicesConfig { - return device.deviceType === 'Curtain' || device.deviceType === 'Curtain3'; - } - - function isBlindTiltDevice(device: device & devicesConfig): device is blindTilt & devicesConfig { - return device.deviceType === 'Blind Tilt'; - }*/ - let registerWindowCovering: boolean; + let registerWindowCovering: boolean if (isCurtainDevice(device)) { - await this.debugWarnLog(`deviceName: ${device.deviceName} deviceId: ${device.deviceId}, curtainDevicesIds: ${device.curtainDevicesIds},` - + ` master: ${device.master}, group: ${device.group}, disable_group: ${device.curtain?.disable_group},` - + ` connectionType: ${device.connectionType}`); - registerWindowCovering = await this.registerWindowCovering(device); + await this.debugWarnLog(`deviceName: ${device.deviceName} deviceId: ${device.deviceId}, curtainDevicesIds: ${device.curtainDevicesIds},x master: ${device.master}, group: ${device.group}, disable_group: ${device.curtain?.disable_group}, connectionType: ${device.connectionType}`) + registerWindowCovering = await this.registerWindowCovering(device) } else if (isBlindTiltDevice(device)) { - await this.debugWarnLog(`deviceName: ${device.deviceName} deviceId: ${device.deviceId}, blindTiltDevicesIds: ${device.blindTiltDevicesIds},` - + ` master: ${device.master}, group: ${device.group}, disable_group: ${device.curtain?.disable_group},` - + ` connectionType: ${device.connectionType}`); - registerWindowCovering = await this.registerWindowCovering(device); + await this.debugWarnLog(`deviceName: ${device.deviceName} deviceId: ${device.deviceId}, blindTiltDevicesIds: ${device.blindTiltDevicesIds}, master: ${device.master}, group: ${device.group}, disable_group: ${device.curtain?.disable_group}, connectionType: ${device.connectionType}`) + registerWindowCovering = await this.registerWindowCovering(device) } else { - registerWindowCovering = false; + registerWindowCovering = false } - return registerWindowCovering; + return registerWindowCovering } async registerWindowCovering(device: ((curtain | curtain3) & devicesConfig) | (blindTilt & devicesConfig)) { - await this.debugLog(`master: ${device.master}`); - let registerCurtain: boolean; + await this.debugLog(`master: ${device.master}`) + let registerCurtain: boolean if (device.master && device.group) { // OpenAPI: Master Curtains/Blind Tilt in Group - registerCurtain = true; - await this.debugLog(`deviceName: ${device.deviceName} [${device.deviceType} Config] device.master: ${device.master},` - + ` device.group: ${device.group} connectionType; ${device.connectionType}`); - await this.debugWarnLog(`Device: ${device.deviceName} registerCurtains: ${registerCurtain}`); + registerCurtain = true + await this.debugLog(`deviceName: ${device.deviceName} [${device.deviceType} Config] device.master: ${device.master}, device.group: ${device.group} connectionType; ${device.connectionType}`) + await this.debugWarnLog(`Device: ${device.deviceName} registerCurtains: ${registerCurtain}`) } else if (!device.master && device.curtain?.disable_group) { - //!device.group && device.connectionType === 'BLE' + // !device.group && device.connectionType === 'BLE' // OpenAPI: Non-Master Curtains/Blind Tilts that has Disable Grouping Checked - registerCurtain = true; - await this.debugLog(`deviceName: ${device.deviceName} [${device.deviceType} Config] device.master: ${device.master}, disable_group: ` - + `${device.curtain?.disable_group}, connectionType; ${device.connectionType}`); - await this.debugWarnLog(`Device: ${device.deviceName} registerCurtains: ${registerCurtain}`); + registerCurtain = true + await this.debugLog(`deviceName: ${device.deviceName} [${device.deviceType} Config] device.master: ${device.master}, disable_group: ${device.curtain?.disable_group}, connectionType; ${device.connectionType}`) + await this.debugWarnLog(`Device: ${device.deviceName} registerCurtains: ${registerCurtain}`) } else if (device.master && !device.group) { // OpenAPI: Master Curtains/Blind Tilts not in Group - registerCurtain = true; - await this.debugLog(`deviceName: ${device.deviceName} [${device.deviceType} Config] device.master: ${device.master},` - + ` device.group: ${device.group} connectionType; ${device.connectionType}`); - await this.debugWarnLog(`Device: ${device.deviceName} registerCurtains: ${registerCurtain}`); + registerCurtain = true + await this.debugLog(`deviceName: ${device.deviceName} [${device.deviceType} Config] device.master: ${device.master}, device.group: ${device.group} connectionType; ${device.connectionType}`) + await this.debugWarnLog(`Device: ${device.deviceName} registerCurtains: ${registerCurtain}`) } else if (device.connectionType === 'BLE') { // BLE: Curtains/Blind Tilt - registerCurtain = true; - await this.debugLog(`deviceName: ${device.deviceName} [${device.deviceType} Config] connectionType: ${device.connectionType}, ` - + ` group: ${device.group}`); - await this.debugWarnLog(`Device: ${device.deviceName} registerCurtains: ${registerCurtain}`); + registerCurtain = true + await this.debugLog(`deviceName: ${device.deviceName} [${device.deviceType} Config] connectionType: ${device.connectionType}, group: ${device.group}`) + await this.debugWarnLog(`Device: ${device.deviceName} registerCurtains: ${registerCurtain}`) } else { - registerCurtain = false; - await this.debugErrorLog(`deviceName: ${device.deviceName} [${device.deviceType} Config] disable_group: ${device.curtain?.disable_group},` - + ` device.master: ${device.master}, device.group: ${device.group}`); - await this.debugWarnLog(`Device: ${device.deviceName} registerCurtains: ${registerCurtain}, device.connectionType: ${device.connectionType}`); + registerCurtain = false + await this.debugErrorLog(`deviceName: ${device.deviceName} [${device.deviceType} Config] disable_group: ${device.curtain?.disable_group}, device.master: ${device.master}, device.group: ${device.group}`) + await this.debugWarnLog(`Device: ${device.deviceName} registerCurtains: ${registerCurtain}, device.connectionType: ${device.connectionType}`) } - return registerCurtain; + return registerCurtain } async connectionType(device: device & devicesConfig): Promise { - let connectionType: string; + let connectionType: string if (!device.connectionType && this.config.credentials?.token && this.config.credentials.secret) { - connectionType = 'OpenAPI'; + connectionType = 'OpenAPI' } else { - connectionType = device.connectionType!; + connectionType = device.connectionType! } - return connectionType; + return connectionType } async registerDevice(device: device & devicesConfig) { - device.connectionType = await this.connectionType(device); - let registerDevice: boolean; - if (!device.hide_device && device.connectionType === 'BLE/OpenAPI') { - switch (device.deviceType) { - case 'Curtain': - case 'Curtain3': - case 'Blind Tilt': - registerDevice = await this.registerCurtains(device); - await this.debugWarnLog(`Device: ${device.deviceName} ${device.deviceType} registerDevice: ${registerDevice}`); - break; - default: - registerDevice = true; - await this.debugWarnLog(`Device: ${device.deviceName} registerDevice: ${registerDevice}`); - } - if (registerDevice === true) { - await this.debugWarnLog(`Device: ${device.deviceName} connectionType: ${device.connectionType}, will display in HomeKit`); - } else { - await this.debugErrorLog(`Device: ${device.deviceName} connectionType: ${device.connectionType}, will not display in HomeKit`); - } - } else if (!device.hide_device && device.deviceId && device.configDeviceType && device.configDeviceName && device.connectionType === 'BLE') { - switch (device.deviceType) { - case 'Curtain': - case 'Curtain3': - case 'Blind Tilt': - registerDevice = await this.registerCurtains(device); - await this.debugWarnLog(`Device: ${device.deviceName} ${device.deviceType} registerDevice: ${registerDevice}`); - break; - default: - registerDevice = true; - await this.debugWarnLog(`Device: ${device.deviceName} registerDevice: ${registerDevice}`); - } - if (registerDevice === true) { - await this.debugWarnLog(`Device: ${device.deviceName} connectionType: ${device.connectionType}, will display in HomeKit`); - } else { - await this.debugErrorLog(`Device: ${device.deviceName} connectionType: ${device.connectionType}, will not display in HomeKit`); - } - } else if (!device.hide_device && device.connectionType === 'OpenAPI') { - switch (device.deviceType) { - case 'Curtain': - case 'Curtain3': - case 'Blind Tilt': - registerDevice = await this.registerCurtains(device); - await this.debugWarnLog(`Device: ${device.deviceName} ${device.deviceType} registerDevice: ${registerDevice}`); - break; - default: - registerDevice = true; - await this.debugWarnLog(`Device: ${device.deviceName} registerDevice: ${registerDevice}`); - } - if (registerDevice === true) { - await this.debugWarnLog(`Device: ${device.deviceName} connectionType: ${device.connectionType}, will display in HomeKit`); - } else { - await this.debugErrorLog(`Device: ${device.deviceName} connectionType: ${device.connectionType}, will not display in HomeKit`); - } - } else if (!device.hide_device && device.connectionType === 'Disabled') { - switch (device.deviceType) { - case 'Curtain': - case 'Curtain3': - case 'Blind Tilt': - registerDevice = await this.registerCurtains(device); - await this.debugWarnLog(`Device: ${device.deviceName} ${device.deviceType} registerDevice: ${registerDevice}`); - break; - default: - registerDevice = true; - await this.debugWarnLog(`Device: ${device.deviceName} registerDevice: ${registerDevice}`); - } - await this.debugWarnLog(`Device: ${device.deviceName} connectionType: ${device.connectionType}, will continue to display in HomeKit`); - } else if (!device.connectionType && !device.hide_device) { - registerDevice = false; - await this.debugErrorLog(`Device: ${device.deviceName} connectionType: ${device.connectionType}, will not display in HomeKit`); - } else if (device.hide_device) { - registerDevice = false; - await this.debugErrorLog(`Device: ${device.deviceName} hide_device: ${device.hide_device}, will not display in HomeKit`); - } else { - registerDevice = false; - await this.debugErrorLog(`Device: ${device.deviceName} connectionType: ${device.connectionType}, hide_device: ` - + `${device.hide_device}, will not display in HomeKit`); - } - return registerDevice; - } - - public async externalOrPlatformIR(device: device & irDevicesConfig, accessory: PlatformAccessory) { - /** - * Publish as external accessory - * Only one TV can exist per bridge, to bypass this limitation, you should - * publish your TV as an external accessory. - */ - if (device.external) { - await this.debugWarnLog(`${accessory.displayName} External Accessory Mode`); - this.externalAccessory(accessory); + device.connectionType = await this.connectionType(device) + let registerDevice: boolean + + const shouldRegister = !device.hide_device && (device.connectionType === 'BLE/OpenAPI' || (device.deviceId && device.configDeviceType && device.configDeviceName && device.connectionType === 'BLE') || device.connectionType === 'OpenAPI' || device.connectionType === 'Disabled') + + if (shouldRegister) { + registerDevice = await this.handleDeviceRegistration(device) } else { - await this.debugLog(`${accessory.displayName} External Accessory Mode: ${device.external}`); - this.api.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory]); + registerDevice = false + await this.debugErrorLog(`Device: ${device.deviceName} connectionType: ${device.connectionType}, hide_device: ${device.hide_device}, will not display in HomeKit`) } + + return registerDevice } - public async externalOrPlatform(device: device & devicesConfig, accessory: PlatformAccessory) { - if (device.external) { - await this.debugWarnLog(`${accessory.displayName} External Accessory Mode`); - await this.externalAccessory(accessory); + async handleDeviceRegistration(device: device & devicesConfig): Promise { + let registerDevice: boolean + + switch (device.deviceType) { + case 'Curtain': + case 'Curtain3': + case 'Blind Tilt': + registerDevice = await this.registerCurtains(device) + await this.debugWarnLog(`Device: ${device.deviceName} ${device.deviceType} registerDevice: ${registerDevice}`) + break + default: + registerDevice = true + await this.debugWarnLog(`Device: ${device.deviceName} registerDevice: ${registerDevice}`) + } + + if (registerDevice) { + await this.debugWarnLog(`Device: ${device.deviceName} connectionType: ${device.connectionType}, will display in HomeKit`) } else { - await this.debugLog(`${accessory.displayName} External Accessory Mode: ${device.external}`); - this.api.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory]); + await this.debugErrorLog(`Device: ${device.deviceName} connectionType: ${device.connectionType}, will not display in HomeKit`) } + + return registerDevice } - public async externalAccessory(accessory: PlatformAccessory) { - this.api.publishExternalAccessories(PLUGIN_NAME, [accessory]); + public async externalOrPlatform(device: device & (irDevicesConfig | devicesConfig), accessory: PlatformAccessory) { + const { displayName } = accessory + const isExternal = device.external ?? false + + if (isExternal) { + await this.debugWarnLog(`${displayName} External Accessory Mode`) + this.api.publishExternalAccessories(PLUGIN_NAME, [accessory]) + } else { + await this.debugLog(`${displayName} External Accessory Mode: ${isExternal}`) + this.api.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory]) + } } public unregisterPlatformAccessories(existingAccessory: PlatformAccessory) { + const { displayName } = existingAccessory // remove platform accessories when no longer present - this.api.unregisterPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [existingAccessory]); - this.warnLog(`Removing existing accessory from cache: ${existingAccessory.displayName}`); + this.api.unregisterPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [existingAccessory]) + this.warnLog(`Removing existing accessory from cache: ${displayName}`) } + /** + * Handles the status codes returned by the device and logs appropriate messages. + * + * @param statusCode - The status code returned by the device. + * @returns A promise that resolves when the logging is complete. + */ async statusCode(statusCode: number): Promise { - switch (statusCode) { - case 151: - await this.errorLog(`Command not supported by this device type, statusCode: ${statusCode}, Submit Feature Request Here: ` + - 'https://tinyurl.com/SwitchBotFeatureRequest'); - break; - case 152: - await this.errorLog(`Device not found, statusCode: ${statusCode}`); - break; - case 160: - await this.errorLog(`Command is not supported, statusCode: ${statusCode}, Submit Bugs Here: https://tinyurl.com/SwitchBotBug`); - break; - case 161: - await this.errorLog(`Device is offline, statusCode: ${statusCode}`); - break; - case 171: - await this.errorLog(`is offline, statusCode: ${statusCode}`); - break; - case 190: - await this.errorLog(`Requests reached the daily limit, statusCode: ${statusCode}`); - break; - case 100: - await this.debugLog(`Command successfully sent, statusCode: ${statusCode}`); - break; - case 200: - await this.debugLog(`Request successful, statusCode: ${statusCode}`); - break; - case 400: - await this.errorLog('Bad Request, The client has issued an invalid request. This is commonly used to specify validation errors in a request ' - + `payload, statusCode: ${statusCode}`); - break; - case 401: - await this.errorLog('Unauthorized, Authorization for the API is required, but the request has not been authenticated, ' - + `statusCode: ${statusCode}`); - break; - case 403: - await this.errorLog('Forbidden, The request has been authenticated but does not have appropriate permissions, or a requested resource is not ' - + `found, statusCode: ${statusCode}`); - break; - case 404: - await this.errorLog(`Not Found, Specifies the requested path does not exist, statusCode: ${statusCode}`); - break; - case 406: - await this.errorLog('Not Acceptable, The client has requested a MIME type via the Accept header for a value not supported by the server, ' - + `statusCode: ${statusCode}`); - break; - case 415: - await this.errorLog('Unsupported Media Type, The client has defined a contentType header that is not supported by the server, ' - + `statusCode: ${statusCode}`); - break; - case 422: - await this.errorLog('Unprocessable Entity, 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, statusCode: ${statusCode}`); - break; - case 429: - await this.errorLog('Too Many Requests, The client has exceeded the number of requests allowed for a given time window, ' - + `statusCode: ${statusCode}`); - break; - case 500: - await this.errorLog('Internal Server Error, An unexpected error on the SmartThings servers has occurred. These errors should be rare, ' - + `statusCode: ${statusCode}`); - break; - default: - await this.errorLog(`Unknown statusCode, statusCode: ${statusCode}, Submit Bugs Here: ' + 'https://tinyurl.com/SwitchBotBug`); + const messages: { [key: number]: string } = { + 151: `Command not supported by this device type, statusCode: ${statusCode}, Submit Feature Request Here: + https://tinyurl.com/SwitchBotFeatureRequest`, + 152: `Device not found, statusCode: ${statusCode}`, + 160: `Command is not supported, statusCode: ${statusCode}, Submit Bugs Here: https://tinyurl.com/SwitchBotBug`, + 161: `Device is offline, statusCode: ${statusCode}`, + 171: `is offline, statusCode: ${statusCode}`, + 190: `Requests reached the daily limit, statusCode: ${statusCode}`, + 100: `Command successfully sent, statusCode: ${statusCode}`, + 200: `Request successful, statusCode: ${statusCode}`, + 400: `Bad Request, The client has issued an invalid request. This is commonly used to specify validation errors in a request payload, + statusCode: ${statusCode}`, + 401: `Unauthorized, Authorization for the API is required, but the request has not been authenticated, statusCode: ${statusCode}`, + 403: `Forbidden, The request has been authenticated but does not have appropriate permissions, or a requested resource is not found, + statusCode: ${statusCode}`, + 404: `Not Found, Specifies the requested path does not exist, statusCode: ${statusCode}`, + 406: `Not Acceptable, The client has requested a MIME type via the Accept header for a value not supported by the server, + statusCode: ${statusCode}`, + 415: `Unsupported Media Type, The client has defined a contentType header that is not supported by the server, statusCode: ${statusCode}`, + 422: `Unprocessable Entity, 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, statusCode: ${statusCode}`, + 429: `Too Many Requests, The client has exceeded the number of requests allowed for a given time window, statusCode: ${statusCode}`, + 500: `Internal Server Error, An unexpected error on the SmartThings servers has occurred. These errors should be rare, + statusCode: ${statusCode}`, + } + + const message = messages[statusCode] ?? `Unknown statusCode, statusCode: ${statusCode}, Submit Bugs Here: https://tinyurl.com/SwitchBotBug` + + if ([100, 200].includes(statusCode)) { + await this.debugLog(message) + } else { + await this.errorLog(message) } } - async retryRequest(deviceMaxRetries: number, deviceDelayBetweenRetries: number, url: string | URL | UrlObject, - options?: { dispatcher?: Dispatcher } & Omit & Partial>): Promise<{ body: any; statusCode: number }> { - let retryCount = 0; - const maxRetries = deviceMaxRetries; - const delayBetweenRetries = deviceDelayBetweenRetries; + async retryRequest(deviceMaxRetries: number, deviceDelayBetweenRetries: number, url: string | URL | UrlObject, options?: { dispatcher?: Dispatcher } & Omit & Partial>): Promise<{ body: any, statusCode: number }> { + let retryCount = 0 + const maxRetries = deviceMaxRetries + const delayBetweenRetries = deviceDelayBetweenRetries while (retryCount < maxRetries) { try { - const { body, statusCode } = await request(url, options); + const { body, statusCode } = await request(url, options) if (statusCode === 200 || statusCode === 100) { - return { body, statusCode }; + return { body, statusCode } } else { - await this.debugLog(`Received status code: ${statusCode}`); + await this.debugLog(`Received status code: ${statusCode}`) } } catch (error: any) { - await this.errorLog(`Error making request: ${error.message}`); + await this.errorLog(`Error making request: ${error.message}`) } - retryCount++; - await this.debugLog(`Retry attempt ${retryCount} of ${maxRetries}`); - await sleep(delayBetweenRetries); + retryCount++ + await this.debugLog(`Retry attempt ${retryCount} of ${maxRetries}`) + await sleep(delayBetweenRetries) } - return { body: null, statusCode: -1 }; + return { body: null, statusCode: -1 } } // BLE Connection - async connectBLE(accessory: PlatformAccessory, device: device & devicesConfig) { - let switchbot: any; + async connectBLE(accessory: PlatformAccessory, device: device & devicesConfig): Promise { try { - const SwitchBot = (await import('node-switchbot')).SwitchBot; - queueScheduler.schedule(() => (switchbot = new SwitchBot())); - await this.debugLog(`${device.deviceType}: ${accessory.displayName} 'node-switchbot' found: ${switchbot}`); + const switchbot = new SwitchBot() + queueScheduler.schedule(async () => switchbot) + await this.debugLog(`${device.deviceType}: ${accessory.displayName} 'node-switchbot' found: ${switchbot}`) + return switchbot } catch (e: any) { - switchbot = false; - await this.errorLog(`${device.deviceType}: ${accessory.displayName} 'node-switchbot' found: ${switchbot}, Error: ${e}`); + await this.errorLog(`${device.deviceType}: ${accessory.displayName} 'node-switchbot' not found, Error: ${e}`) + return false } - return switchbot; } - async getVersion() { + async getVersion(): Promise { const json = JSON.parse( readFileSync( new URL('../package.json', import.meta.url), 'utf-8', ), - ); - await this.debugLog(`Plugin Version: ${json.version}`); - this.version = json.version; + ) + await this.debugLog(`Plugin Version: ${json.version}`) + this.version = json.version + return json.version } async getPlatformConfigSettings() { - const platformConfig: SwitchBotPlatformConfig['options'] = {}; - if (this.config.options) { - if (this.config.options.logging) { - platformConfig.logging = this.config.options.logging; - } - if (this.config.options.refreshRate) { - platformConfig.refreshRate = this.config.options.refreshRate; - } - if (this.config.options.updateRate) { - platformConfig.updateRate = this.config.options.updateRate; - } - if (this.config.options.pushRate) { - platformConfig.pushRate = this.config.options.pushRate; - } - if (this.config.options.maxRetries) { - this.maxRetries = this.config.options.maxRetries; - platformConfig.maxRetries = this.config.options.maxRetries; - } else { - this.maxRetries = 3; - this.debugWarnLog('Using Default Max Retries'); - platformConfig.maxRetries = this.maxRetries; + const { options } = this.config + const platformConfig: SwitchBotPlatformConfig['options'] = {} + + if (options) { + platformConfig.logging = options.logging + platformConfig.refreshRate = options.refreshRate + platformConfig.updateRate = options.updateRate + platformConfig.pushRate = options.pushRate + + this.maxRetries = options.maxRetries || 3 + platformConfig.maxRetries = this.maxRetries + if (!options.maxRetries) { + await this.debugWarnLog('Using Default Max Retries') } - if (this.config.options.delayBetweenRetries) { - this.delayBetweenRetries = this.config.options.delayBetweenRetries * 1000; - platformConfig.delayBetweenRetries = this.config.options.delayBetweenRetries; - } else { - this.delayBetweenRetries = 3000; - this.debugWarnLog('Using Default Delay Between Retries'); - platformConfig.delayBetweenRetries = this.delayBetweenRetries / 1000; + + this.delayBetweenRetries = (options.delayBetweenRetries || 3) * 1000 + platformConfig.delayBetweenRetries = this.delayBetweenRetries / 1000 + if (!options.delayBetweenRetries) { + await this.debugWarnLog('Using Default Delay Between Retries') } - if (Object.entries(platformConfig).length !== 0) { - await this.debugLog(`Platform Config: ${JSON.stringify(platformConfig)}`); + + if (Object.keys(platformConfig).length) { + await this.debugLog(`Platform Config: ${JSON.stringify(platformConfig)}`) } - this.platformConfig = platformConfig; + + this.platformConfig = platformConfig } } async getPlatformLogSettings() { - this.debugMode = process.argv.includes('-D') ?? process.argv.includes('--debug'); + 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; - await this.debugWarnLog(`Using Config Logging: ${this.platformLogging}`); + this.platformLogging = this.config.options.logging + await this.debugWarnLog(`Using Config Logging: ${this.platformLogging}`) } else if (this.debugMode) { - this.platformLogging = 'debugMode'; - await this.debugWarnLog(`Using ${this.platformLogging} Logging`); + this.platformLogging = 'debugMode' + await this.debugWarnLog(`Using ${this.platformLogging} Logging`) } else { - this.platformLogging = 'standard'; - await this.debugWarnLog(`Using ${this.platformLogging} Logging`); + this.platformLogging = 'standard' + await this.debugWarnLog(`Using ${this.platformLogging} Logging`) } } /** * Validate and clean a string value for a Name Characteristic. - * @param displayName - * @param name - * @param value - * @returns string value - */ + * @param displayName - The display name of the accessory. + * @param name - The name of the characteristic. + * @param value - The value to be validated and cleaned. + * @returns The cleaned string value. + */ async validateAndCleanDisplayName(displayName: string, name: string, value: string): Promise { - const validPattern = /^[a-zA-Z0-9][a-zA-Z0-9 ']*[a-zA-Z0-9]$/; - const invalidCharsPattern = /[^a-zA-Z0-9 ']/g; - const invalidStartEndPattern = /^[^a-zA-Z0-9]+|[^a-zA-Z0-9]+$/g; - - if (!validPattern.test(value)) { - this.warnLog(`WARNING: The accessory '${displayName}' has an invalid '${name}' characteristic ('${value}'). Please use only alphanumeric,` - + ' space, and apostrophe characters. Ensure it starts and ends with an alphabetic or numeric character, and avoid emojis. This may' - + ' prevent the accessory from being added in the Home App or cause unresponsiveness.'); - - // Remove invalid characters - if (invalidCharsPattern.test(value)) { - this.warnLog(`Removing invalid characters from '${name}' characteristic`); - value = value.replace(invalidCharsPattern, ''); - } + if (this.config.options?.allowInvalidCharacters) { + return value + } else { + const validPattern = /^[\p{L}\p{N}][\p{L}\p{N} ']*[\p{L}\p{N}]$/u + const invalidCharsPattern = /[^\p{L}\p{N} ']/gu + const invalidStartEndPattern = /^[^\p{L}\p{N}]+|[^\p{L}\p{N}]+$/gu + + if (typeof value === 'string' && !validPattern.test(value)) { + await this.warnLog(`WARNING: The accessory '${displayName}' has an invalid '${name}' characteristic ('${value}'). Please use only alphanumeric, space, and apostrophe characters. Ensure it starts and ends with an alphabetic or numeric character, and avoid emojis. This may prevent the accessory from being added in the Home App or cause unresponsiveness.`) + + // Remove invalid characters + if (invalidCharsPattern.test(value)) { + const before = value + await this.warnLog(`Removing invalid characters from '${name}' characteristic, if you feel this is incorrect, please enable \'allowInvalidCharacter\' in the config to allow all characters`) + value = value.replace(invalidCharsPattern, '') + await this.warnLog(`${name} Before: '${before}' After: '${value}'`) + } - // Ensure it starts and ends with an alphanumeric character - if (invalidStartEndPattern.test(value)) { - this.warnLog(`Removing invalid starting or ending characters from '${name}' characteristic`); - value = value.replace(invalidStartEndPattern, ''); + // Ensure it starts and ends with an alphanumeric character + if (invalidStartEndPattern.test(value)) { + const before = value + await this.warnLog(`Removing invalid starting or ending characters from '${name}' characteristic, if you feel this is incorrect, please enable \'allowInvalidCharacter\' in the config to allow all characters`) + value = value.replace(invalidStartEndPattern, '') + await this.warnLog(`${name} Before: '${before}' After: '${value}'`) + } } - } - return value; + return value + } } /** @@ -2959,48 +2769,48 @@ export class SwitchBotPlatform implements DynamicPlatformPlugin { */ async infoLog(...log: any[]): Promise { if (await this.enablingPlatformLogging()) { - this.log.info(String(...log)); + this.log.info(String(...log)) } } async successLog(...log: any[]): Promise { if (await this.enablingPlatformLogging()) { - this.log.success(String(...log)); + this.log.success(String(...log)) } } async debugSuccessLog(...log: any[]): Promise { if (await this.enablingPlatformLogging()) { if (await this.loggingIsDebug()) { - this.log.success('[DEBUG]', String(...log)); + this.log.success('[DEBUG]', String(...log)) } } } async warnLog(...log: any[]): Promise { if (await this.enablingPlatformLogging()) { - this.log.warn(String(...log)); + this.log.warn(String(...log)) } } async debugWarnLog(...log: any[]): Promise { if (await this.enablingPlatformLogging()) { if (await this.loggingIsDebug()) { - this.log.warn('[DEBUG]', String(...log)); + this.log.warn('[DEBUG]', String(...log)) } } } async errorLog(...log: any[]): Promise { if (await this.enablingPlatformLogging()) { - this.log.error(String(...log)); + this.log.error(String(...log)) } } async debugErrorLog(...log: any[]): Promise { if (await this.enablingPlatformLogging()) { if (await this.loggingIsDebug()) { - this.log.error('[DEBUG]', String(...log)); + this.log.error('[DEBUG]', String(...log)) } } } @@ -3008,18 +2818,18 @@ export class SwitchBotPlatform implements DynamicPlatformPlugin { async debugLog(...log: any[]): Promise { if (await this.enablingPlatformLogging()) { if (this.platformLogging === 'debug') { - this.log.info('[DEBUG]', String(...log)); + this.log.info('[DEBUG]', String(...log)) } else if (this.platformLogging === 'debugMode') { - this.log.debug(String(...log)); + this.log.debug(String(...log)) } } } async loggingIsDebug(): Promise { - return this.platformLogging === 'debugMode' || this.platformLogging === 'debug'; + return this.platformLogging === 'debugMode' || this.platformLogging === 'debug' } async enablingPlatformLogging(): Promise { - return this.platformLogging === 'debugMode' || this.platformLogging === 'debug' || this.platformLogging === 'standard'; + return this.platformLogging === 'debugMode' || this.platformLogging === 'debug' || this.platformLogging === 'standard' } } diff --git a/src/settings.ts b/src/settings.ts index e251604f..a42172ef 100644 --- a/src/settings.ts +++ b/src/settings.ts @@ -2,267 +2,271 @@ * * settings.ts: @switchbot/homebridge-switchbot platform class. */ -import type { IClientOptions } from 'async-mqtt'; -import type { PlatformConfig } from 'homebridge'; -import type { device } from './types/devicelist'; -import type { irdevice } from './types/irdevicelist'; -import type { SwitchBotBLEModel, SwitchBotBLEModelName, SwitchBotBLEModelFriendlyName } from 'node-switchbot'; +import type { IClientOptions } from 'async-mqtt' +import type { PlatformConfig } from 'homebridge' +import type { SwitchBotBLEModel, SwitchBotBLEModelFriendlyName, SwitchBotBLEModelName } from 'node-switchbot' + +import type { device } from './types/devicelist' +import type { irdevice } from './types/irdevicelist' /** * This is the name of the platform that users will use to register the plugin in the Homebridge config.json */ -export const PLATFORM_NAME = 'SwitchBot'; +export const PLATFORM_NAME = 'SwitchBot' /** * This must match the name of your plugin as defined the package.json */ -export const PLUGIN_NAME = '@switchbot/homebridge-switchbot'; +export const PLUGIN_NAME = '@switchbot/homebridge-switchbot' /** * This is the main url used to access SwitchBot API */ -export const Devices = 'https://api.switch-bot.com/v1.1/devices'; +export const Devices = 'https://api.switch-bot.com/v1.1/devices' /** * This is the updateWebhook url used to access SwitchBot API */ -export const setupWebhook = 'https://api.switch-bot.com/v1.1/webhook/setupWebhook'; +export const setupWebhook = 'https://api.switch-bot.com/v1.1/webhook/setupWebhook' /** * This is the updateWebhook url used to access SwitchBot API */ -export const queryWebhook = 'https://api.switch-bot.com/v1.1/webhook/queryWebhook'; +export const queryWebhook = 'https://api.switch-bot.com/v1.1/webhook/queryWebhook' /** * This is the updateWebhook url used to access SwitchBot API */ -export const updateWebhook = 'https://api.switch-bot.com/v1.1/webhook/updateWebhook'; +export const updateWebhook = 'https://api.switch-bot.com/v1.1/webhook/updateWebhook' /** * This is the deleteWebhook url used to access SwitchBot API */ -export const deleteWebhook = 'https://api.switch-bot.com/v1.1/webhook/deleteWebhook'; - +export const deleteWebhook = 'https://api.switch-bot.com/v1.1/webhook/deleteWebhook' -//Config +// Config export interface SwitchBotPlatformConfig extends PlatformConfig { - credentials?: credentials; - options?: options; + credentials?: credentials + options?: options } interface credentials { - token?: any; - secret?: any; - notice?: any; - openToken?: any; + token?: any + secret?: any + notice?: any + openToken?: any } -interface options { - devices?: devicesConfig[]; - irdevices?: irDevicesConfig[]; - mqttURL?: string; - mqttOptions?: IClientOptions; - mqttPubOptions?: IClientOptions; - BLE?: boolean; - webhookURL?: string; - maxRetries?: number; - delayBetweenRetries?: number; - refreshRate?: number; - updateRate?: number; - pushRate?: number; - logging?: string; +export interface options { + devices?: devicesConfig[] + irdevices?: irDevicesConfig[] + allowInvalidCharacters?: boolean + mqttURL?: string + mqttOptions?: IClientOptions + mqttPubOptions?: IClientOptions + BLE?: boolean + webhookURL?: string + maxRetries?: number + delayBetweenRetries?: number + refreshRate?: number + updateRate?: number + pushRate?: number + logging?: string }; export interface devicesConfig extends device { - bleMac?: string; - model: string; - bleModel: SwitchBotBLEModel; - bleModelName: SwitchBotBLEModelName; - bleModelFriednlyName: SwitchBotBLEModelFriendlyName; - configDeviceType: string; - configDeviceName?: string; - deviceId: string; - external?: boolean; - refreshRate?: number; - updateRate?: number; - pushRate?: number; - firmware?: string; - logging?: string; - connectionType?: string; - customBLEaddress?: string; - scanDuration?: number; - hide_device?: boolean; - offline?: boolean; - maxRetry?: number; - maxRetries?: number; - delayBetweenRetries?: number; - disableCaching?: boolean; - mqttURL?: string; - mqttOptions?: IClientOptions; - mqttPubOptions?: IClientOptions; - history?: boolean; - webhook?: boolean; - bot?: bot; - meter?: meter; - iosensor?: iosensor; - humidifier?: humidifier; - curtain?: curtain; - blindTilt?: blindTilt; - contact?: contact; - motion?: motion; - waterdetector?: waterdetector; - colorbulb?: colorbulb; - striplight?: striplight; - ceilinglight?: ceilinglight; - plug?: plug; - lock?: lock; - hub?: hub; + bleMac?: string + model: string + bleModel: SwitchBotBLEModel + bleModelName: SwitchBotBLEModelName + bleModelFriednlyName: SwitchBotBLEModelFriendlyName + configDeviceType: string + configDeviceName?: string + deviceId: string + external?: boolean + refreshRate?: number + updateRate?: number + pushRate?: number + firmware?: string + logging?: string + connectionType?: string + customBLEaddress?: string + scanDuration?: number + hide_device?: boolean + offline?: boolean + maxRetry?: number + maxRetries?: number + delayBetweenRetries?: number + disableCaching?: boolean + mqttURL?: string + mqttOptions?: IClientOptions + mqttPubOptions?: IClientOptions + history?: boolean + webhook?: boolean + bot?: bot + meter?: meter + iosensor?: iosensor + humidifier?: humidifier + curtain?: curtain + blindTilt?: blindTilt + contact?: contact + motion?: motion + waterdetector?: waterdetector + colorbulb?: colorbulb + striplight?: striplight + ceilinglight?: ceilinglight + plug?: plug + lock?: lock + hub?: hub } interface meter { - hide_temperature?: boolean; - convertUnitTo?: string; - hide_humidity?: boolean; + hide_temperature?: boolean + convertUnitTo?: string + hide_humidity?: boolean }; interface iosensor { - hide_temperature?: boolean; - convertUnitTo?: string; - hide_humidity?: boolean; + hide_temperature?: boolean + convertUnitTo?: string + hide_humidity?: boolean }; interface bot { - mode?: string; - deviceType?: string; - doublePress?: number; - pushRatePress?: number; - allowPush?: boolean; - multiPress?: boolean; + mode?: string + deviceType?: string + doublePress?: number + pushRatePress?: number + allowPush?: boolean + multiPress?: boolean }; interface humidifier { - hide_temperature?: boolean; - set_minStep?: number; + hide_temperature?: boolean + set_minStep?: number }; interface curtain { - disable_group?: boolean; - hide_lightsensor?: boolean; - set_minLux?: number; - set_maxLux?: number; - set_max?: number; - set_min?: number; - set_minStep?: number; - setCloseMode?: string; - setOpenMode?: string; + disable_group?: boolean + hide_lightsensor?: boolean + set_minLux?: number + set_maxLux?: number + set_max?: number + set_min?: number + set_minStep?: number + setCloseMode?: string + setOpenMode?: string + silentModeSwitch?: boolean }; interface blindTilt { - mode?: string; - hide_lightsensor?: boolean; - set_minLux?: number; - set_maxLux?: number; - set_max?: number; - set_min?: number; - set_minStep?: number; - setCloseMode?: string; - setOpenMode?: string; + mode?: string + hide_lightsensor?: boolean + set_minLux?: number + set_maxLux?: number + set_max?: number + set_min?: number + set_minStep?: number + setCloseMode?: string + setOpenMode?: string + silentModeSwitch?: boolean }; interface contact { - hide_lightsensor?: boolean; - set_minLux?: number; - set_maxLux?: number; - hide_motionsensor?: boolean; + hide_lightsensor?: boolean + set_minLux?: number + set_maxLux?: number + hide_motionsensor?: boolean }; interface motion { - hide_lightsensor?: boolean; - set_minLux?: number; - set_maxLux?: number; + hide_lightsensor?: boolean + set_minLux?: number + set_maxLux?: number }; interface waterdetector { - hide_leak?: boolean; + hide_leak?: boolean + dry?: boolean }; interface colorbulb { - set_minStep?: number; - adaptiveLightingShift?: number; + set_minStep?: number + adaptiveLightingShift?: number }; interface striplight { - set_minStep?: number; - adaptiveLightingShift?: number; + set_minStep?: number + adaptiveLightingShift?: number }; interface ceilinglight { - set_minStep?: number; - adaptiveLightingShift?: number; + set_minStep?: number + adaptiveLightingShift?: number }; -type plug = object; +type plug = object interface lock { - hide_contactsensor?: boolean; - activate_latchbutton?: boolean; + hide_contactsensor?: boolean + activate_latchbutton?: boolean }; interface hub { - hide_temperature?: boolean; - convertUnitTo?: string; - hide_humidity?: boolean; - hide_lightsensor?: boolean; + hide_temperature?: boolean + convertUnitTo?: string + hide_humidity?: boolean + hide_lightsensor?: boolean }; export interface irDevicesConfig extends irdevice { - configDeviceName?: string; - configRemoteType?: string; - connectionType?: string; - hide_device?: boolean; - external?: boolean; - firmware?: string; - deviceId: string; - logging?: string; - customOn?: string; - customOff?: string; - customize?: boolean; - commandType?: string; - disablePushOn?: boolean; - disablePushOff?: boolean; - disablePushDetail?: boolean; - irfan?: irfan; - irair?: irair; - irpur?: Record; - ircam?: Record; - irlight?: irlight; - irvc?: Record; - irwh?: Record; - irtv?: Record; - other?: other; + configDeviceName?: string + configRemoteType?: string + connectionType?: string + hide_device?: boolean + external?: boolean + firmware?: string + deviceId: string + logging?: string + customOn?: string + customOff?: string + customize?: boolean + commandType?: string + disablePushOn?: boolean + disablePushOff?: boolean + disablePushDetail?: boolean + irfan?: irfan + irair?: irair + irpur?: Record + ircam?: Record + irlight?: irlight + irvc?: Record + irwh?: Record + irtv?: Record + other?: other } interface irfan { - swing_mode?: boolean; - rotation_speed?: boolean; - set_minStep?: number; - set_max?: number; - set_min?: number; + swing_mode?: boolean + rotation_speed?: boolean + set_minStep?: number + set_max?: number + set_min?: number }; interface irlight { - stateless?: boolean; + stateless?: boolean }; interface irair { - hide_automode?: boolean; - set_max_heat?: number; - set_min_heat?: number; - set_max_cool?: number; - set_min_cool?: number; - meterType?: string; - meterId?: string; - meterUuid?: string; + hide_automode?: boolean + set_max_heat?: number + set_min_heat?: number + set_max_cool?: number + set_min_cool?: number + meterType?: string + meterId?: string + meterUuid?: string }; interface other { - deviceType?: string; + deviceType?: string }; diff --git a/src/types/bledevicestatus.ts b/src/types/bledevicestatus.ts index ef6c9a9d..727518d3 100644 --- a/src/types/bledevicestatus.ts +++ b/src/types/bledevicestatus.ts @@ -2,307 +2,303 @@ * * bledevicestatus.ts: @switchbot/homebridge-switchbot platform class. */ -import type { MacAddress } from 'homebridge'; -import type { SwitchBotBLEModel, SwitchBotBLEModelName, SwitchBotBLEModelFriendlyName } from 'node-switchbot'; +import type { MacAddress } from 'homebridge' +import type { SwitchBotBLEModel, SwitchBotBLEModelFriendlyName, SwitchBotBLEModelName } from 'node-switchbot' -export type switchbot = { - discover: (arg0: { duration?: any; model: string; quick: boolean; id?: MacAddress }) => Promise; - wait: (arg0: number) => any; -}; +export interface switchbot { + discover: (arg0: { duration?: any, model: string, quick: boolean, id?: MacAddress }) => Promise + wait: (arg0: number) => any +} -export type ad = { - id: string; - address: string; - rssi: number; - serviceData: botServiceData | colorBulbServiceData | contactSensorServiceData | - curtainServiceData | curtain3ServiceData | stripLightServiceData | lockServiceData | - lockProServiceData | meterServiceData | meterPlusServiceData | motionSensorServiceData | outdoorMeterServiceData | - plugMiniUSServiceData | plugMiniJPServiceData | blindTiltServiceData | ceilingLightServiceData | ceilingLightProServiceData | - hub2ServiceData | batteryCirculatorFanServiceData | waterLeakDetectorServiceData | humidifierServiceData | robotVacuumCleanerServiceData; -}; +export interface ad { + id: string + address: string + rssi: number + serviceData: botServiceData | colorBulbServiceData | contactSensorServiceData | curtainServiceData | curtain3ServiceData | stripLightServiceData | lockServiceData | lockProServiceData | meterServiceData | meterPlusServiceData | motionSensorServiceData | outdoorMeterServiceData | plugMiniUSServiceData | plugMiniJPServiceData | blindTiltServiceData | ceilingLightServiceData | ceilingLightProServiceData | hub2ServiceData | batteryCirculatorFanServiceData | waterLeakDetectorServiceData | humidifierServiceData | robotVacuumCleanerServiceData +} -type serviceData = { - model: string, - modelName: string, -}; +interface serviceData { + model: string + modelName: string +} export type botServiceData = serviceData & { - model: SwitchBotBLEModel.Bot, - modelName: SwitchBotBLEModelName.Bot, - modelFriendlyName: SwitchBotBLEModelFriendlyName.Bot, - mode: string; - state: boolean; - battery: number; -}; + model: SwitchBotBLEModel.Bot + modelName: SwitchBotBLEModelName.Bot + modelFriendlyName: SwitchBotBLEModelFriendlyName.Bot + mode: string + state: boolean + battery: number +} export type colorBulbServiceData = serviceData & { - model: SwitchBotBLEModel.ColorBulb, - modelName: SwitchBotBLEModelName.ColorBulb, - modelFriendlyName: SwitchBotBLEModelFriendlyName.ColorBulb, - color_temperature: number; - power: boolean; - state: boolean; - red: number; - green: number; - blue: number; - brightness: number; - delay: number; - preset: number; - color_mode: number; - speed: number; - loop_index: number; -}; + model: SwitchBotBLEModel.ColorBulb + modelName: SwitchBotBLEModelName.ColorBulb + modelFriendlyName: SwitchBotBLEModelFriendlyName.ColorBulb + color_temperature: number + power: boolean + state: boolean + red: number + green: number + blue: number + brightness: number + delay: number + preset: number + color_mode: number + speed: number + loop_index: number +} export type contactSensorServiceData = serviceData & { - model: SwitchBotBLEModel.ContactSensor, - modelName: SwitchBotBLEModelName.ContactSensor, - modelFriendlyName: SwitchBotBLEModelFriendlyName.ContactSensor, - movement: boolean; - tested: boolean; - battery: number; - contact_open: boolean; - contact_timeout: boolean; - lightLevel: string; - button_count: number; - doorState: string; -}; + model: SwitchBotBLEModel.ContactSensor + modelName: SwitchBotBLEModelName.ContactSensor + modelFriendlyName: SwitchBotBLEModelFriendlyName.ContactSensor + movement: boolean + tested: boolean + battery: number + contact_open: boolean + contact_timeout: boolean + lightLevel: string + button_count: number + doorState: string +} export type curtainServiceData = serviceData & { - model: SwitchBotBLEModel.Curtain, - modelName: SwitchBotBLEModelName.Curtain, - modelFriendlyName: SwitchBotBLEModelFriendlyName.Curtain, - calibration: boolean; - battery: number; - inMotion: boolean; - position: number; - lightLevel: number; - deviceChain: number; -}; + model: SwitchBotBLEModel.Curtain + modelName: SwitchBotBLEModelName.Curtain + modelFriendlyName: SwitchBotBLEModelFriendlyName.Curtain + calibration: boolean + battery: number + inMotion: boolean + position: number + lightLevel: number + deviceChain: number +} export type curtain3ServiceData = serviceData & { - model: SwitchBotBLEModel.Curtain3, - modelName: SwitchBotBLEModelName.Curtain3, - modelFriendlyName: SwitchBotBLEModelFriendlyName.Curtain3, - calibration: boolean; - battery: number; - inMotion: boolean; - position: number; - lightLevel: number; - deviceChain: number; -}; + model: SwitchBotBLEModel.Curtain3 + modelName: SwitchBotBLEModelName.Curtain3 + modelFriendlyName: SwitchBotBLEModelFriendlyName.Curtain3 + calibration: boolean + battery: number + inMotion: boolean + position: number + lightLevel: number + deviceChain: number +} export type stripLightServiceData = serviceData & { - model: SwitchBotBLEModel.StripLight, - modelName: SwitchBotBLEModelName.StripLight, - modelFriendlyName: SwitchBotBLEModelFriendlyName.StripLight, - power: boolean; - state: boolean; - red: number; - green: number; - blue: number; - brightness: number; - delay: number; - preset: number; - color_mode: number; - speed: number; - loop_index: number; -}; + model: SwitchBotBLEModel.StripLight + modelName: SwitchBotBLEModelName.StripLight + modelFriendlyName: SwitchBotBLEModelFriendlyName.StripLight + power: boolean + state: boolean + red: number + green: number + blue: number + brightness: number + delay: number + preset: number + color_mode: number + speed: number + loop_index: number +} export type lockServiceData = serviceData & { - model: SwitchBotBLEModel.Lock, - modelName: SwitchBotBLEModelName.Lock, - modelFriendlyName: SwitchBotBLEModelFriendlyName.Lock, - battery: number; - calibration: boolean; - status: string; - update_from_secondary_lock: boolean; - door_open: string; - double_lock_mode: boolean; - unclosed_alarm: boolean; - unlocked_alarm: boolean; - auto_lock_paused: boolean; - night_latch: boolean; -}; + model: SwitchBotBLEModel.Lock + modelName: SwitchBotBLEModelName.Lock + modelFriendlyName: SwitchBotBLEModelFriendlyName.Lock + battery: number + calibration: boolean + status: string + update_from_secondary_lock: boolean + door_open: string + double_lock_mode: boolean + unclosed_alarm: boolean + unlocked_alarm: boolean + auto_lock_paused: boolean + night_latch: boolean +} export type lockProServiceData = serviceData & { - model: SwitchBotBLEModel.LockPro, - modelName: SwitchBotBLEModelName.LockPro, - modelFriendlyName: SwitchBotBLEModelFriendlyName.LockPro, - battery: number; - calibration: boolean; - status: string; - update_from_secondary_lock: boolean; - door_open: string; - double_lock_mode: boolean; - unclosed_alarm: boolean; - unlocked_alarm: boolean; - auto_lock_paused: boolean; - night_latch: boolean; -}; + model: SwitchBotBLEModel.LockPro + modelName: SwitchBotBLEModelName.LockPro + modelFriendlyName: SwitchBotBLEModelFriendlyName.LockPro + battery: number + calibration: boolean + status: string + update_from_secondary_lock: boolean + door_open: string + double_lock_mode: boolean + unclosed_alarm: boolean + unlocked_alarm: boolean + auto_lock_paused: boolean + night_latch: boolean +} export type meterServiceData = serviceData & { - model: SwitchBotBLEModel.Meter, - modelName: SwitchBotBLEModelName.Meter, - modelFriendlyName: SwitchBotBLEModelFriendlyName.Meter, - temperature: temperature; - fahrenheit: boolean; - humidity: number; - battery: number; -}; + model: SwitchBotBLEModel.Meter + modelName: SwitchBotBLEModelName.Meter + modelFriendlyName: SwitchBotBLEModelFriendlyName.Meter + temperature: temperature + fahrenheit: boolean + humidity: number + battery: number +} export type meterPlusServiceData = serviceData & { - model: SwitchBotBLEModel.MeterPlus, - modelName: SwitchBotBLEModelName.MeterPlus, - modelFriendlyName: SwitchBotBLEModelFriendlyName.MeterPlus, - temperature: temperature; - fahrenheit: boolean; - humidity: number; - battery: number; -}; + model: SwitchBotBLEModel.MeterPlus + modelName: SwitchBotBLEModelName.MeterPlus + modelFriendlyName: SwitchBotBLEModelFriendlyName.MeterPlus + temperature: temperature + fahrenheit: boolean + humidity: number + battery: number +} export type outdoorMeterServiceData = serviceData & { - model: SwitchBotBLEModel.OutdoorMeter, - modelName: SwitchBotBLEModelName.OutdoorMeter, - modelFriendlyName: SwitchBotBLEModelFriendlyName.OutdoorMeter, - temperature: temperature; - fahrenheit: boolean; - humidity: number; - battery: number; -}; + model: SwitchBotBLEModel.OutdoorMeter + modelName: SwitchBotBLEModelName.OutdoorMeter + modelFriendlyName: SwitchBotBLEModelFriendlyName.OutdoorMeter + temperature: temperature + fahrenheit: boolean + humidity: number + battery: number +} export type motionSensorServiceData = serviceData & { - model: SwitchBotBLEModel.MotionSensor, - modelName: SwitchBotBLEModelName.MotionSensor, - modelFriendlyName: SwitchBotBLEModelFriendlyName.MotionSensor, - tested: boolean; - movement: boolean; - battery: number; - led: number; - iot: number; - sense_distance: number; - lightLevel: string; - is_light: boolean; -}; + model: SwitchBotBLEModel.MotionSensor + modelName: SwitchBotBLEModelName.MotionSensor + modelFriendlyName: SwitchBotBLEModelFriendlyName.MotionSensor + tested: boolean + movement: boolean + battery: number + led: number + iot: number + sense_distance: number + lightLevel: string + is_light: boolean +} export type plugMiniUSServiceData = serviceData & { - model: SwitchBotBLEModel.PlugMiniUS, - modelName: SwitchBotBLEModelName.PlugMini, - modelFriendlyName: SwitchBotBLEModelFriendlyName.PlugMini, - state: string; - delay: boolean; - timer: boolean; - syncUtcTime: boolean; - wifiRssi: number; - overload: boolean; - currentPower: number; -}; + model: SwitchBotBLEModel.PlugMiniUS + modelName: SwitchBotBLEModelName.PlugMini + modelFriendlyName: SwitchBotBLEModelFriendlyName.PlugMini + state: string + delay: boolean + timer: boolean + syncUtcTime: boolean + wifiRssi: number + overload: boolean + currentPower: number +} export type plugMiniJPServiceData = serviceData & { - model: SwitchBotBLEModel.PlugMiniUS, - modelName: SwitchBotBLEModelName.PlugMini, - modelFriendlyName: SwitchBotBLEModelFriendlyName.PlugMini, - state: string; - delay: boolean; - timer: boolean; - syncUtcTime: boolean; - wifiRssi: number; - overload: boolean; - currentPower: number; -}; + model: SwitchBotBLEModel.PlugMiniUS + modelName: SwitchBotBLEModelName.PlugMini + modelFriendlyName: SwitchBotBLEModelFriendlyName.PlugMini + state: string + delay: boolean + timer: boolean + syncUtcTime: boolean + wifiRssi: number + overload: boolean + currentPower: number +} export type blindTiltServiceData = serviceData & { - model: SwitchBotBLEModel.BlindTilt, - modelName: SwitchBotBLEModelName.BlindTilt, - modelFriendlyName: SwitchBotBLEModelFriendlyName.BlindTilt, - calibration: boolean; - battery: number; - inMotion: boolean; - tilt: number; - lightLevel: number; -}; + model: SwitchBotBLEModel.BlindTilt + modelName: SwitchBotBLEModelName.BlindTilt + modelFriendlyName: SwitchBotBLEModelFriendlyName.BlindTilt + calibration: boolean + battery: number + inMotion: boolean + tilt: number + lightLevel: number +} export type ceilingLightServiceData = serviceData & { - model: SwitchBotBLEModel.CeilingLight, - modelName: SwitchBotBLEModelName.CeilingLight, - modelFriendlyName: SwitchBotBLEModelFriendlyName.CeilingLight, - color_temperature: number; - power: boolean; - state: boolean; - red: number; - green: number; - blue: number; - brightness: number; - delay: number; - preset: number; - color_mode: number; - speed: number; - loop_index: number; -}; + model: SwitchBotBLEModel.CeilingLight + modelName: SwitchBotBLEModelName.CeilingLight + modelFriendlyName: SwitchBotBLEModelFriendlyName.CeilingLight + color_temperature: number + power: boolean + state: boolean + red: number + green: number + blue: number + brightness: number + delay: number + preset: number + color_mode: number + speed: number + loop_index: number +} export type ceilingLightProServiceData = serviceData & { - model: SwitchBotBLEModel.CeilingLightPro, - modelName: SwitchBotBLEModelName.CeilingLightPro, - modelFriendlyName: SwitchBotBLEModelFriendlyName.CeilingLightPro, - color_temperature: number; - power: boolean; - state: boolean; - red: number; - green: number; - blue: number; - brightness: number; - delay: number; - preset: number; - color_mode: number; - speed: number; - loop_index: number; -}; + model: SwitchBotBLEModel.CeilingLightPro + modelName: SwitchBotBLEModelName.CeilingLightPro + modelFriendlyName: SwitchBotBLEModelFriendlyName.CeilingLightPro + color_temperature: number + power: boolean + state: boolean + red: number + green: number + blue: number + brightness: number + delay: number + preset: number + color_mode: number + speed: number + loop_index: number +} export type hub2ServiceData = serviceData & { - model: SwitchBotBLEModel.Hub2, - modelName: SwitchBotBLEModelName.Hub2, - modelFriendlyName: SwitchBotBLEModelFriendlyName.Hub2, - temperature: temperature; - fahrenheit: boolean; - humidity: number; - lightLevel: number; -}; + model: SwitchBotBLEModel.Hub2 + modelName: SwitchBotBLEModelName.Hub2 + modelFriendlyName: SwitchBotBLEModelFriendlyName.Hub2 + temperature: temperature + fahrenheit: boolean + humidity: number + lightLevel: number +} export type batteryCirculatorFanServiceData = serviceData & { - model: SwitchBotBLEModel.Unknown, - modelName: SwitchBotBLEModelName.Unknown, - modelFriendlyName: SwitchBotBLEModelFriendlyName.Unknown, - state: string; - fanSpeed: number; -}; + model: SwitchBotBLEModel.Unknown + modelName: SwitchBotBLEModelName.Unknown + modelFriendlyName: SwitchBotBLEModelFriendlyName.Unknown + state: string + fanSpeed: number +} export type waterLeakDetectorServiceData = serviceData & { - model: SwitchBotBLEModel.Unknown, - modelName: SwitchBotBLEModelName.Unknown, - modelFriendlyName: SwitchBotBLEModelFriendlyName.Unknown, - state: boolean; - status: number; - battery: number; -}; + model: SwitchBotBLEModel.Unknown + modelName: SwitchBotBLEModelName.Unknown + modelFriendlyName: SwitchBotBLEModelFriendlyName.Unknown + state: boolean + status: number + battery: number +} export type humidifierServiceData = serviceData & { - model: SwitchBotBLEModel.Humidifier, - modelName: SwitchBotBLEModelName.Humidifier, - modelFriendlyName: SwitchBotBLEModelFriendlyName.Humidifier, - onState: boolean; - autoMode: boolean; - percentage: number; - humidity: number; -}; + model: SwitchBotBLEModel.Humidifier + modelName: SwitchBotBLEModelName.Humidifier + modelFriendlyName: SwitchBotBLEModelFriendlyName.Humidifier + onState: boolean + autoMode: boolean + percentage: number + humidity: number +} export type robotVacuumCleanerServiceData = serviceData & { - model: SwitchBotBLEModel.Unknown, - modelName: SwitchBotBLEModelName.Unknown, - modelFriendlyName: SwitchBotBLEModelFriendlyName.Unknown, - state: string; - battery: number; -}; + model: SwitchBotBLEModel.Unknown + modelName: SwitchBotBLEModelName.Unknown + modelFriendlyName: SwitchBotBLEModelFriendlyName.Unknown + state: string + battery: number +} -export type temperature = { - c: number; - f: number; -}; \ No newline at end of file +export interface temperature { + c: number + f: number +} diff --git a/src/types/devicelist.ts b/src/types/devicelist.ts index ad98e5ff..41d65f8c 100644 --- a/src/types/devicelist.ts +++ b/src/types/devicelist.ts @@ -3,122 +3,122 @@ * devicelist.ts: @switchbot/homebridge-switchbot platform class. */ -export type deviceList = { - device: device[]; -}; +export interface deviceList { + device: device[] +} -export type device = { - deviceId: string; - deviceName: string; - deviceType: string; - enableCloudService: boolean; - hubDeviceId: string; - version?: number; -}; +export interface device { + deviceId: string + deviceName: string + deviceType: string + enableCloudService: boolean + hubDeviceId: string + version?: number +} -export type bot = device & {}; +export type bot = device & {} export type curtain = device & { - curtainDevicesIds: string[]; - calibrate: boolean; - group: boolean; - master: boolean; - openDirection: string; -}; + curtainDevicesIds: string[] + calibrate: boolean + group: boolean + master: boolean + openDirection: string +} export type curtain3 = device & { - curtainDevicesIds: string[]; - calibrate: boolean; - group: boolean; - master: boolean; - openDirection?: string; -}; + curtainDevicesIds: string[] + calibrate: boolean + group: boolean + master: boolean + openDirection?: string +} -export type hub2 = device & {}; +export type hub2 = device & {} -export type meter = device & {}; +export type meter = device & {} -export type meterPlus = device & {}; +export type meterPlus = device & {} -export type outdoorMeter = device & {}; +export type outdoorMeter = device & {} export type lock = device & { - group: boolean; - master: boolean; - groupName: string; - lockDevicesIds: string[]; -}; + group: boolean + master: boolean + groupName: string + lockDevicesIds: string[] +} export type lockPro = device & { - group: boolean; - master: boolean; - groupName: string; - lockDevicesIds: string[]; -}; + group: boolean + master: boolean + groupName: string + lockDevicesIds: string[] +} export type keypad = device & { - remoteType: string; - lockDeviceId: string; + remoteType: string + lockDeviceId: string keyList: keyList -}; +} export type keypadTouch = device & { - remoteType: string; - lockDeviceId: string; + remoteType: string + lockDeviceId: string keyList: keyList -}; +} -type keyList = { - id: number; - name: string; - type: string; - password: string; - iv: string; - status: string; - createTime: number; -}; +interface keyList { + id: number + name: string + type: string + password: string + iv: string + status: string + createTime: number +} -export type remote = device & {}; +export type remote = device & {} -export type motionSensor = device & {}; +export type motionSensor = device & {} -export type contactSensor = device & {}; +export type contactSensor = device & {} -export type waterLeakDetector = device & {}; +export type waterLeakDetector = device & {} -export type ceilingLight = device & {}; +export type ceilingLight = device & {} -export type ceilingLightPro = device & {}; +export type ceilingLightPro = device & {} -export type plug = device & {}; +export type plug = device & {} -export type plugMini = device & {}; +export type plugMini = device & {} -export type stripLight = device & {}; +export type stripLight = device & {} -export type colorBulb = device & {}; +export type colorBulb = device & {} -export type robotVacuumCleanerS1 = device & {}; +export type robotVacuumCleanerS1 = device & {} -export type robotVacuumCleanerS1Plus = device & {}; +export type robotVacuumCleanerS1Plus = device & {} -export type floorCleaningRobotS10 = device & {}; +export type floorCleaningRobotS10 = device & {} -export type humidifier = device & {}; +export type humidifier = device & {} -export type indoorCam = device & {}; +export type indoorCam = device & {} -export type pantiltCam = device & {}; +export type pantiltCam = device & {} -export type pantiltCam2k = device & {}; +export type pantiltCam2k = device & {} export type blindTilt = device & { - blindTiltDevicesIds: string[]; - calibrate: boolean; - group: boolean; - master: boolean; - direction: string; - slidePosition: number; -}; - -export type batteryCirculatorFan = device & {}; \ No newline at end of file + blindTiltDevicesIds: string[] + calibrate: boolean + group: boolean + master: boolean + direction: string + slidePosition: number +} + +export type batteryCirculatorFan = device & {} diff --git a/src/types/deviceresponse.ts b/src/types/deviceresponse.ts index b7916e98..080f8b8b 100644 --- a/src/types/deviceresponse.ts +++ b/src/types/deviceresponse.ts @@ -1,15 +1,14 @@ -import type { deviceList } from './devicelist'; -import type { infraredRemoteList } from './irdevicelist'; +import type { deviceList } from './devicelist' +import type { infraredRemoteList } from './irdevicelist' +// json response from SwitchBot API +export interface devices { + statusCode: 100 | 190 | 'n/a' + message: string + body: body +} -//json response from SwitchBot API -export type devices = { - statusCode: 100 | 190 | 'n/a'; - message: string; - body: body; -}; - -type body = { - deviceList: deviceList; - infraredRemoteList: infraredRemoteList; -}; \ No newline at end of file +interface body { + deviceList: deviceList + infraredRemoteList: infraredRemoteList +} diff --git a/src/types/devicestatus.ts b/src/types/devicestatus.ts index 904387a7..c8a1be46 100644 --- a/src/types/devicestatus.ts +++ b/src/types/devicestatus.ts @@ -1,178 +1,178 @@ -import type { device } from './devicelist'; +import type { device } from './devicelist' /* Copyright(C) 2017-2024, donavanbecker (https://github.com/donavanbecker). All rights reserved. * * devicestatus.ts: @switchbot/homebridge-switchbot platform class. */ -export type deviceStatusRequest = { - statusCode: number; - message: string; - body: deviceStatus; -}; +export interface deviceStatusRequest { + statusCode: number + message: string + body: deviceStatus +} export interface deviceStatus extends device { - //properties on all devices - deviceId: string; - deviceType: string; - hubDeviceId: string; - version: number; + // properties on all devices + deviceId: string + deviceType: string + hubDeviceId: string + version: number }; export type botStatus = deviceStatus & { - power: string; - battery: number; - mode: 'pressMode' | 'switchMode' | 'customizeMode'; -}; + power: string + battery: number + mode: 'pressMode' | 'switchMode' | 'customizeMode' +} export type curtainStatus = deviceStatus & { - calibrate: boolean; - group: boolean; - moving: boolean; - battery: number; - slidePosition: number; - lightLevel?: 'bright' | 'dim'; -}; + calibrate: boolean + group: boolean + moving: boolean + battery: number + slidePosition: number + lightLevel?: 'bright' | 'dim' +} export type meterStatus = deviceStatus & { - temperature: number; - battery: number; - humidity: number; -}; + temperature: number + battery: number + humidity: number +} export type meterPlusStatus = deviceStatus & { - temperature: number; - battery: number; - humidity: number; -}; + temperature: number + battery: number + humidity: number +} export type outdoorMeterStatus = deviceStatus & { - battery: number; - temperature: number; - humidity: number; -}; + battery: number + temperature: number + humidity: number +} export type lockStatus = deviceStatus & { - lockState: string; - doorState: string; - moveDetected: boolean; - battery: number; -}; + lockState: string + doorState: string + moveDetected: boolean + battery: number +} export type lockProStatus = deviceStatus & { - lockState: string; - doorState: string; - moveDetected: boolean; - battery: number; -}; + lockState: string + doorState: string + moveDetected: boolean + battery: number +} export type motionSensorStatus = deviceStatus & { - battery: number; - moveDetected: boolean; - brightness: 'bright' | 'dim'; -}; + battery: number + moveDetected: boolean + brightness: 'bright' | 'dim' +} export type contactSensorStatus = deviceStatus & { - battery: number; - moveDetected: boolean; - openState: 'open' | 'close' | 'timeOutNotClose'; - brightness: 'bright' | 'dim'; -}; + battery: number + moveDetected: boolean + openState: 'open' | 'close' | 'timeOutNotClose' + brightness: 'bright' | 'dim' +} export type waterLeakDetectorStatus = deviceStatus & { - battery: number; - status: 0 /*dry*/ | 1 /*leak detected*/; -}; + battery: number + status: 0 /* dry */ | 1 /* leak detected */ +} export type ceilingLightStatus = deviceStatus & { - power: boolean; - brightness: number; - colorTemperature: number; -}; + power: boolean + brightness: number + colorTemperature: number +} export type ceilingLightProStatus = deviceStatus & { - power: boolean; - brightness: number; - colorTemperature: number; -}; + power: boolean + brightness: number + colorTemperature: number +} export type plugStatus = deviceStatus & { - power: string; - version: string; -}; + power: string + version: string +} export type plugMiniStatus = deviceStatus & { - voltage: Float64Array; - weight: Float64Array; - electricityOfDay: number; - electricCurrent: Float64Array; - power: string; -}; + voltage: Float64Array + weight: Float64Array + electricityOfDay: number + electricCurrent: Float64Array + power: string +} export type stripLightStatus = deviceStatus & { - power: string; - brightness: number; - color: string; -}; + power: string + brightness: number + color: string +} export type colorBulbStatus = deviceStatus & { - power: string; - brightness: number; - color: string; - colorTemperature: number; -}; + power: string + brightness: number + color: string + colorTemperature: number +} export type robotVacuumCleanerS1Status = deviceStatus & { - workingStatus: string; - onlineStatus: string; - battery: number; -}; + workingStatus: string + onlineStatus: string + battery: number +} export type robotVacuumCleanerS1PlusStatus = deviceStatus & { - workingStatus: string; - onlineStatus: string; - battery: number; -}; + workingStatus: string + onlineStatus: string + battery: number +} export type floorCleaningRobotS10Status = deviceStatus & { - workingStatus: string; - onlineStatus: string; - battery: number; - waterBaseBattery: number; - taskType: string; -}; + workingStatus: string + onlineStatus: string + battery: number + waterBaseBattery: number + taskType: string +} export type humidifierStatus = deviceStatus & { - power: string; - humidity: number; - temperature: number; - nebulizationEfficiency: number; - auto: boolean; - childLock: boolean; - sound: boolean; - lackWater: boolean; -}; + power: string + humidity: number + temperature: number + nebulizationEfficiency: number + auto: boolean + childLock: boolean + sound: boolean + lackWater: boolean +} export type blindTiltStatus = deviceStatus & { - calibrate: boolean; - battery: number; - direction: string; - slidePosition: string; - lightLevel?: 'bright' | 'dim'; -}; + calibrate: boolean + battery: number + direction: string + slidePosition: string + lightLevel?: 'bright' | 'dim' +} export type hub2Status = deviceStatus & { - temperature: number; - lightLevel: number; - humidity: number; -}; + temperature: number + lightLevel: number + humidity: number +} export type batteryCirculatorFanStatus = deviceStatus & { - mode: 'direct' | 'natural' | 'sleep' | 'baby'; - battery: number; - power: string; - nightStatus: number; - oscillation: string; - verticalOscillation: string; - chargingStatus: string; - fanSpeed: number; -}; + mode: 'direct' | 'natural' | 'sleep' | 'baby' + battery: number + power: string + nightStatus: number + oscillation: string + verticalOscillation: string + chargingStatus: string + fanSpeed: number +} diff --git a/src/types/devicewebhookstatus.ts b/src/types/devicewebhookstatus.ts index 61dcd143..89194c08 100644 --- a/src/types/devicewebhookstatus.ts +++ b/src/types/devicewebhookstatus.ts @@ -2,195 +2,190 @@ * * devicestatus.ts: @switchbot/homebridge-switchbot platform class. */ -export { deviceWebhook }; - -type deviceWebhook = { - eventType: string, - eventVersion: string, +interface deviceWebhook { + eventType: string + eventVersion: string context: deviceWebhookContext -}; +} + +export { deviceWebhook } -export type deviceWebhookContext = { - //properties on all devices - deviceMac: string; - deviceType: string; +export interface deviceWebhookContext { + // properties on all devices + deviceMac: string + deviceType: string timeOfSample: number -}; +} export type botWebhookContext = deviceWebhookContext & { - power: string, //"on"or"off" - battery: number, - deviceMode: 'pressMode' | 'switchMode' | 'customizeMode', -}; + power: string // "on"or"off" + battery: number + deviceMode: 'pressMode' | 'switchMode' | 'customizeMode' +} export type curtainWebhookContext = deviceWebhookContext & { - calibrate: boolean, - group: boolean, - slidePosition: number, //0~100 - battery: number, -}; + calibrate: boolean + group: boolean + slidePosition: number // 0~100 + battery: number +} export type curtain3WebhookContext = deviceWebhookContext & { - calibrate: boolean, - group: boolean, - slidePosition: number, //0~100 - battery: number, -}; + calibrate: boolean + group: boolean + slidePosition: number // 0~100 + battery: number +} export type motionSensorWebhookContext = deviceWebhookContext & { - detectionState: 'NOT_DETECTED' | 'DETECTED', -}; + detectionState: 'NOT_DETECTED' | 'DETECTED' +} export type contactSensorWebhookContext = deviceWebhookContext & { - detectionState: 'NOT_DETECTED' | 'DETECTED', - doorMode: 'IN_DOOR' | 'OUT_DOOR', - brightness: 'dim' | 'bright', - openState: 'open' | 'close' | 'timeOutNotClose', -}; + detectionState: 'NOT_DETECTED' | 'DETECTED' + doorMode: 'IN_DOOR' | 'OUT_DOOR' + brightness: 'dim' | 'bright' + openState: 'open' | 'close' | 'timeOutNotClose' +} export type waterLeakDetectorWebhookContext = deviceWebhookContext & { - detectionState: 0 | 1, - battery: number, //0~100 -}; + detectionState: 0 | 1 + battery: number // 0~100 +} export type meterWebhookContext = deviceWebhookContext & { - temperature: number, - scale: 'CELSIUS' | 'FAHRENHEIT', - humidity: number, -}; + temperature: number + scale: 'CELSIUS' | 'FAHRENHEIT' + humidity: number +} export type meterPlusWebhookContext = deviceWebhookContext & { - temperature: number, - scale: 'CELSIUS' | 'FAHRENHEIT', - humidity: number, -}; + temperature: number + scale: 'CELSIUS' | 'FAHRENHEIT' + humidity: number +} export type outdoorMeterWebhookContext = deviceWebhookContext & { - temperature: number, - scale: 'CELSIUS' | 'FAHRENHEIT', - humidity: number, -}; + temperature: number + scale: 'CELSIUS' | 'FAHRENHEIT' + humidity: number +} export type lockWebhookContext = deviceWebhookContext & { - lockState: 'UNLOCKED' | 'LOCKED' | 'JAMMED', -}; + lockState: 'UNLOCKED' | 'LOCKED' | 'JAMMED' +} export type lockProWebhookContext = deviceWebhookContext & { - lockState: 'UNLOCKED' | 'LOCKED' | 'JAMMED', -}; + lockState: 'UNLOCKED' | 'LOCKED' | 'JAMMED' +} export type indoorCameraWebhookContext = deviceWebhookContext & { - detectionState: 'DETECTED', -}; + detectionState: 'DETECTED' +} export type panTiltCamWebhookContext = deviceWebhookContext & { - detectionState: 'DETECTED', -}; + detectionState: 'DETECTED' +} export type colorBulbWebhookContext = deviceWebhookContext & { - powerState: 'ON' | 'OFF', - brightness: number, - color: string, //RGB 255:255:255 - colorTemperature: number, // 2700~6500 -}; + powerState: 'ON' | 'OFF' + brightness: number + color: string // RGB 255:255:255 + colorTemperature: number // 2700~6500 +} export type stripLightWebhookContext = deviceWebhookContext & { - powerState: 'ON' | 'OFF', - brightness: number, - color: string, //RGB 255:255:255 -}; + powerState: 'ON' | 'OFF' + brightness: number + color: string // RGB 255:255:255 +} export type plugWebhookContext = deviceWebhookContext & { - powerState: 'ON' | 'OFF', -}; + powerState: 'ON' | 'OFF' +} export type plugMiniUSWebhookContext = deviceWebhookContext & { - powerState: 'ON' | 'OFF', -}; + powerState: 'ON' | 'OFF' +} export type plugMiniJPWebhookContext = deviceWebhookContext & { - powerState: 'ON' | 'OFF', -}; + powerState: 'ON' | 'OFF' +} export type robotVacuumCleanerS1WebhookContext = deviceWebhookContext & { - workingStatus: 'Standby' | 'Clearing' | 'Paused' | 'GotoChargeBase' | 'Charging' | 'ChargeDone' | 'Dormant' | 'InTrouble' - | 'InRemoteControl' | 'InDustCollecting', - onlineStatus: 'online' | 'offline', - battery: number, //0~100 -}; + workingStatus: 'Standby' | 'Clearing' | 'Paused' | 'GotoChargeBase' | 'Charging' | 'ChargeDone' | 'Dormant' | 'InTrouble' | 'InRemoteControl' | 'InDustCollecting' + onlineStatus: 'online' | 'offline' + battery: number // 0~100 +} export type robotVacuumCleanerS1PlusWebhookContext = deviceWebhookContext & { - workingStatus: 'Standby' | 'Clearing' | 'Paused' | 'GotoChargeBase' | 'Charging' | 'ChargeDone' | 'Dormant' | 'InTrouble' - | 'InRemoteControl' | 'InDustCollecting', - onlineStatus: 'online' | 'offline', - battery: number, //0~100 -}; + workingStatus: 'Standby' | 'Clearing' | 'Paused' | 'GotoChargeBase' | 'Charging' | 'ChargeDone' | 'Dormant' | 'InTrouble' | 'InRemoteControl' | 'InDustCollecting' + onlineStatus: 'online' | 'offline' + battery: number // 0~100 +} export type floorCleaningRobotS10WebhookContext = deviceWebhookContext & { - workingStatus: 'Standby' | 'Clearing' | 'Paused' | 'GotoChargeBase' | 'Charging' | 'ChargeDone' | 'Dormant' | 'InTrouble' - | 'InRemoteControl' | 'InDustCollecting', - onlineStatus: 'online' | 'offline', - battery: number, //0~100 - waterBaseBattery: number, //0~100 - taskType: 'standBy' | 'explore' | 'cleanAll' | 'cleanArea' | 'cleanRoom' | 'fillWater' | 'deepWashing' | 'backToCharge' | 'markingWaterBase' - | 'drying' | 'collectDust' | 'remoteControl' | 'cleanWithExplorer' | 'fillWaterForHumi' | 'markingHumi', -}; + workingStatus: 'Standby' | 'Clearing' | 'Paused' | 'GotoChargeBase' | 'Charging' | 'ChargeDone' | 'Dormant' | 'InTrouble' | 'InRemoteControl' | 'InDustCollecting' + onlineStatus: 'online' | 'offline' + battery: number // 0~100 + waterBaseBattery: number // 0~100 + taskType: 'standBy' | 'explore' | 'cleanAll' | 'cleanArea' | 'cleanRoom' | 'fillWater' | 'deepWashing' | 'backToCharge' | 'markingWaterBase' | 'drying' | 'collectDust' | 'remoteControl' | 'cleanWithExplorer' | 'fillWaterForHumi' | 'markingHumi' +} export type ceilingLightWebhookContext = deviceWebhookContext & { - powerState: 'ON' | 'OFF', - brightness: number, - colorTemperature: number, // 2700~6500 -}; + powerState: 'ON' | 'OFF' + brightness: number + colorTemperature: number // 2700~6500 +} export type ceilingLightProWebhookContext = deviceWebhookContext & { - powerState: 'ON' | 'OFF', - brightness: number, - colorTemperature: number, // 2700~6500 -}; + powerState: 'ON' | 'OFF' + brightness: number + colorTemperature: number // 2700~6500 +} export type keypadWebhookContext = deviceWebhookContext & { - eventName: 'createKey' | 'deleteKey', - commandId: string, - result: 'success' | 'failed' | 'timeout', -}; + eventName: 'createKey' | 'deleteKey' + commandId: string + result: 'success' | 'failed' | 'timeout' +} export type keypadTouchWebhookContext = deviceWebhookContext & { - eventName: 'createKey' | 'deleteKey', - commandId: string, - result: 'success' | 'failed' | 'timeout', -}; + eventName: 'createKey' | 'deleteKey' + commandId: string + result: 'success' | 'failed' | 'timeout' +} export type hub2WebhookContext = deviceWebhookContext & { - temperature: number, - humidity: number, - lightLevel: number, - scale: 'CELSIUS' | 'FAHRENHEIT', -}; + temperature: number + humidity: number + lightLevel: number + scale: 'CELSIUS' | 'FAHRENHEIT' +} export type batteryCirculatorFanWebhookContext = deviceWebhookContext & { - mode: 'direct' | 'natural' | 'sleep' | 'baby', - version: string, - battery: number, - powerState: 'ON' | 'OFF', - nightStatus: 'off' | 1 | 2, - oscillation: 'on' | 'off', - verticalOscillation: 'on' | 'off', - chargingStatus: 'charging' | 'uncharged', - fanSpeed: number, //1~100 -}; + mode: 'direct' | 'natural' | 'sleep' | 'baby' + version: string + battery: number + powerState: 'ON' | 'OFF' + nightStatus: 'off' | 1 | 2 + oscillation: 'on' | 'off' + verticalOscillation: 'on' | 'off' + chargingStatus: 'charging' | 'uncharged' + fanSpeed: number // 1~100 +} export type blindTiltWebhookContext = deviceWebhookContext & { - version: string, - calibrate: boolean, - group: boolean, - direction: string, - slidePosition: number, //0~100 - battery: number, -}; + version: string + calibrate: boolean + group: boolean + direction: string + slidePosition: number // 0~100 + battery: number +} export type humidifierWebhookContext = deviceWebhookContext & { - temperature: number, - humidity: number, - scale: 'CELSIUS' | 'FAHRENHEIT', -}; - + temperature: number + humidity: number + scale: 'CELSIUS' | 'FAHRENHEIT' +} diff --git a/src/types/irdevicelist.ts b/src/types/irdevicelist.ts index 355ef4ff..a2a67bfe 100644 --- a/src/types/irdevicelist.ts +++ b/src/types/irdevicelist.ts @@ -2,14 +2,14 @@ * * irdevicelist.ts: @switchbot/homebridge-switchbot platform class. */ -//a list of virtual infrared remote devices that are linked to SwitchBot Hubs. -export type infraredRemoteList = { - device: irdevice[]; -}; +// a list of virtual infrared remote devices that are linked to SwitchBot Hubs. +export interface infraredRemoteList { + device: irdevice[] +} -export type irdevice = { - deviceId?: string; - deviceName: string; - remoteType: string; - hubDeviceId: string; -}; +export interface irdevice { + deviceId?: string + deviceName: string + remoteType: string + hubDeviceId: string +} diff --git a/src/types/pushbody.ts b/src/types/pushbody.ts index 4e29f98c..9e3ed39d 100644 --- a/src/types/pushbody.ts +++ b/src/types/pushbody.ts @@ -2,8 +2,8 @@ * * pushbody.ts: @switchbot/homebridge-switchbot platform class. */ -export type body = { - command: string; - parameter: string; - commandType: string; -}; +export interface body { + command: string + parameter: string + commandType: string +} diff --git a/src/utils.ts b/src/utils.ts index 81dc862e..e61f38d2 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -2,6 +2,9 @@ * * util.ts: @switchbot/homebridge-switchbot platform class. */ +import type { devicesConfig } from './settings.js' +import type { blindTilt, curtain, curtain3, device } from './types/devicelist.js' + export enum SwitchBotModel { HubMini = 'W0202200', HubPlus = 'SwitchBot Hub S1', @@ -115,19 +118,16 @@ export enum BlindTiltMappingMode { UseTiltForDirection = 'use_tilt_for_direction', } -import type { devicesConfig } from './settings.js'; -import type { blindTilt, curtain, curtain3, device } from './types/devicelist.js'; - export function isCurtainDevice(device: device & devicesConfig): device is (curtain | curtain3) & devicesConfig { - return device.deviceType === 'Curtain' || device.deviceType === 'Curtain3'; + return device.deviceType === 'Curtain' || device.deviceType === 'Curtain3' } export function isBlindTiltDevice(device: device & devicesConfig): device is blindTilt & devicesConfig { - return device.deviceType === 'Blind Tilt'; + return device.deviceType === 'Blind Tilt' } export function sleep(ms: number): Promise { - return new Promise((resolve) => setTimeout(resolve, ms)); + return new Promise(resolve => setTimeout(resolve, ms)) } /** * Check if the humidity is within the min and max range @@ -135,63 +135,62 @@ export function sleep(ms: number): Promise { * @param min - The minimum humidity value * @param max - The maximum humidity value * @returns The humidity value -**/ + */ export function validHumidity(humidity: number, min?: number, max?: number): number { if (humidity < (min || 0)) { - return min ?? 0; + return min ?? 0 } else if (humidity > (max || 100)) { - return max ?? 100; + return max ?? 100 } - return humidity; + return humidity } /** * Converts the value to celsius if the temperature units are in Fahrenheit -**/ + */ export function convertUnits(value: number, unit: string, convert?: string): number { if (unit === 'CELSIUS' && convert === 'CELSIUS') { - return Math.round((value * 9) / 5 + 32); + return Math.round((value * 9) / 5 + 32) } else if (unit === 'FAHRENHEIT' && convert === 'FAHRENHEIT') { // celsius should be to the nearest 0.5 degree - return Math.round((5 / 9) * (value - 32) * 2) / 2; + return Math.round((5 / 9) * (value - 32) * 2) / 2 } - return value; + return value } - export function rgb2hs(r: any, g: any, b: any) { /** * Credit: * https://github.com/WickyNilliams/pure-color - **/ - r = parseInt(r); - g = parseInt(g); - b = parseInt(b); - const min = Math.min(r, g, b); - const max = Math.max(r, g, b); - const delta = max - min; - let h; - let s; + */ + r = Number.parseInt(r) + g = Number.parseInt(g) + b = Number.parseInt(b) + const min = Math.min(r, g, b) + const max = Math.max(r, g, b) + const delta = max - min + let h + let s if (max === 0) { - s = 0; + s = 0 } else { - s = (delta / max) * 100; + s = (delta / max) * 100 } if (max === min) { - h = 0; + h = 0 } else if (r === max) { - h = (g - b) / delta; + h = (g - b) / delta } else if (g === max) { - h = 2 + (b - r) / delta; + h = 2 + (b - r) / delta } else if (b === max) { - h = 4 + (r - g) / delta; + h = 4 + (r - g) / delta } - h = Math.min(h * 60, 360); + h = Math.min(h * 60, 360) if (h < 0) { - h += 360; + h += 360 } - return [Math.round(h), Math.round(s)]; + return [Math.round(h), Math.round(s)] } export function hs2rgb(h: any, s: any) { @@ -199,48 +198,48 @@ export function hs2rgb(h: any, s: any) { Credit: https://github.com/WickyNilliams/pure-color */ - h = parseInt(h) / 60; - s = parseInt(s) / 100; - const f = h - Math.floor(h); - const p = 255 * (1 - s); - const q = 255 * (1 - s * f); - const t = 255 * (1 - s * (1 - f)); - let rgb; + h = Number.parseInt(h) / 60 + s = Number.parseInt(s) / 100 + const f = h - Math.floor(h) + const p = 255 * (1 - s) + const q = 255 * (1 - s * f) + const t = 255 * (1 - s * (1 - f)) + let rgb switch (Math.floor(h) % 6) { case 0: - rgb = [255, t, p]; - break; + rgb = [255, t, p] + break case 1: - rgb = [q, 255, p]; - break; + rgb = [q, 255, p] + break case 2: - rgb = [p, 255, t]; - break; + rgb = [p, 255, t] + break case 3: - rgb = [p, q, 255]; - break; + rgb = [p, q, 255] + break case 4: - rgb = [t, p, 255]; - break; + rgb = [t, p, 255] + break case 5: - rgb = [255, p, q]; - break; + rgb = [255, p, q] + break } if (rgb[0] === 255) { - rgb[1] *= 0.8; - rgb[2] *= 0.8; + rgb[1] *= 0.8 + rgb[2] *= 0.8 if (rgb[1] <= 25 && rgb[2] <= 25) { - rgb[1] = 0; - rgb[2] = 0; + rgb[1] = 0 + rgb[2] = 0 } } - return [Math.round(rgb[0]), Math.round(rgb[1]), Math.round(rgb[2])]; + return [Math.round(rgb[0]), Math.round(rgb[1]), Math.round(rgb[2])] } export function k2rgb(k: number) { // Set kelvin to nearest 100, between 2000 and 7100 - k = Math.round(k / 100) * 100; - k = Math.max(Math.min(k, 7100), 2000); + k = Math.round(k / 100) * 100 + k = Math.max(Math.min(k, 7100), 2000) // k should now appear in our table of kelvin to rgb const values = { @@ -295,10 +294,10 @@ export function k2rgb(k: number) { 6900: [247, 245, 255], 7000: [245, 243, 255], 7100: [243, 242, 255], - }; + } // Return the value - return values[k]; + return values[k] } export function m2hs(m) { @@ -708,8 +707,8 @@ export function m2hs(m) { 498: [63.8, 28.3], 499: [63.9, 28.3], 500: [64.1, 28.3], - }; - const input = Math.min(Math.max(Math.round(m), 140), 500); - const toReturn = table[input]; - return [Math.round(toReturn[1]), Math.round(toReturn[0])]; + } + const input = Math.min(Math.max(Math.round(m), 140), 500) + const toReturn = table[input] + return [Math.round(toReturn[1]), Math.round(toReturn[0])] } diff --git a/tsconfig.json b/tsconfig.json index b5ad9a38..eee14d55 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,22 +1,23 @@ { "compilerOptions": { "target": "ES2022", - "module": "ES2022", "lib": [ "DOM", "ES2022" ], - "declaration": true, - "declarationMap": true, - "sourceMap": true, - "outDir": "dist", "rootDir": "src", + "module": "ES2022", + "moduleResolution": "node", + "types": ["node"], "strict": true, - "esModuleInterop": true, "noImplicitAny": false, + "declaration": true, + "declarationMap": true, + "outDir": "dist", + "sourceMap": true, "allowSyntheticDefaultImports": true, - "forceConsistentCasingInFileNames": true, - "moduleResolution": "node", + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true }, "include": [ "src" @@ -24,4 +25,4 @@ "exclude": [ "**/*.spec.ts" ] -} \ No newline at end of file +}

Wl<%Suj+r#f8Te)-YIKaeHs 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