homebridge-unifi-protect 7.8.2 → 7.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/devices/protect-camera-package.d.ts +4 -1
- package/dist/devices/protect-camera-package.js +75 -5
- package/dist/devices/protect-camera-package.js.map +1 -1
- package/dist/devices/protect-camera.d.ts +1 -1
- package/dist/devices/protect-camera.js +10 -12
- package/dist/devices/protect-camera.js.map +1 -1
- package/dist/devices/protect-device.d.ts +3 -3
- package/dist/devices/protect-doorbell.d.ts +4 -1
- package/dist/devices/protect-doorbell.js +59 -2
- package/dist/devices/protect-doorbell.js.map +1 -1
- package/dist/devices/protect-sensor.js +0 -7
- package/dist/devices/protect-sensor.js.map +1 -1
- package/dist/protect-nvr.d.ts +2 -2
- package/dist/protect-nvr.js +3 -3
- package/dist/protect-nvr.js.map +1 -1
- package/dist/protect-options.js +13 -11
- package/dist/protect-options.js.map +1 -1
- package/dist/protect-stream.js +20 -3
- package/dist/protect-stream.js.map +1 -1
- package/dist/protect-timeshift.js +1 -1
- package/dist/protect-timeshift.js.map +1 -1
- package/dist/protect-types.d.ts +2 -0
- package/dist/protect-types.js +2 -0
- package/dist/protect-types.js.map +1 -1
- package/dist/settings.d.ts +1 -0
- package/dist/settings.js +2 -0
- package/dist/settings.js.map +1 -1
- package/homebridge-ui/public/lib/featureoptions.js +45 -28
- package/homebridge-ui/public/lib/featureoptions.js.map +1 -1
- package/homebridge-ui/public/lib/webUi-featureoptions.mjs +77 -78
- package/homebridge-ui/public/ui.mjs +3 -3
- package/package.json +10 -11
|
@@ -312,7 +312,7 @@ export class webUiFeatureOptions {
|
|
|
312
312
|
}
|
|
313
313
|
|
|
314
314
|
// The first entry returned by getDevices() must always be the controller.
|
|
315
|
-
this.#controller = this.#devices[0]?.
|
|
315
|
+
this.#controller = this.#devices[0]?.serialNumber ?? null;
|
|
316
316
|
}
|
|
317
317
|
|
|
318
318
|
// Make the UI visible.
|
|
@@ -329,7 +329,7 @@ export class webUiFeatureOptions {
|
|
|
329
329
|
this.#sidebar.showDevices(controller, this.#devices);
|
|
330
330
|
|
|
331
331
|
// Display the feature options to the user.
|
|
332
|
-
this.showDeviceOptions(controller ? this.#devices[0].
|
|
332
|
+
this.showDeviceOptions(controller ? this.#devices[0].serialNumber : "Global Options");
|
|
333
333
|
|
|
334
334
|
// All done. Let the user interact with us.
|
|
335
335
|
homebridge.hideSpinner();
|
|
@@ -345,7 +345,7 @@ export class webUiFeatureOptions {
|
|
|
345
345
|
webUiEntry.parentElement.classList.add("bg-info", "text-white") : webUiEntry.parentElement.classList.remove("bg-info", "text-white"));
|
|
346
346
|
|
|
347
347
|
// Populate the device information info pane.
|
|
348
|
-
const currentDevice = this.#devices.find(device => device.
|
|
348
|
+
const currentDevice = this.#devices.find(device => device.serialNumber === deviceId);
|
|
349
349
|
|
|
350
350
|
// Populate the details view. If there's no device specified, the context is considered global and we hide the device details view.
|
|
351
351
|
if(!currentDevice) {
|
|
@@ -428,13 +428,13 @@ export class webUiFeatureOptions {
|
|
|
428
428
|
checkbox.readOnly = false;
|
|
429
429
|
checkbox.id = featureOption;
|
|
430
430
|
checkbox.name = featureOption;
|
|
431
|
-
checkbox.value = featureOption + (!currentDevice ? "" : ("." + currentDevice.
|
|
431
|
+
checkbox.value = featureOption + (!currentDevice ? "" : ("." + currentDevice.serialNumber));
|
|
432
432
|
|
|
433
433
|
let initialValue = undefined;
|
|
434
434
|
let initialScope;
|
|
435
435
|
|
|
436
436
|
// Determine our initial option scope to show the user what's been set.
|
|
437
|
-
switch(initialScope = this.#featureOptions.scope(featureOption, currentDevice?.
|
|
437
|
+
switch(initialScope = this.#featureOptions.scope(featureOption, currentDevice?.serialNumber, this.#controller)) {
|
|
438
438
|
|
|
439
439
|
case "global":
|
|
440
440
|
case "controller":
|
|
@@ -442,13 +442,11 @@ export class webUiFeatureOptions {
|
|
|
442
442
|
// If we're looking at the global scope, show the option value. Otherwise, we show that we're inheriting a value from the scope above.
|
|
443
443
|
if(!currentDevice) {
|
|
444
444
|
|
|
445
|
+
checkbox.checked = this.#featureOptions.test(featureOption);
|
|
446
|
+
|
|
445
447
|
if(this.#featureOptions.isValue(featureOption)) {
|
|
446
448
|
|
|
447
|
-
checkbox.checked = this.#featureOptions.exists(featureOption);
|
|
448
449
|
initialValue = this.#featureOptions.value(checkbox.id);
|
|
449
|
-
} else {
|
|
450
|
-
|
|
451
|
-
checkbox.checked = this.#featureOptions.test(featureOption);
|
|
452
450
|
}
|
|
453
451
|
|
|
454
452
|
if(checkbox.checked) {
|
|
@@ -472,13 +470,11 @@ export class webUiFeatureOptions {
|
|
|
472
470
|
case "none":
|
|
473
471
|
default:
|
|
474
472
|
|
|
475
|
-
|
|
473
|
+
checkbox.checked = this.#featureOptions.test(featureOption, currentDevice?.serialNumber);
|
|
476
474
|
|
|
477
|
-
|
|
478
|
-
initialValue = this.#featureOptions.value(checkbox.id, currentDevice?.serial);
|
|
479
|
-
} else {
|
|
475
|
+
if(this.#featureOptions.isValue(featureOption)) {
|
|
480
476
|
|
|
481
|
-
|
|
477
|
+
initialValue = this.#featureOptions.value(checkbox.id, currentDevice?.serialNumber);
|
|
482
478
|
}
|
|
483
479
|
|
|
484
480
|
break;
|
|
@@ -512,35 +508,10 @@ export class webUiFeatureOptions {
|
|
|
512
508
|
inputValue.type = "text";
|
|
513
509
|
inputValue.value = initialValue ?? option.defaultValue;
|
|
514
510
|
inputValue.size = 5;
|
|
515
|
-
inputValue.readOnly =
|
|
511
|
+
inputValue.readOnly = checkbox.readOnly;
|
|
516
512
|
|
|
517
513
|
// Add or remove the setting from our configuration when we've changed our state.
|
|
518
|
-
inputValue.addEventListener("change",
|
|
519
|
-
|
|
520
|
-
// Find the option in our list and delete it if it exists.
|
|
521
|
-
const optionRegex = new RegExp("^(?:Enable|Disable)\\." + checkbox.id + (!currentDevice ? "" : ("\\." + currentDevice.serial)) + "\\.[^\\.]+$", "gi");
|
|
522
|
-
const newOptions = this.#featureOptions.configuredOptions.filter(entry => !optionRegex.test(entry));
|
|
523
|
-
|
|
524
|
-
if(checkbox.checked) {
|
|
525
|
-
|
|
526
|
-
newOptions.push("Enable." + checkbox.value + "." + inputValue.value);
|
|
527
|
-
} else if(checkbox.indeterminate) {
|
|
528
|
-
|
|
529
|
-
// If we're in an indeterminate state, we need to traverse the tree to get the upstream value we're inheriting.
|
|
530
|
-
inputValue.value = (currentDevice?.serial !== this.#controller) ?
|
|
531
|
-
(this.#featureOptions.value(checkbox.id, this.#controller) ?? this.#featureOptions.value(checkbox.id)) :
|
|
532
|
-
(this.#featureOptions.value(checkbox.id) ?? option.defaultValue);
|
|
533
|
-
} else {
|
|
534
|
-
|
|
535
|
-
inputValue.value = option.defaultValue;
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
// Update our configuration in Homebridge.
|
|
539
|
-
this.currentConfig[0].options = newOptions;
|
|
540
|
-
this.#featureOptions.configuredOptions = newOptions;
|
|
541
|
-
await homebridge.updatePluginConfig(this.currentConfig);
|
|
542
|
-
});
|
|
543
|
-
|
|
514
|
+
inputValue.addEventListener("change", () => checkbox.dispatchEvent(new Event("change")));
|
|
544
515
|
tdInput.appendChild(inputValue);
|
|
545
516
|
trX.appendChild(tdInput);
|
|
546
517
|
}
|
|
@@ -553,7 +524,7 @@ export class webUiFeatureOptions {
|
|
|
553
524
|
labelDescription.classList.add("user-select-none", "my-0", "py-0");
|
|
554
525
|
|
|
555
526
|
// Highlight options for the user that are different than our defaults.
|
|
556
|
-
const scopeColor = this.#featureOptions.color(featureOption, currentDevice?.
|
|
527
|
+
const scopeColor = this.#featureOptions.color(featureOption, currentDevice?.serialNumber, this.#controller);
|
|
557
528
|
|
|
558
529
|
if(scopeColor) {
|
|
559
530
|
|
|
@@ -564,19 +535,21 @@ export class webUiFeatureOptions {
|
|
|
564
535
|
checkbox.addEventListener("change", async () => {
|
|
565
536
|
|
|
566
537
|
// Find the option in our list and delete it if it exists.
|
|
567
|
-
const optionRegex = new RegExp("^(?:Enable|Disable)\\." + checkbox.id + (!currentDevice ? "" : ("\\." + currentDevice.
|
|
538
|
+
const optionRegex = new RegExp("^(?:Enable|Disable)\\." + checkbox.id + (!currentDevice ? "" : ("\\." + currentDevice.serialNumber)) +
|
|
539
|
+
"(?:\\.([^\\.]*))?$", "gi");
|
|
540
|
+
|
|
568
541
|
const newOptions = this.#featureOptions.configuredOptions.filter(entry => !optionRegex.test(entry));
|
|
569
542
|
|
|
570
543
|
// Figure out if we've got the option set upstream.
|
|
571
544
|
let upstreamOption = false;
|
|
572
545
|
|
|
573
546
|
// We explicitly want to check for the scope of the feature option above where we are now, so we can appropriately determine what we should show.
|
|
574
|
-
switch(this.#featureOptions.scope(checkbox.id, (currentDevice && (currentDevice.
|
|
547
|
+
switch(this.#featureOptions.scope(checkbox.id, (currentDevice && (currentDevice.serialNumber !== this.#controller)) ? this.#controller : undefined)) {
|
|
575
548
|
|
|
576
549
|
case "device":
|
|
577
550
|
case "controller":
|
|
578
551
|
|
|
579
|
-
if(currentDevice.
|
|
552
|
+
if(currentDevice.serialNumber !== this.#controller) {
|
|
580
553
|
|
|
581
554
|
upstreamOption = true;
|
|
582
555
|
}
|
|
@@ -597,15 +570,24 @@ export class webUiFeatureOptions {
|
|
|
597
570
|
break;
|
|
598
571
|
}
|
|
599
572
|
|
|
600
|
-
//
|
|
601
|
-
|
|
602
|
-
if(checkbox.readOnly && (!this.#featureOptions.isValue(featureOption) || (this.#featureOptions.isValue(featureOption) && inputValue && !upstreamOption))) {
|
|
573
|
+
// We're currently in an indetermindate state and transitioning to an unchecked state.
|
|
574
|
+
if(checkbox.readOnly) {
|
|
603
575
|
|
|
604
|
-
//
|
|
605
|
-
//
|
|
576
|
+
// The user wants to change the state to unchecked. We need this because a checkbox can be in both an unchecked and indeterminate simultaneously, so we use
|
|
577
|
+
// the readOnly property to let us know that we've just cycled from an indeterminate state.
|
|
606
578
|
checkbox.checked = checkbox.readOnly = false;
|
|
579
|
+
|
|
580
|
+
// If we have a value-centric feature option, we show the default value when we're in an indeterminate state.
|
|
581
|
+
if(this.#featureOptions.isValue(featureOption)) {
|
|
582
|
+
|
|
583
|
+
// If we're unchecked, clear out the value and make it read only. We show the system default for reference.
|
|
584
|
+
inputValue.value = option.defaultValue;
|
|
585
|
+
inputValue.readOnly = true;
|
|
586
|
+
}
|
|
607
587
|
} else if(!checkbox.checked) {
|
|
608
588
|
|
|
589
|
+
// We're currently in a checked state and transitioning to an unchecked or an indeterminate state.
|
|
590
|
+
|
|
609
591
|
// If we have an upstream option configured, we reveal a third state to show inheritance of that option and allow the user to select it.
|
|
610
592
|
if(upstreamOption) {
|
|
611
593
|
|
|
@@ -614,51 +596,64 @@ export class webUiFeatureOptions {
|
|
|
614
596
|
checkbox.readOnly = checkbox.indeterminate = true;
|
|
615
597
|
}
|
|
616
598
|
|
|
617
|
-
|
|
599
|
+
// If we're in an indeterminate state, we need to traverse the tree to get the upstream value we're inheriting.
|
|
600
|
+
if(this.#featureOptions.isValue(featureOption)) {
|
|
601
|
+
|
|
602
|
+
let newInputValue;
|
|
603
|
+
|
|
604
|
+
// If our scope is global, let's fallback on the default value.
|
|
605
|
+
// eslint-disable-next-line eqeqeq
|
|
606
|
+
if((currentDevice?.serialNumber == null) && (this.#controller == null)) {
|
|
618
607
|
|
|
608
|
+
newInputValue = option.defaultValue;
|
|
609
|
+
} else if(currentDevice?.serialNumber !== this.#controller) {
|
|
610
|
+
|
|
611
|
+
// We're at the device level - get the controller level value if it exists and fallback to the global value otherwise.
|
|
612
|
+
newInputValue = this.#featureOptions.value(checkbox.id, this.#controller) ?? this.#featureOptions.value(checkbox.id);
|
|
613
|
+
} else {
|
|
614
|
+
|
|
615
|
+
// We're at the controller level - get the global value.
|
|
616
|
+
newInputValue = this.#featureOptions.value(checkbox.id);
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
// Our fallback if there's no value defined within the scope hierarchy is the default value.
|
|
620
|
+
inputValue.value = newInputValue ?? option.defaultValue;
|
|
619
621
|
inputValue.readOnly = true;
|
|
620
622
|
}
|
|
621
623
|
} else if(checkbox.checked) {
|
|
622
624
|
|
|
623
|
-
// We'
|
|
625
|
+
// We're currently in an unchecked state and transitioning to a checked state.
|
|
624
626
|
checkbox.readOnly = checkbox.indeterminate = false;
|
|
625
627
|
|
|
626
|
-
if(this.#featureOptions.isValue(featureOption)
|
|
628
|
+
if(this.#featureOptions.isValue(featureOption)) {
|
|
627
629
|
|
|
628
630
|
inputValue.readOnly = false;
|
|
629
631
|
}
|
|
630
632
|
}
|
|
631
633
|
|
|
632
|
-
// The
|
|
633
|
-
|
|
634
|
+
// The feature option is different from the default - highlight it for the user, accounting for the scope hierarchy, and add it to our configuration. We
|
|
635
|
+
// provide a visual queue to the user, highlighting to indicate that a non-default option has been set.
|
|
636
|
+
if(!checkbox.indeterminate && ((checkbox.checked !== option.default) ||
|
|
637
|
+
(this.#featureOptions.isValue(featureOption) && (inputValue.value.toString() !== option.defaultValue.toString())) || upstreamOption)) {
|
|
634
638
|
|
|
635
639
|
labelDescription.classList.add("text-info");
|
|
636
|
-
newOptions.push((checkbox.checked ? "Enable." : "Disable.") + checkbox.value
|
|
640
|
+
newOptions.push((checkbox.checked ? "Enable." : "Disable.") + checkbox.value +
|
|
641
|
+
(this.#featureOptions.isValue(featureOption) && checkbox.checked ? ("." + inputValue.value) : ""));
|
|
637
642
|
} else {
|
|
638
643
|
|
|
639
644
|
// We've reset to the defaults, remove our highlighting.
|
|
640
645
|
labelDescription.classList.remove("text-info");
|
|
641
646
|
}
|
|
642
647
|
|
|
643
|
-
// Update our Homebridge
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
const changeEvent = new Event("change");
|
|
648
|
-
|
|
649
|
-
inputValue.dispatchEvent(changeEvent);
|
|
650
|
-
} else {
|
|
651
|
-
|
|
652
|
-
// Update our configuration in Homebridge.
|
|
653
|
-
this.currentConfig[0].options = newOptions;
|
|
654
|
-
this.#featureOptions.configuredOptions = newOptions;
|
|
655
|
-
await homebridge.updatePluginConfig(this.currentConfig);
|
|
656
|
-
}
|
|
648
|
+
// Update our configuration in Homebridge.
|
|
649
|
+
this.currentConfig[0].options = newOptions;
|
|
650
|
+
this.#featureOptions.configuredOptions = newOptions;
|
|
651
|
+
await homebridge.updatePluginConfig(this.currentConfig);
|
|
657
652
|
|
|
658
653
|
// If we've reset to defaults, make sure our color coding for scope is reflected.
|
|
659
654
|
if((checkbox.checked === option.default) || checkbox.indeterminate) {
|
|
660
655
|
|
|
661
|
-
const scopeColor = this.#featureOptions.color(featureOption, currentDevice?.
|
|
656
|
+
const scopeColor = this.#featureOptions.color(featureOption, currentDevice?.serialNumber, this.#controller);
|
|
662
657
|
|
|
663
658
|
if(scopeColor) {
|
|
664
659
|
|
|
@@ -669,7 +664,7 @@ export class webUiFeatureOptions {
|
|
|
669
664
|
// Adjust visibility of other feature options that depend on us.
|
|
670
665
|
if(this.#featureOptions.groups[checkbox.id]) {
|
|
671
666
|
|
|
672
|
-
const entryVisibility = this.#featureOptions.test(featureOption, currentDevice?.
|
|
667
|
+
const entryVisibility = this.#featureOptions.test(featureOption, currentDevice?.serialNumber) ? "" : "none";
|
|
673
668
|
|
|
674
669
|
// Lookup each feature option setting and set the visibility accordingly.
|
|
675
670
|
for(const entry of this.#featureOptions.groups[checkbox.id]) {
|
|
@@ -692,7 +687,7 @@ export class webUiFeatureOptions {
|
|
|
692
687
|
trX.appendChild(tdLabel);
|
|
693
688
|
|
|
694
689
|
// Adjust the visibility of the feature option, if it's logically grouped.
|
|
695
|
-
if((option.group !== undefined) && !this.#featureOptions.test(category.name + (option.group.length ? ("." + option.group) : ""), currentDevice?.
|
|
690
|
+
if((option.group !== undefined) && !this.#featureOptions.test(category.name + (option.group.length ? ("." + option.group) : ""), currentDevice?.serialNumber)) {
|
|
696
691
|
|
|
697
692
|
trX.style.display = "none";
|
|
698
693
|
} else {
|
|
@@ -738,7 +733,7 @@ export class webUiFeatureOptions {
|
|
|
738
733
|
|
|
739
734
|
// Display our device details.
|
|
740
735
|
deviceFirmware.innerHTML = device.firmwareVersion;
|
|
741
|
-
deviceSerial.innerHTML = device.
|
|
736
|
+
deviceSerial.innerHTML = device.serialNumber;
|
|
742
737
|
}
|
|
743
738
|
|
|
744
739
|
// Default method for enumerating the device list in the sidebar.
|
|
@@ -785,12 +780,12 @@ export class webUiFeatureOptions {
|
|
|
785
780
|
|
|
786
781
|
const label = document.createElement("label");
|
|
787
782
|
|
|
788
|
-
label.name = device.
|
|
783
|
+
label.name = device.serialNumber;
|
|
789
784
|
label.appendChild(document.createTextNode(device.name ?? "Unknown"));
|
|
790
785
|
label.style.cursor = "pointer";
|
|
791
786
|
label.classList.add("mx-2", "my-0", "p-0", "w-100");
|
|
792
787
|
|
|
793
|
-
label.addEventListener("click", () => this.showDeviceOptions(device.
|
|
788
|
+
label.addEventListener("click", () => this.showDeviceOptions(device.serialNumber));
|
|
794
789
|
|
|
795
790
|
// Add the device label to our cell.
|
|
796
791
|
tdDevice.appendChild(label);
|
|
@@ -814,10 +809,14 @@ export class webUiFeatureOptions {
|
|
|
814
809
|
// Filter out only the components we're interested in.
|
|
815
810
|
devices = devices.map(device => ({
|
|
816
811
|
|
|
817
|
-
|
|
812
|
+
firmwareRevision: (device.services.find(service => service.constructorName ===
|
|
818
813
|
"AccessoryInformation")?.characteristics.find(characteristic => characteristic.constructorName === "FirmwareRevision")?.value ?? ""),
|
|
814
|
+
manufacturer: (device.services.find(service => service.constructorName ===
|
|
815
|
+
"AccessoryInformation")?.characteristics.find(characteristic => characteristic.constructorName === "Manufacturer")?.value ?? ""),
|
|
816
|
+
model: (device.services.find(service => service.constructorName ===
|
|
817
|
+
"AccessoryInformation")?.characteristics.find(characteristic => characteristic.constructorName === "Model")?.value ?? ""),
|
|
819
818
|
name: device.displayName,
|
|
820
|
-
|
|
819
|
+
serialNumber: (device.services.find(service => service.constructorName ===
|
|
821
820
|
"AccessoryInformation")?.characteristics.find(characteristic => characteristic.constructorName === "SerialNumber")?.value ?? "")
|
|
822
821
|
}));
|
|
823
822
|
|
|
@@ -91,7 +91,7 @@ const getDevices = async (controller) => {
|
|
|
91
91
|
devices = devices.map(device => ({
|
|
92
92
|
|
|
93
93
|
...device,
|
|
94
|
-
|
|
94
|
+
serialNumber: device.mac
|
|
95
95
|
}));
|
|
96
96
|
|
|
97
97
|
return devices;
|
|
@@ -157,12 +157,12 @@ const showSidebarDevices = (controller, devices) => {
|
|
|
157
157
|
|
|
158
158
|
const label = document.createElement("label");
|
|
159
159
|
|
|
160
|
-
label.name = device.
|
|
160
|
+
label.name = device.serialNumber;
|
|
161
161
|
label.appendChild(document.createTextNode(device.name ?? device.marketName));
|
|
162
162
|
label.style.cursor = "pointer";
|
|
163
163
|
label.classList.add("mx-2", "my-0", "p-0", "w-100");
|
|
164
164
|
|
|
165
|
-
label.addEventListener("click", () => ui.featureOptions.showDeviceOptions(device.
|
|
165
|
+
label.addEventListener("click", () => ui.featureOptions.showDeviceOptions(device.serialNumber));
|
|
166
166
|
|
|
167
167
|
// Add the device label to our cell.
|
|
168
168
|
tdDevice.appendChild(label);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "homebridge-unifi-protect",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.9.0",
|
|
4
4
|
"displayName": "Homebridge UniFi Protect",
|
|
5
5
|
"description": "Homebridge UniFi Protect plugin providing complete HomeKit integration for the UniFi Protect ecosystem with full support for most features including autoconfiguration, motion detection, multiple controllers, and realtime updates.",
|
|
6
6
|
"author": {
|
|
@@ -75,22 +75,21 @@
|
|
|
75
75
|
},
|
|
76
76
|
"main": "dist/index.js",
|
|
77
77
|
"dependencies": {
|
|
78
|
-
"@homebridge/plugin-ui-utils": "
|
|
78
|
+
"@homebridge/plugin-ui-utils": "2.0.0",
|
|
79
79
|
"ffmpeg-for-homebridge": "^2.1.7",
|
|
80
|
-
"homebridge-plugin-utils": "^1.
|
|
81
|
-
"unifi-protect": "^4.
|
|
80
|
+
"homebridge-plugin-utils": "^1.11.3",
|
|
81
|
+
"unifi-protect": "^4.17.0",
|
|
82
82
|
"ws": "8.18.0"
|
|
83
83
|
},
|
|
84
84
|
"devDependencies": {
|
|
85
|
-
"@stylistic/eslint-plugin": "2.
|
|
86
|
-
"@types/node": "22.
|
|
87
|
-
"@types/
|
|
88
|
-
"
|
|
89
|
-
"eslint": "9.13.0",
|
|
85
|
+
"@stylistic/eslint-plugin": "2.11.0",
|
|
86
|
+
"@types/node": "22.10.1",
|
|
87
|
+
"@types/ws": "8.5.13",
|
|
88
|
+
"eslint": "9.16.0",
|
|
90
89
|
"homebridge": "1.8.4",
|
|
91
90
|
"shx": "0.3.4",
|
|
92
|
-
"typescript": "5.
|
|
93
|
-
"typescript-eslint": "8.
|
|
91
|
+
"typescript": "5.7.2",
|
|
92
|
+
"typescript-eslint": "8.17.0"
|
|
94
93
|
},
|
|
95
94
|
"optionalDependencies": {
|
|
96
95
|
"bufferutil": "4.0.8"
|