From e91cf863b1af7898f167103821095b4253e0a3be Mon Sep 17 00:00:00 2001 From: HJD Date: Sun, 22 Dec 2024 15:37:49 -0600 Subject: [PATCH] v4.18.0. --- docs/Changelog.md | 5 + docs/ProtectApi.md | 28 +-- docs/ProtectApiEvents.md | 8 +- docs/ProtectLivestream.md | 4 +- docs/ProtectTypes.md | 48 ++--- package-lock.json | 354 ++++++++++++++++------------------ package.json | 18 +- src/protect-api-events.ts | 5 +- src/protect-api-livestream.ts | 17 +- src/protect-api.ts | 72 +++---- 10 files changed, 272 insertions(+), 287 deletions(-) diff --git a/docs/Changelog.md b/docs/Changelog.md index 1ee95e8..6d8f009 100644 --- a/docs/Changelog.md +++ b/docs/Changelog.md @@ -2,6 +2,11 @@ All notable changes to this project will be documented in this file. This project uses [semantic versioning](https://semver.org/). +## 4.18.0 (2024-12-08) + * Breaking change: the minimum supported node version is now node 20. + * Improvement: we've changed strategies to deal with Protect controller quirkiness/bugginess that results in network disconnects from the controller, while ensuring we have reasonable backpressure measures. + * Housekeeping. + ## 4.17.0 (2024-12-08) * Minor updates to the Protect JSON descriptions. * Housekeeping. diff --git a/docs/ProtectApi.md b/docs/ProtectApi.md index 56f8ec3..de04192 100644 --- a/docs/ProtectApi.md +++ b/docs/ProtectApi.md @@ -66,7 +66,7 @@ Create an instance of the UniFi Protect API. ###### Get Signature ```ts -get bootstrap(): null | ProtectNvrBootstrapInterface +get bootstrap(): Nullable ``` Access the Protect controller bootstrap JSON. @@ -78,7 +78,7 @@ request to the Protect controller before accessing the bootstrap JSON. ###### Returns -`null` \| [`ProtectNvrBootstrapInterface`](ProtectTypes.md#protectnvrbootstrapinterface) +`Nullable`\<[`ProtectNvrBootstrapInterface`](ProtectTypes.md#protectnvrbootstrapinterface)\> Returns the bootstrap JSON if the Protect controller has been bootstrapped, `null` otherwise. @@ -206,7 +206,7 @@ process.stdout.write(util.inspect(ufp.bootstrap, { colors: true, depth: null, so ##### getSnapshot() ```ts -getSnapshot(device, options): Promise> +getSnapshot(device, options): Promise>> ``` Retrieve a snapshot image from a Protect camera. @@ -220,7 +220,7 @@ Retrieve a snapshot image from a Protect camera. ###### Returns -`Promise`\<`null` \| `Buffer`\<`ArrayBufferLike`\>\> +`Promise`\<`Nullable`\<`Buffer`\<`ArrayBufferLike`\>\>\> Returns a promise that will resolve to a Buffer containing the JPEG image snapshot if successful, and `null` otherwise. @@ -237,7 +237,7 @@ The `options` object for snapshot parameters accepts the following properties, a ##### getWsEndpoint() ```ts -getWsEndpoint(endpoint, params?): Promise +getWsEndpoint(endpoint, params?): Promise> ``` Return a websocket API endpoint for the requested endpoint type. @@ -251,7 +251,7 @@ Return a websocket API endpoint for the requested endpoint type. ###### Returns -`Promise`\<`null` \| `string`\> +`Promise`\<`Nullable`\<`string`\>\> Returns a promise that will resolve to a URL to the requested endpoint if successful, and `null` otherwise. @@ -271,7 +271,7 @@ Valid API endpoints are `livestream` and `talkback`. retrieve( url, options, -logErrors): Promise +logErrors): Promise> ``` Execute an HTTP fetch request to the Protect controller. @@ -286,7 +286,7 @@ Execute an HTTP fetch request to the Protect controller. ###### Returns -`Promise`\<`null` \| `Response`\> +`Promise`\<`Nullable`\<`Response`\>\> Returns a promise that will resolve to a Response object successful, and `null` otherwise. @@ -299,7 +299,7 @@ This method should be used when direct access to the Protect controller is neede ##### updateDevice() ```ts -updateDevice(device, payload): Promise +updateDevice(device, payload): Promise> ``` Update a Protect device's configuration on the UniFi Protect controller. @@ -319,7 +319,7 @@ Update a Protect device's configuration on the UniFi Protect controller. ###### Returns -`Promise`\<`null` \| `DeviceType`\> +`Promise`\<`Nullable`\<`DeviceType`\>\> Returns a promise that will resolve to the updated device-specific configuration JSON if successful, and `null` otherwise. @@ -382,7 +382,7 @@ Returns the Protect controller name in the following format: ##### enableRtsp() ```ts -enableRtsp(device): Promise +enableRtsp(device): Promise> ``` Utility method that enables all RTSP channels on a given Protect camera. @@ -395,7 +395,7 @@ Utility method that enables all RTSP channels on a given Protect camera. ###### Returns -`Promise`\<`null` \| [`ProtectCameraConfigInterface`](ProtectTypes.md#protectcameraconfiginterface)\> +`Promise`\<`Nullable`\<[`ProtectCameraConfigInterface`](ProtectTypes.md#protectcameraconfiginterface)\>\> Returns a promise that will resolve to the updated [ProtectCameraConfig](ProtectTypes.md#protectcameraconfig) if successful, and `null` otherwise. @@ -540,7 +540,7 @@ Clear the login credentials and terminate any open connection to the UniFi Prote ### ProtectKnownDevicePayloads ```ts -type ProtectKnownDevicePayloads: +type ProtectKnownDevicePayloads = | ProtectCameraConfigPayload | ProtectChimeConfigPayload | ProtectLightConfigPayload @@ -556,7 +556,7 @@ Define our known Protect device payload types. ### ProtectKnownDeviceTypes ```ts -type ProtectKnownDeviceTypes: +type ProtectKnownDeviceTypes = | ProtectCameraConfig | ProtectChimeConfig | ProtectLightConfig diff --git a/docs/ProtectApiEvents.md b/docs/ProtectApiEvents.md index a617a50..54c891b 100644 --- a/docs/ProtectApiEvents.md +++ b/docs/ProtectApiEvents.md @@ -74,7 +74,7 @@ UniFi Protect event utility class that provides functions for decoding realtime ##### decodePacket() ```ts -static decodePacket(log, packet): null | ProtectEventPacket +static decodePacket(log, packet): Nullable ``` Decode a UniFi Protect event packet. @@ -88,7 +88,7 @@ Decode a UniFi Protect event packet. ###### Returns -`null` \| [`ProtectEventPacket`](ProtectApiEvents.md#protecteventpacket) +`Nullable`\<[`ProtectEventPacket`](ProtectApiEvents.md#protecteventpacket)\> ###### Remarks @@ -102,7 +102,7 @@ successfully logged into the Protect controller, events are generated automatica ### ProtectEventHeader ```ts -type ProtectEventHeader: { +type ProtectEventHeader = { [key: string]: string | number | boolean | object; action: string; id: string; modelKey: string; @@ -145,7 +145,7 @@ A UniFi Protect event packet represents a realtime event update from a UniFi Pro ### ProtectEventPacket ```ts -type ProtectEventPacket: { +type ProtectEventPacket = { header: ProtectEventHeader; payload: unknown; }; diff --git a/docs/ProtectLivestream.md b/docs/ProtectLivestream.md index cadc032..9979a0b 100644 --- a/docs/ProtectLivestream.md +++ b/docs/ProtectLivestream.md @@ -118,14 +118,14 @@ Returns a string containing the codec information,if it exists, or `null` otherw ###### Get Signature ```ts -get initSegment(): null | Buffer +get initSegment(): Nullable> ``` The initialization segment that must be at the start of every fMP4 stream. ###### Returns -`null` \| `Buffer`\<`ArrayBufferLike`\> +`Nullable`\<`Buffer`\<`ArrayBufferLike`\>\> Returns the initialization segment if it exists, or `null` otherwise. diff --git a/docs/ProtectTypes.md b/docs/ProtectTypes.md index 9989741..13dde7f 100644 --- a/docs/ProtectTypes.md +++ b/docs/ProtectTypes.md @@ -1045,7 +1045,7 @@ A semi-complete description of the UniFi Protect viewer JSON. ### ProtectCameraChannelConfig ```ts -type ProtectCameraChannelConfig: ProtectCameraChannelConfigInterface; +type ProtectCameraChannelConfig = ProtectCameraChannelConfigInterface; ``` #### See @@ -1057,7 +1057,7 @@ type ProtectCameraChannelConfig: ProtectCameraChannelConfigInterface; ### ProtectCameraConfig ```ts -type ProtectCameraConfig: ProtectCameraConfigInterface; +type ProtectCameraConfig = ProtectCameraConfigInterface; ``` #### See @@ -1069,7 +1069,7 @@ type ProtectCameraConfig: ProtectCameraConfigInterface; ### ProtectCameraConfigPayload ```ts -type ProtectCameraConfigPayload: DeepPartial; +type ProtectCameraConfigPayload = DeepPartial; ``` #### See @@ -1081,7 +1081,7 @@ type ProtectCameraConfigPayload: DeepPartial; ### ProtectCameraLcdMessageConfig ```ts -type ProtectCameraLcdMessageConfig: ProtectCameraLcdMessageConfigInterface; +type ProtectCameraLcdMessageConfig = ProtectCameraLcdMessageConfigInterface; ``` #### See @@ -1093,7 +1093,7 @@ type ProtectCameraLcdMessageConfig: ProtectCameraLcdMessageConfigInterface; ### ProtectCameraLcdMessagePayload ```ts -type ProtectCameraLcdMessagePayload: DeepPartial; +type ProtectCameraLcdMessagePayload = DeepPartial; ``` #### See @@ -1105,7 +1105,7 @@ type ProtectCameraLcdMessagePayload: DeepPartial; +type ProtectChimeConfigPayload = DeepPartial; ``` #### See @@ -1129,7 +1129,7 @@ type ProtectChimeConfigPayload: DeepPartial; ### ProtectEventAdd ```ts -type ProtectEventAdd: ProtectEventAddInterface; +type ProtectEventAdd = ProtectEventAddInterface; ``` #### See @@ -1141,7 +1141,7 @@ type ProtectEventAdd: ProtectEventAddInterface; ### ProtectEventMetadata ```ts -type ProtectEventMetadata: ProtectEventMetadataInterface; +type ProtectEventMetadata = ProtectEventMetadataInterface; ``` #### See @@ -1153,7 +1153,7 @@ type ProtectEventMetadata: ProtectEventMetadataInterface; ### ProtectLightConfig ```ts -type ProtectLightConfig: ProtectLightConfigInterface; +type ProtectLightConfig = ProtectLightConfigInterface; ``` #### See @@ -1165,7 +1165,7 @@ type ProtectLightConfig: ProtectLightConfigInterface; ### ProtectLightConfigPayload ```ts -type ProtectLightConfigPayload: DeepPartial; +type ProtectLightConfigPayload = DeepPartial; ``` #### See @@ -1177,7 +1177,7 @@ type ProtectLightConfigPayload: DeepPartial; ### ProtectNvrBootstrap ```ts -type ProtectNvrBootstrap: ProtectNvrBootstrapInterface; +type ProtectNvrBootstrap = ProtectNvrBootstrapInterface; ``` #### See @@ -1189,7 +1189,7 @@ type ProtectNvrBootstrap: ProtectNvrBootstrapInterface; ### ProtectNvrConfig ```ts -type ProtectNvrConfig: ProtectNvrConfigInterface; +type ProtectNvrConfig = ProtectNvrConfigInterface; ``` #### See @@ -1201,7 +1201,7 @@ type ProtectNvrConfig: ProtectNvrConfigInterface; ### ProtectNvrConfigPayload ```ts -type ProtectNvrConfigPayload: DeepPartial; +type ProtectNvrConfigPayload = DeepPartial; ``` #### See @@ -1213,7 +1213,7 @@ type ProtectNvrConfigPayload: DeepPartial; ### ProtectNvrLiveviewConfig ```ts -type ProtectNvrLiveviewConfig: ProtectNvrLiveviewConfigInterface; +type ProtectNvrLiveviewConfig = ProtectNvrLiveviewConfigInterface; ``` #### See @@ -1225,7 +1225,7 @@ type ProtectNvrLiveviewConfig: ProtectNvrLiveviewConfigInterface; ### ProtectNvrSystemEvent ```ts -type ProtectNvrSystemEvent: ProtectNvrSystemEventInterface; +type ProtectNvrSystemEvent = ProtectNvrSystemEventInterface; ``` #### See @@ -1237,7 +1237,7 @@ type ProtectNvrSystemEvent: ProtectNvrSystemEventInterface; ### ProtectNvrSystemEventController ```ts -type ProtectNvrSystemEventController: ProtectNvrSystemEventControllerInterface; +type ProtectNvrSystemEventController = ProtectNvrSystemEventControllerInterface; ``` #### See @@ -1249,7 +1249,7 @@ type ProtectNvrSystemEventController: ProtectNvrSystemEventControllerInterface; ### ProtectNvrSystemInfoConfig ```ts -type ProtectNvrSystemInfoConfig: ProtectNvrSystemInfoInterface; +type ProtectNvrSystemInfoConfig = ProtectNvrSystemInfoInterface; ``` #### See @@ -1261,7 +1261,7 @@ type ProtectNvrSystemInfoConfig: ProtectNvrSystemInfoInterface; ### ProtectNvrUserConfig ```ts -type ProtectNvrUserConfig: ProtectNvrUserConfigInterface; +type ProtectNvrUserConfig = ProtectNvrUserConfigInterface; ``` #### See @@ -1273,7 +1273,7 @@ type ProtectNvrUserConfig: ProtectNvrUserConfigInterface; ### ProtectRingtoneConfig ```ts -type ProtectRingtoneConfig: ProtectRingtoneConfigInterface; +type ProtectRingtoneConfig = ProtectRingtoneConfigInterface; ``` #### See @@ -1285,7 +1285,7 @@ type ProtectRingtoneConfig: ProtectRingtoneConfigInterface; ### ProtectSensorConfig ```ts -type ProtectSensorConfig: ProtectSensorConfigInterface; +type ProtectSensorConfig = ProtectSensorConfigInterface; ``` #### See @@ -1297,7 +1297,7 @@ type ProtectSensorConfig: ProtectSensorConfigInterface; ### ProtectSensorConfigPayload ```ts -type ProtectSensorConfigPayload: DeepPartial; +type ProtectSensorConfigPayload = DeepPartial; ``` #### See @@ -1309,7 +1309,7 @@ type ProtectSensorConfigPayload: DeepPartial; ### ProtectViewerConfig ```ts -type ProtectViewerConfig: ProtectViewerConfigInterface; +type ProtectViewerConfig = ProtectViewerConfigInterface; ``` #### See @@ -1321,7 +1321,7 @@ type ProtectViewerConfig: ProtectViewerConfigInterface; ### ProtectViewerConfigPayload ```ts -type ProtectViewerConfigPayload: DeepPartial; +type ProtectViewerConfigPayload = DeepPartial; ``` #### See diff --git a/package-lock.json b/package-lock.json index 31d81c1..10d543d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "unifi-protect", - "version": "4.17.0", + "version": "4.18.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "unifi-protect", - "version": "4.17.0", + "version": "4.18.0", "license": "ISC", "dependencies": { "@adobe/fetch": "4.1.11", @@ -16,20 +16,20 @@ "ufp": "dist/util/ufp.js" }, "devDependencies": { - "@stylistic/eslint-plugin": "2.11.0", - "@types/node": "22.10.1", + "@stylistic/eslint-plugin": "2.12.1", + "@types/node": "22.10.2", "@types/ws": "8.5.13", - "eslint": "9.16.0", + "eslint": "9.17.0", "homebridge": "1.8.4", - "homebridge-plugin-utils": "^1.11.3", + "homebridge-plugin-utils": "^1.12.0", "shx": "0.3.4", - "typedoc": "0.27.4", - "typedoc-plugin-markdown": "4.3.2", + "typedoc": "0.27.5", + "typedoc-plugin-markdown": "4.3.3", "typescript": "5.7.2", - "typescript-eslint": "8.17.0" + "typescript-eslint": "8.18.1" }, "engines": { - "node": ">=18" + "node": ">=20" }, "optionalDependencies": { "bufferutil": "4.0.8" @@ -222,9 +222,9 @@ } }, "node_modules/@eslint/js": { - "version": "9.16.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.16.0.tgz", - "integrity": "sha512-tw2HxzQkrbeuvyj1tG2Yqq+0H9wGoI2IMk4EOsQeX+vmd75FtJAzf+gTA69WF+baUKRYQ3x2kbLE08js5OsTVg==", + "version": "9.17.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.17.0.tgz", + "integrity": "sha512-Sxc4hqcs1kTu0iID3kcZDW3JHq2a77HO9P8CP6YEA/FpH3Ll8UXE2r/86Rz9YJLKme39S9vU5OWNjC6Xl0Cr3w==", "dev": true, "license": "MIT", "engines": { @@ -461,9 +461,9 @@ "license": "MIT" }, "node_modules/@stylistic/eslint-plugin": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin/-/eslint-plugin-2.11.0.tgz", - "integrity": "sha512-PNRHbydNG5EH8NK4c+izdJlxajIR6GxcUhzsYNRsn6Myep4dsZt0qFCz3rCPnkvgO5FYibDcMqgNHUT+zvjYZw==", + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin/-/eslint-plugin-2.12.1.tgz", + "integrity": "sha512-fubZKIHSPuo07FgRTn6S4Nl0uXPRPYVNpyZzIDGfp7Fny6JjNus6kReLD7NI380JXi4HtUTSOZ34LBuNPO1XLQ==", "dev": true, "license": "MIT", "dependencies": { @@ -518,9 +518,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "22.10.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.1.tgz", - "integrity": "sha512-qKgsUwfHZV2WCWLAnVP1JqnpE6Im6h3Y0+fYgMTasNQ7V++CBX5OT1as0g0f+OyubbFqhf6XVNIsmN4IIhEgGQ==", + "version": "22.10.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.2.tgz", + "integrity": "sha512-Xxr6BBRCAOQixvonOye19wnzyDiUtTeqldOOmj3CkeblonbccA12PFwlufvRdrpjXxqnmUaeiU5EOA+7s5diUQ==", "dev": true, "license": "MIT", "dependencies": { @@ -563,17 +563,17 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.17.0.tgz", - "integrity": "sha512-HU1KAdW3Tt8zQkdvNoIijfWDMvdSweFYm4hWh+KwhPstv+sCmWb89hCIP8msFm9N1R/ooh9honpSuvqKWlYy3w==", + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.18.1.tgz", + "integrity": "sha512-Ncvsq5CT3Gvh+uJG0Lwlho6suwDfUXH0HztslDf5I+F2wAFAZMRwYLEorumpKLzmO2suAXZ/td1tBg4NZIi9CQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.17.0", - "@typescript-eslint/type-utils": "8.17.0", - "@typescript-eslint/utils": "8.17.0", - "@typescript-eslint/visitor-keys": "8.17.0", + "@typescript-eslint/scope-manager": "8.18.1", + "@typescript-eslint/type-utils": "8.18.1", + "@typescript-eslint/utils": "8.18.1", + "@typescript-eslint/visitor-keys": "8.18.1", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", @@ -588,25 +588,21 @@ }, "peerDependencies": { "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", - "eslint": "^8.57.0 || ^9.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" } }, "node_modules/@typescript-eslint/parser": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.17.0.tgz", - "integrity": "sha512-Drp39TXuUlD49F7ilHHCG7TTg8IkA+hxCuULdmzWYICxGXvDXmDmWEjJYZQYgf6l/TFfYNE167m7isnc3xlIEg==", + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.18.1.tgz", + "integrity": "sha512-rBnTWHCdbYM2lh7hjyXqxk70wvon3p2FyaniZuey5TrcGBpfhVp0OxOa6gxr9Q9YhZFKyfbEnxc24ZnVbbUkCA==", "dev": true, - "license": "BSD-2-Clause", + "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.17.0", - "@typescript-eslint/types": "8.17.0", - "@typescript-eslint/typescript-estree": "8.17.0", - "@typescript-eslint/visitor-keys": "8.17.0", + "@typescript-eslint/scope-manager": "8.18.1", + "@typescript-eslint/types": "8.18.1", + "@typescript-eslint/typescript-estree": "8.18.1", + "@typescript-eslint/visitor-keys": "8.18.1", "debug": "^4.3.4" }, "engines": { @@ -617,23 +613,19 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.17.0.tgz", - "integrity": "sha512-/ewp4XjvnxaREtqsZjF4Mfn078RD/9GmiEAtTeLQ7yFdKnqwTOgRMSvFz4et9U5RiJQ15WTGXPLj89zGusvxBg==", + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.18.1.tgz", + "integrity": "sha512-HxfHo2b090M5s2+/9Z3gkBhI6xBH8OJCFjH9MhQ+nnoZqxU3wNxkLT+VWXWSFWc3UF3Z+CfPAyqdCTdoXtDPCQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.17.0", - "@typescript-eslint/visitor-keys": "8.17.0" + "@typescript-eslint/types": "8.18.1", + "@typescript-eslint/visitor-keys": "8.18.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -644,14 +636,14 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.17.0.tgz", - "integrity": "sha512-q38llWJYPd63rRnJ6wY/ZQqIzPrBCkPdpIsaCfkR3Q4t3p6sb422zougfad4TFW9+ElIFLVDzWGiGAfbb/v2qw==", + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.18.1.tgz", + "integrity": "sha512-jAhTdK/Qx2NJPNOTxXpMwlOiSymtR2j283TtPqXkKBdH8OAMmhiUfP0kJjc/qSE51Xrq02Gj9NY7MwK+UxVwHQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "8.17.0", - "@typescript-eslint/utils": "8.17.0", + "@typescript-eslint/typescript-estree": "8.18.1", + "@typescript-eslint/utils": "8.18.1", "debug": "^4.3.4", "ts-api-utils": "^1.3.0" }, @@ -663,18 +655,14 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" } }, "node_modules/@typescript-eslint/types": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.17.0.tgz", - "integrity": "sha512-gY2TVzeve3z6crqh2Ic7Cr+CAv6pfb0Egee7J5UAVWCpVvDI/F71wNfolIim4FE6hT15EbpZFVUj9j5i38jYXA==", + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.18.1.tgz", + "integrity": "sha512-7uoAUsCj66qdNQNpH2G8MyTFlgerum8ubf21s3TSM3XmKXuIn+H2Sifh/ES2nPOPiYSRJWAk0fDkW0APBWcpfw==", "dev": true, "license": "MIT", "engines": { @@ -686,14 +674,14 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.17.0.tgz", - "integrity": "sha512-JqkOopc1nRKZpX+opvKqnM3XUlM7LpFMD0lYxTqOTKQfCWAmxw45e3qlOCsEqEB2yuacujivudOFpCnqkBDNMw==", + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.18.1.tgz", + "integrity": "sha512-z8U21WI5txzl2XYOW7i9hJhxoKKNG1kcU4RzyNvKrdZDmbjkmLBo8bgeiOJmA06kizLI76/CCBAAGlTlEeUfyg==", "dev": true, - "license": "BSD-2-Clause", + "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.17.0", - "@typescript-eslint/visitor-keys": "8.17.0", + "@typescript-eslint/types": "8.18.1", + "@typescript-eslint/visitor-keys": "8.18.1", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -708,23 +696,21 @@ "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "peerDependencies": { + "typescript": ">=4.8.4 <5.8.0" } }, "node_modules/@typescript-eslint/utils": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.17.0.tgz", - "integrity": "sha512-bQC8BnEkxqG8HBGKwG9wXlZqg37RKSMY7v/X8VEWD8JG2JuTHuNK0VFvMPMUKQcbk6B+tf05k+4AShAEtCtJ/w==", + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.18.1.tgz", + "integrity": "sha512-8vikiIj2ebrC4WRdcAdDcmnu9Q/MXXwg+STf40BVfT8exDqBCUPdypvzcUPxEqRGKg9ALagZ0UWcYCtn+4W2iQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.17.0", - "@typescript-eslint/types": "8.17.0", - "@typescript-eslint/typescript-estree": "8.17.0" + "@typescript-eslint/scope-manager": "8.18.1", + "@typescript-eslint/types": "8.18.1", + "@typescript-eslint/typescript-estree": "8.18.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -734,22 +720,18 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.17.0.tgz", - "integrity": "sha512-1Hm7THLpO6ww5QU6H/Qp+AusUUl+z/CAm3cNZZ0jQvon9yicgO7Rwd+/WWRpMKLYV6p2UvdbR27c86rzCPpreg==", + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.18.1.tgz", + "integrity": "sha512-Vj0WLm5/ZsD013YeUKn+K0y8p1M0jPpxOkKdbD1wB0ns53a5piVY02zjf072TblEweAbcYiFiPoSMF3kp+VhhQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.17.0", + "@typescript-eslint/types": "8.18.1", "eslint-visitor-keys": "^4.2.0" }, "engines": { @@ -1322,9 +1304,9 @@ } }, "node_modules/eslint": { - "version": "9.16.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.16.0.tgz", - "integrity": "sha512-whp8mSQI4C8VXd+fLgSM0lh3UlmcFtVwUQjyKCFfsp+2ItAIYhlq/hqGahGqHE6cv9unM41VlqKk2VtKYR2TaA==", + "version": "9.17.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.17.0.tgz", + "integrity": "sha512-evtlNcpJg+cZLcnVKwsai8fExnqjGPicK7gnUtlNuzu+Fv9bI0aLpND5T44VLQtoMEnI57LoXO9XAkIXwohKrA==", "dev": true, "license": "MIT", "dependencies": { @@ -1333,7 +1315,7 @@ "@eslint/config-array": "^0.19.0", "@eslint/core": "^0.9.0", "@eslint/eslintrc": "^3.2.0", - "@eslint/js": "9.16.0", + "@eslint/js": "9.17.0", "@eslint/plugin-kit": "^0.2.3", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", @@ -1342,7 +1324,7 @@ "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", "chalk": "^4.0.0", - "cross-spawn": "^7.0.5", + "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.2.0", @@ -2034,9 +2016,9 @@ } }, "node_modules/homebridge-plugin-utils": { - "version": "1.11.3", - "resolved": "https://registry.npmjs.org/homebridge-plugin-utils/-/homebridge-plugin-utils-1.11.3.tgz", - "integrity": "sha512-aSVmY8vFHMhosQZRKG1EVx65tXqqLsDboiMbNg1qLLVjTl8i2nh7y/L4jw9ti8o2oV6OdPSaUF+LNsJla+Z35Q==", + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/homebridge-plugin-utils/-/homebridge-plugin-utils-1.12.0.tgz", + "integrity": "sha512-+zRpW72lGk8eHEINHTrGcFU/K8sFhDn1eYxqjjq2hNDWCjiR7yVaF2Zb/janA41p9zbCCbJEeqMY24PdzzeIYg==", "dev": true, "license": "ISC", "dependencies": { @@ -3532,9 +3514,9 @@ "license": "MIT" }, "node_modules/typedoc": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.27.4.tgz", - "integrity": "sha512-wXPQs1AYC2Crk+1XFpNuutLIkNWleokZf1UNf/X8w9KsMnirkvT+LzxTXDvfF6ug3TSLf3Xu5ZXRKGfoXPX7IA==", + "version": "0.27.5", + "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.27.5.tgz", + "integrity": "sha512-x+fhKJtTg4ozXwKayh/ek4wxZQI/+2hmZUdO2i2NGDBRUflDble70z+ewHod3d4gRpXSO6fnlnjbDTnJk7HlkQ==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -3555,9 +3537,9 @@ } }, "node_modules/typedoc-plugin-markdown": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/typedoc-plugin-markdown/-/typedoc-plugin-markdown-4.3.2.tgz", - "integrity": "sha512-hCF3V0axzbzGDYFW21XigWIJQBOJ2ZRVWWs7X+e62ew/pXnvz7iKF/zVdkBm3w8Mk4bmXWp/FT0IF4Zn9uBRww==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/typedoc-plugin-markdown/-/typedoc-plugin-markdown-4.3.3.tgz", + "integrity": "sha512-kESCcNRzOcFJATLML2FoCfaTF9c0ujmbZ+UXsJvmNlFLS3v8tDKfDifreJXvXWa9d8gUcetZqOqFcZ/7+Ba34Q==", "dev": true, "license": "MIT", "engines": { @@ -3582,15 +3564,15 @@ } }, "node_modules/typescript-eslint": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.17.0.tgz", - "integrity": "sha512-409VXvFd/f1br1DCbuKNFqQpXICoTB+V51afcwG1pn1a3Cp92MqAUges3YjwEdQ0cMUoCIodjVDAYzyD8h3SYA==", + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.18.1.tgz", + "integrity": "sha512-Mlaw6yxuaDEPQvb/2Qwu3/TfgeBHy9iTJ3mTwe7OvpPmF6KPQjVOfGyEJpPv6Ez2C34OODChhXrzYw/9phI0MQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.17.0", - "@typescript-eslint/parser": "8.17.0", - "@typescript-eslint/utils": "8.17.0" + "@typescript-eslint/eslint-plugin": "8.18.1", + "@typescript-eslint/parser": "8.18.1", + "@typescript-eslint/utils": "8.18.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3600,12 +3582,8 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" } }, "node_modules/uc.micro": { @@ -3960,9 +3938,9 @@ } }, "@eslint/js": { - "version": "9.16.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.16.0.tgz", - "integrity": "sha512-tw2HxzQkrbeuvyj1tG2Yqq+0H9wGoI2IMk4EOsQeX+vmd75FtJAzf+gTA69WF+baUKRYQ3x2kbLE08js5OsTVg==", + "version": "9.17.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.17.0.tgz", + "integrity": "sha512-Sxc4hqcs1kTu0iID3kcZDW3JHq2a77HO9P8CP6YEA/FpH3Ll8UXE2r/86Rz9YJLKme39S9vU5OWNjC6Xl0Cr3w==", "dev": true }, "@eslint/object-schema": { @@ -4125,9 +4103,9 @@ "dev": true }, "@stylistic/eslint-plugin": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin/-/eslint-plugin-2.11.0.tgz", - "integrity": "sha512-PNRHbydNG5EH8NK4c+izdJlxajIR6GxcUhzsYNRsn6Myep4dsZt0qFCz3rCPnkvgO5FYibDcMqgNHUT+zvjYZw==", + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin/-/eslint-plugin-2.12.1.tgz", + "integrity": "sha512-fubZKIHSPuo07FgRTn6S4Nl0uXPRPYVNpyZzIDGfp7Fny6JjNus6kReLD7NI380JXi4HtUTSOZ34LBuNPO1XLQ==", "dev": true, "requires": { "@typescript-eslint/utils": "^8.13.0", @@ -4167,9 +4145,9 @@ "dev": true }, "@types/node": { - "version": "22.10.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.1.tgz", - "integrity": "sha512-qKgsUwfHZV2WCWLAnVP1JqnpE6Im6h3Y0+fYgMTasNQ7V++CBX5OT1as0g0f+OyubbFqhf6XVNIsmN4IIhEgGQ==", + "version": "22.10.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.2.tgz", + "integrity": "sha512-Xxr6BBRCAOQixvonOye19wnzyDiUtTeqldOOmj3CkeblonbccA12PFwlufvRdrpjXxqnmUaeiU5EOA+7s5diUQ==", "dev": true, "requires": { "undici-types": "~6.20.0" @@ -4209,16 +4187,16 @@ } }, "@typescript-eslint/eslint-plugin": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.17.0.tgz", - "integrity": "sha512-HU1KAdW3Tt8zQkdvNoIijfWDMvdSweFYm4hWh+KwhPstv+sCmWb89hCIP8msFm9N1R/ooh9honpSuvqKWlYy3w==", + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.18.1.tgz", + "integrity": "sha512-Ncvsq5CT3Gvh+uJG0Lwlho6suwDfUXH0HztslDf5I+F2wAFAZMRwYLEorumpKLzmO2suAXZ/td1tBg4NZIi9CQ==", "dev": true, "requires": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.17.0", - "@typescript-eslint/type-utils": "8.17.0", - "@typescript-eslint/utils": "8.17.0", - "@typescript-eslint/visitor-keys": "8.17.0", + "@typescript-eslint/scope-manager": "8.18.1", + "@typescript-eslint/type-utils": "8.18.1", + "@typescript-eslint/utils": "8.18.1", + "@typescript-eslint/visitor-keys": "8.18.1", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", @@ -4226,54 +4204,54 @@ } }, "@typescript-eslint/parser": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.17.0.tgz", - "integrity": "sha512-Drp39TXuUlD49F7ilHHCG7TTg8IkA+hxCuULdmzWYICxGXvDXmDmWEjJYZQYgf6l/TFfYNE167m7isnc3xlIEg==", + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.18.1.tgz", + "integrity": "sha512-rBnTWHCdbYM2lh7hjyXqxk70wvon3p2FyaniZuey5TrcGBpfhVp0OxOa6gxr9Q9YhZFKyfbEnxc24ZnVbbUkCA==", "dev": true, "requires": { - "@typescript-eslint/scope-manager": "8.17.0", - "@typescript-eslint/types": "8.17.0", - "@typescript-eslint/typescript-estree": "8.17.0", - "@typescript-eslint/visitor-keys": "8.17.0", + "@typescript-eslint/scope-manager": "8.18.1", + "@typescript-eslint/types": "8.18.1", + "@typescript-eslint/typescript-estree": "8.18.1", + "@typescript-eslint/visitor-keys": "8.18.1", "debug": "^4.3.4" } }, "@typescript-eslint/scope-manager": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.17.0.tgz", - "integrity": "sha512-/ewp4XjvnxaREtqsZjF4Mfn078RD/9GmiEAtTeLQ7yFdKnqwTOgRMSvFz4et9U5RiJQ15WTGXPLj89zGusvxBg==", + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.18.1.tgz", + "integrity": "sha512-HxfHo2b090M5s2+/9Z3gkBhI6xBH8OJCFjH9MhQ+nnoZqxU3wNxkLT+VWXWSFWc3UF3Z+CfPAyqdCTdoXtDPCQ==", "dev": true, "requires": { - "@typescript-eslint/types": "8.17.0", - "@typescript-eslint/visitor-keys": "8.17.0" + "@typescript-eslint/types": "8.18.1", + "@typescript-eslint/visitor-keys": "8.18.1" } }, "@typescript-eslint/type-utils": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.17.0.tgz", - "integrity": "sha512-q38llWJYPd63rRnJ6wY/ZQqIzPrBCkPdpIsaCfkR3Q4t3p6sb422zougfad4TFW9+ElIFLVDzWGiGAfbb/v2qw==", + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.18.1.tgz", + "integrity": "sha512-jAhTdK/Qx2NJPNOTxXpMwlOiSymtR2j283TtPqXkKBdH8OAMmhiUfP0kJjc/qSE51Xrq02Gj9NY7MwK+UxVwHQ==", "dev": true, "requires": { - "@typescript-eslint/typescript-estree": "8.17.0", - "@typescript-eslint/utils": "8.17.0", + "@typescript-eslint/typescript-estree": "8.18.1", + "@typescript-eslint/utils": "8.18.1", "debug": "^4.3.4", "ts-api-utils": "^1.3.0" } }, "@typescript-eslint/types": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.17.0.tgz", - "integrity": "sha512-gY2TVzeve3z6crqh2Ic7Cr+CAv6pfb0Egee7J5UAVWCpVvDI/F71wNfolIim4FE6hT15EbpZFVUj9j5i38jYXA==", + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.18.1.tgz", + "integrity": "sha512-7uoAUsCj66qdNQNpH2G8MyTFlgerum8ubf21s3TSM3XmKXuIn+H2Sifh/ES2nPOPiYSRJWAk0fDkW0APBWcpfw==", "dev": true }, "@typescript-eslint/typescript-estree": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.17.0.tgz", - "integrity": "sha512-JqkOopc1nRKZpX+opvKqnM3XUlM7LpFMD0lYxTqOTKQfCWAmxw45e3qlOCsEqEB2yuacujivudOFpCnqkBDNMw==", + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.18.1.tgz", + "integrity": "sha512-z8U21WI5txzl2XYOW7i9hJhxoKKNG1kcU4RzyNvKrdZDmbjkmLBo8bgeiOJmA06kizLI76/CCBAAGlTlEeUfyg==", "dev": true, "requires": { - "@typescript-eslint/types": "8.17.0", - "@typescript-eslint/visitor-keys": "8.17.0", + "@typescript-eslint/types": "8.18.1", + "@typescript-eslint/visitor-keys": "8.18.1", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -4283,24 +4261,24 @@ } }, "@typescript-eslint/utils": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.17.0.tgz", - "integrity": "sha512-bQC8BnEkxqG8HBGKwG9wXlZqg37RKSMY7v/X8VEWD8JG2JuTHuNK0VFvMPMUKQcbk6B+tf05k+4AShAEtCtJ/w==", + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.18.1.tgz", + "integrity": "sha512-8vikiIj2ebrC4WRdcAdDcmnu9Q/MXXwg+STf40BVfT8exDqBCUPdypvzcUPxEqRGKg9ALagZ0UWcYCtn+4W2iQ==", "dev": true, "requires": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.17.0", - "@typescript-eslint/types": "8.17.0", - "@typescript-eslint/typescript-estree": "8.17.0" + "@typescript-eslint/scope-manager": "8.18.1", + "@typescript-eslint/types": "8.18.1", + "@typescript-eslint/typescript-estree": "8.18.1" } }, "@typescript-eslint/visitor-keys": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.17.0.tgz", - "integrity": "sha512-1Hm7THLpO6ww5QU6H/Qp+AusUUl+z/CAm3cNZZ0jQvon9yicgO7Rwd+/WWRpMKLYV6p2UvdbR27c86rzCPpreg==", + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.18.1.tgz", + "integrity": "sha512-Vj0WLm5/ZsD013YeUKn+K0y8p1M0jPpxOkKdbD1wB0ns53a5piVY02zjf072TblEweAbcYiFiPoSMF3kp+VhhQ==", "dev": true, "requires": { - "@typescript-eslint/types": "8.17.0", + "@typescript-eslint/types": "8.18.1", "eslint-visitor-keys": "^4.2.0" }, "dependencies": { @@ -4685,9 +4663,9 @@ "dev": true }, "eslint": { - "version": "9.16.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.16.0.tgz", - "integrity": "sha512-whp8mSQI4C8VXd+fLgSM0lh3UlmcFtVwUQjyKCFfsp+2ItAIYhlq/hqGahGqHE6cv9unM41VlqKk2VtKYR2TaA==", + "version": "9.17.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.17.0.tgz", + "integrity": "sha512-evtlNcpJg+cZLcnVKwsai8fExnqjGPicK7gnUtlNuzu+Fv9bI0aLpND5T44VLQtoMEnI57LoXO9XAkIXwohKrA==", "dev": true, "requires": { "@eslint-community/eslint-utils": "^4.2.0", @@ -4695,7 +4673,7 @@ "@eslint/config-array": "^0.19.0", "@eslint/core": "^0.9.0", "@eslint/eslintrc": "^3.2.0", - "@eslint/js": "9.16.0", + "@eslint/js": "9.17.0", "@eslint/plugin-kit": "^0.2.3", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", @@ -4704,7 +4682,7 @@ "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", "chalk": "^4.0.0", - "cross-spawn": "^7.0.5", + "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.2.0", @@ -5187,9 +5165,9 @@ } }, "homebridge-plugin-utils": { - "version": "1.11.3", - "resolved": "https://registry.npmjs.org/homebridge-plugin-utils/-/homebridge-plugin-utils-1.11.3.tgz", - "integrity": "sha512-aSVmY8vFHMhosQZRKG1EVx65tXqqLsDboiMbNg1qLLVjTl8i2nh7y/L4jw9ti8o2oV6OdPSaUF+LNsJla+Z35Q==", + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/homebridge-plugin-utils/-/homebridge-plugin-utils-1.12.0.tgz", + "integrity": "sha512-+zRpW72lGk8eHEINHTrGcFU/K8sFhDn1eYxqjjq2hNDWCjiR7yVaF2Zb/janA41p9zbCCbJEeqMY24PdzzeIYg==", "dev": true, "requires": { "mqtt": "5.10.3" @@ -6180,9 +6158,9 @@ "dev": true }, "typedoc": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.27.4.tgz", - "integrity": "sha512-wXPQs1AYC2Crk+1XFpNuutLIkNWleokZf1UNf/X8w9KsMnirkvT+LzxTXDvfF6ug3TSLf3Xu5ZXRKGfoXPX7IA==", + "version": "0.27.5", + "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.27.5.tgz", + "integrity": "sha512-x+fhKJtTg4ozXwKayh/ek4wxZQI/+2hmZUdO2i2NGDBRUflDble70z+ewHod3d4gRpXSO6fnlnjbDTnJk7HlkQ==", "dev": true, "requires": { "@gerrit0/mini-shiki": "^1.24.0", @@ -6193,9 +6171,9 @@ } }, "typedoc-plugin-markdown": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/typedoc-plugin-markdown/-/typedoc-plugin-markdown-4.3.2.tgz", - "integrity": "sha512-hCF3V0axzbzGDYFW21XigWIJQBOJ2ZRVWWs7X+e62ew/pXnvz7iKF/zVdkBm3w8Mk4bmXWp/FT0IF4Zn9uBRww==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/typedoc-plugin-markdown/-/typedoc-plugin-markdown-4.3.3.tgz", + "integrity": "sha512-kESCcNRzOcFJATLML2FoCfaTF9c0ujmbZ+UXsJvmNlFLS3v8tDKfDifreJXvXWa9d8gUcetZqOqFcZ/7+Ba34Q==", "dev": true, "requires": {} }, @@ -6206,14 +6184,14 @@ "dev": true }, "typescript-eslint": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.17.0.tgz", - "integrity": "sha512-409VXvFd/f1br1DCbuKNFqQpXICoTB+V51afcwG1pn1a3Cp92MqAUges3YjwEdQ0cMUoCIodjVDAYzyD8h3SYA==", + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.18.1.tgz", + "integrity": "sha512-Mlaw6yxuaDEPQvb/2Qwu3/TfgeBHy9iTJ3mTwe7OvpPmF6KPQjVOfGyEJpPv6Ez2C34OODChhXrzYw/9phI0MQ==", "dev": true, "requires": { - "@typescript-eslint/eslint-plugin": "8.17.0", - "@typescript-eslint/parser": "8.17.0", - "@typescript-eslint/utils": "8.17.0" + "@typescript-eslint/eslint-plugin": "8.18.1", + "@typescript-eslint/parser": "8.18.1", + "@typescript-eslint/utils": "8.18.1" } }, "uc.micro": { diff --git a/package.json b/package.json index 4f7e25f..d298cc5 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "unifi-protect", "type": "module", - "version": "4.17.0", + "version": "4.18.0", "displayName": "UniFi Protect API", "description": "A complete implementation of the UniFi Protect API.", "author": { @@ -48,7 +48,7 @@ "rtsp" ], "engines": { - "node": ">=18" + "node": ">=20" }, "bin": { "ufp": "dist/util/ufp.js" @@ -64,17 +64,17 @@ "test": "eslint src/**.ts" }, "devDependencies": { - "@stylistic/eslint-plugin": "2.11.0", - "@types/node": "22.10.1", + "@stylistic/eslint-plugin": "2.12.1", + "@types/node": "22.10.2", "@types/ws": "8.5.13", - "eslint": "9.16.0", + "eslint": "9.17.0", "homebridge": "1.8.4", - "homebridge-plugin-utils": "^1.11.3", + "homebridge-plugin-utils": "^1.12.0", "shx": "0.3.4", - "typedoc": "0.27.4", - "typedoc-plugin-markdown": "4.3.2", + "typedoc": "0.27.5", + "typedoc-plugin-markdown": "4.3.3", "typescript": "5.7.2", - "typescript-eslint": "8.17.0" + "typescript-eslint": "8.18.1" }, "dependencies": { "@adobe/fetch": "4.1.11", diff --git a/src/protect-api-events.ts b/src/protect-api-events.ts index 4a10084..f3c159d 100644 --- a/src/protect-api-events.ts +++ b/src/protect-api-events.ts @@ -63,6 +63,7 @@ * * @module ProtectApiEvents */ +import { Nullable } from "homebridge-plugin-utils"; import { ProtectLogging } from "./protect-logging.js"; import zlib from "node:zlib"; @@ -163,7 +164,7 @@ export class ProtectApiEvents { * successfully logged into the Protect controller, events are generated automatically and can be accessed by listening to `message` events emitted by * {@link ProtectApi}. */ - public static decodePacket(log: ProtectLogging, packet: Buffer): ProtectEventPacket | null { + public static decodePacket(log: ProtectLogging, packet: Buffer): Nullable { // What we need to do here is to split this packet into the header and payload, and decode them. @@ -201,7 +202,7 @@ export class ProtectApiEvents { } // Decode a frame, composed of a header and payload, received through the update events API. - private static decodeFrame(log: ProtectLogging, packet: Buffer, packetType: ProtectEventPacketType): Buffer | JSON | ProtectEventHeader | null | string { + private static decodeFrame(log: ProtectLogging, packet: Buffer, packetType: ProtectEventPacketType): Nullable { // Read the packet frame type. const frameType = packet.readUInt8(ProtectEventPacketHeader.TYPE) as ProtectEventPacketType; diff --git a/src/protect-api-livestream.ts b/src/protect-api-livestream.ts index b10cfce..0c880fe 100644 --- a/src/protect-api-livestream.ts +++ b/src/protect-api-livestream.ts @@ -43,6 +43,7 @@ */ /* eslint-enable @stylistic/max-len */ import events, { EventEmitter } from "node:events"; +import { Nullable } from "homebridge-plugin-utils"; import { PROTECT_API_TIMEOUT } from "./settings.js"; import { ProtectApi } from "./protect-api.js"; import { ProtectLogging } from "./protect-logging.js"; @@ -79,15 +80,15 @@ enum ProtectLiveFrame { */ export class ProtectLivestream extends EventEmitter { - private _initSegment: Buffer | null; - private _codec: string | null; + private _initSegment: Nullable; + private _codec: Nullable; private api: ProtectApi; - private errorHandler: ((error: Error) => void) | null; - private heartbeat: NodeJS.Timeout | null; + private errorHandler: Nullable<(error: Error) => void>; + private heartbeat: Nullable; private lastMessage: number; private log: ProtectLogging; - private segmentHandler: ((packet: Buffer) => void) | null; - private ws: WebSocket | null; + private segmentHandler: Nullable<(packet: Buffer) => void>; + private ws: Nullable; // Create a new instance. constructor(api: ProtectApi, log: ProtectLogging) { @@ -231,7 +232,7 @@ export class ProtectLivestream extends EventEmitter { } // Setup our heartbeat timer. - let heartbeat: NodeJS.Timeout | null = null; + let heartbeat: Nullable = null; // Start our heartbeat once we've opened the connection. this.ws?.once("open", () => { @@ -506,7 +507,7 @@ export class ProtectLivestream extends EventEmitter { * * @returns Returns the initialization segment if it exists, or `null` otherwise. */ - public get initSegment(): Buffer | null { + public get initSegment(): Nullable { return this._initSegment; } diff --git a/src/protect-api.ts b/src/protect-api.ts index 09bfbdf..842b855 100644 --- a/src/protect-api.ts +++ b/src/protect-api.ts @@ -13,25 +13,11 @@ */ import { ALPNProtocol, AbortError, FetchError, Headers, Request, RequestOptions, Response, context, timeoutSignal } from "@adobe/fetch"; import { PROTECT_API_ERROR_LIMIT, PROTECT_API_RETRY_INTERVAL, PROTECT_API_TIMEOUT } from "./settings.js"; -import { - ProtectCameraChannelConfigInterface, - ProtectCameraConfig, - ProtectCameraConfigInterface, - ProtectCameraConfigPayload, - ProtectChimeConfig, - ProtectChimeConfigPayload, - ProtectLightConfig, - ProtectLightConfigPayload, - ProtectNvrBootstrap, - ProtectNvrConfig, - ProtectNvrConfigPayload, - ProtectNvrUserConfig, - ProtectSensorConfig, - ProtectSensorConfigPayload, - ProtectViewerConfig, - ProtectViewerConfigPayload -} from "./protect-types.js"; +import { ProtectCameraChannelConfigInterface, ProtectCameraConfig, ProtectCameraConfigInterface, ProtectCameraConfigPayload, ProtectChimeConfig, + ProtectChimeConfigPayload, ProtectLightConfig, ProtectLightConfigPayload, ProtectNvrBootstrap, ProtectNvrConfig, ProtectNvrConfigPayload, ProtectNvrUserConfig, + ProtectSensorConfig, ProtectSensorConfigPayload, ProtectViewerConfig, ProtectViewerConfigPayload } from "./protect-types.js"; import { EventEmitter } from "node:events"; +import { Nullable } from "homebridge-plugin-utils"; import { ProtectApiEvents } from "./protect-api-events.js"; import { ProtectLivestream } from "./protect-api-livestream.js"; import { ProtectLogging } from "./protect-logging.js"; @@ -65,8 +51,8 @@ export type ProtectKnownDevicePayloads = ProtectCameraConfigPayload | ProtectChi */ export class ProtectApi extends EventEmitter { - private _bootstrap: ProtectNvrBootstrap | null; - private _eventsWs: WebSocket | null; + private _bootstrap: Nullable; + private _eventsWs: Nullable; private apiErrorCount: number; private apiLastSuccess: number; @@ -269,7 +255,7 @@ export class ProtectApi extends EventEmitter { } // Now let's get our NVR configuration information. - let data: ProtectNvrBootstrap | null = null; + let data: Nullable = null; try { @@ -342,7 +328,7 @@ export class ProtectApi extends EventEmitter { return false; } - let messageHandler: ((event: Buffer) => void) | null; + let messageHandler: Nullable<(event: Buffer) => void>; // Cleanup after ourselves if our websocket closes for some resaon. ws.once("close", (): void => { @@ -521,7 +507,7 @@ export class ProtectApi extends EventEmitter { * * @category API Access */ - public async getSnapshot(device: ProtectCameraConfig, options: Partial<{ width: number, height: number, usePackageCamera: boolean }> = {}): Promise { + public async getSnapshot(device: ProtectCameraConfig, options: Partial<{ width: number, height: number, usePackageCamera: boolean }> = {}): Promise> { // No device object, or it's not a camera, or we're requesting a package camera snapshot on a camera without one - we're done. if(!device || (device.modelKey !== "camera") || (options.usePackageCamera && !device.featureFlags.hasPackageCamera)) { @@ -584,7 +570,7 @@ export class ProtectApi extends EventEmitter { * * @category API Access */ - public async updateDevice(device: DeviceType, payload: ProtectKnownDevicePayloads): Promise { + public async updateDevice(device: DeviceType, payload: ProtectKnownDevicePayloads): Promise> { // No device object, we're done. if(!device) { @@ -631,7 +617,7 @@ export class ProtectApi extends EventEmitter { } // Update camera channels on a supported Protect device. - private async updateCameraChannels(device: ProtectCameraConfigInterface): Promise { + private async updateCameraChannels(device: ProtectCameraConfigInterface): Promise> { // Make sure we have the permissions to modify the camera JSON. if(!(await this.canModifyCamera(device))) { @@ -681,7 +667,7 @@ export class ProtectApi extends EventEmitter { * * @category Utilities */ - public async enableRtsp(device: ProtectCameraConfigInterface): Promise { + public async enableRtsp(device: ProtectCameraConfigInterface): Promise> { // Make sure we have the permissions to modify the camera JSON. if(!(await this.canModifyCamera(device))) { @@ -841,13 +827,13 @@ export class ProtectApi extends EventEmitter { * @category API Access */ // Return a WebSocket URL endpoint from the Protect controller for Protect API services (e.g. livestream, talkback). - public async getWsEndpoint(endpoint: "livestream" | "talkback", params?: URLSearchParams): Promise { + public async getWsEndpoint(endpoint: "livestream" | "talkback", params?: URLSearchParams): Promise> { return this._getWsEndpoint(endpoint, params); } // Internal interface to returning a WebSocket URL endpoint from the Protect controller for Protect API services (e.g. livestream, talkback). - private async _getWsEndpoint(endpoint: "livestream" | "talkback", params?: URLSearchParams, retry = true): Promise { + private async _getWsEndpoint(endpoint: "livestream" | "talkback", params?: URLSearchParams, retry = true): Promise> { if(!endpoint) { @@ -935,13 +921,14 @@ export class ProtectApi extends EventEmitter { * @category API Access */ // Communicate HTTP requests with a Protect controller. - public async retrieve(url: string, options: RequestOptions = { method: "GET" }, logErrors = true): Promise { + public async retrieve(url: string, options: RequestOptions = { method: "GET" }, logErrors = true): Promise> { return this._retrieve(url, options, logErrors); } // Internal interface to communicating HTTP requests with a Protect controller, with error handling. - private async _retrieve(url: string, options: RequestOptions = { method: "GET" }, logErrors = true, decodeResponse = true, isRetry = false): Promise { + private async _retrieve(url: string, options: RequestOptions = { method: "GET" }, logErrors = true, decodeResponse = true, isRetry = false): + Promise> { // Log errors if that's what the caller requested. const logError = (message: string, ...parameters: unknown[]): void => { @@ -1017,8 +1004,11 @@ export class ProtectApi extends EventEmitter { return response; } - // Preemptively increase the error count. - this.apiErrorCount++; + // Preemptively increase the error count, but only if we're not retrying. + if(!isRetry) { + + this.apiErrorCount++; + } // Bad username and password. if(response.status === 401) { @@ -1070,23 +1060,33 @@ export class ProtectApi extends EventEmitter { if(error instanceof FetchError) { + // We'll delay our retry by 50 to 150ms, with a little randomness thrown in for good measure, to give the controller a chance to recover from it's quirkiness. + const retry = async (): Promise> => new Promise(res => setTimeout(() => res(this._retrieve(url, options, logErrors, decodeResponse, true)), + Math.floor(Math.random() * (150 - 50 + 1)) + 50)); + switch(error.code) { case "ECONNREFUSED": case "EHOSTDOWN": - case "ERR_HTTP2_STREAM_CANCEL": - case "ERR_HTTP2_STREAM_ERROR": + + // Retry on connection refused, but no more than once. + if(!isRetry) { + + return retry(); + } logError("Connection refused."); break; case "ECONNRESET": + case "ERR_HTTP2_STREAM_CANCEL": + case "ERR_HTTP2_STREAM_ERROR": // Retry on connection reset, but no more than once. if(!isRetry) { - return this._retrieve(url, options, logErrors, decodeResponse, true); + return retry(); } logError("Network connection to Protect controller has been reset."); @@ -1253,7 +1253,7 @@ export class ProtectApi extends EventEmitter { * * @category API Access */ - public get bootstrap(): ProtectNvrBootstrap | null { + public get bootstrap(): Nullable { return this._bootstrap; }