homebridge-unifi-protect 6.12.2 → 6.13.1
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/README.md +1 -1
- package/dist/protect-camera.js +1 -1
- package/dist/protect-camera.js.map +1 -1
- package/dist/protect-device.d.ts +1 -0
- package/dist/protect-device.js +19 -17
- package/dist/protect-device.js.map +1 -1
- package/dist/protect-doorbell.js +1 -1
- package/dist/protect-doorbell.js.map +1 -1
- package/dist/protect-ffmpeg.d.ts +1 -1
- package/dist/protect-ffmpeg.js +1 -1
- package/dist/protect-ffmpeg.js.map +1 -1
- package/dist/protect-mqtt.js +1 -2
- package/dist/protect-mqtt.js.map +1 -1
- package/dist/protect-nvr-events.js +37 -47
- package/dist/protect-nvr-events.js.map +1 -1
- package/dist/protect-nvr.d.ts +2 -1
- package/dist/protect-nvr.js +6 -7
- package/dist/protect-nvr.js.map +1 -1
- package/dist/protect-platform.js +2 -2
- package/dist/protect-platform.js.map +1 -1
- package/dist/protect-record.js +32 -17
- package/dist/protect-record.js.map +1 -1
- package/dist/protect-sensor.js +7 -2
- package/dist/protect-sensor.js.map +1 -1
- package/dist/protect-stream.js +2 -2
- package/dist/protect-stream.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/index.html +147 -159
- package/homebridge-ui/public/protect-featureoptions.mjs +11 -20
- package/homebridge-ui/public/ui.mjs +79 -39
- package/homebridge-ui/server.js +58 -10
- package/package.json +7 -7
|
@@ -81,13 +81,11 @@ export class ProtectFeatureOptions extends FeatureOptions {
|
|
|
81
81
|
if(!this.currentConfig[0]?.controllers?.length) {
|
|
82
82
|
|
|
83
83
|
document.getElementById("headerInfo").innerHTML = "Please configure a UniFi Protect controller to access in the main settings tab before configuring feature options."
|
|
84
|
+
document.getElementById("headerInfo").style.display = "";
|
|
84
85
|
homebridge.hideSpinner();
|
|
85
86
|
return;
|
|
86
87
|
}
|
|
87
88
|
|
|
88
|
-
// Initialize our informational header.
|
|
89
|
-
document.getElementById("headerInfo").innerHTML = "Feature options are applied in prioritized order, from global to device-specific options:<br><i class=\"text-warning\">Global options</i> (lowest priority) → <i class=\"text-success\">Protect controller options</i> → <i class=\"text-info\">Protect device options</i> (highest priority)"
|
|
90
|
-
|
|
91
89
|
// Enumerate our global options.
|
|
92
90
|
const trGlobal = document.createElement("tr");
|
|
93
91
|
|
|
@@ -189,7 +187,7 @@ export class ProtectFeatureOptions extends FeatureOptions {
|
|
|
189
187
|
// If we're not accessing global options, pull a list of devices attached to this controller.
|
|
190
188
|
if(controller) {
|
|
191
189
|
|
|
192
|
-
this.ufpDevices = await homebridge.request("/getDevices", { address: controller.address,
|
|
190
|
+
this.ufpDevices = await homebridge.request("/getDevices", { address: controller.address, username: controller.username, password: controller.password });
|
|
193
191
|
}
|
|
194
192
|
|
|
195
193
|
// Couldn't connect to the Protect controller for some reason.
|
|
@@ -198,24 +196,21 @@ export class ProtectFeatureOptions extends FeatureOptions {
|
|
|
198
196
|
devicesTable.innerHTML = "";
|
|
199
197
|
this.configTable.innerHTML = "";
|
|
200
198
|
|
|
201
|
-
document.getElementById("
|
|
202
|
-
document.getElementById("
|
|
203
|
-
document.getElementById("
|
|
204
|
-
document.getElementById("device_model").classList.add("text-center");
|
|
205
|
-
document.getElementById("deviceStatsHeader").style.display = "none";
|
|
206
|
-
|
|
207
|
-
document.getElementById("device_mac").innerHTML = "";
|
|
208
|
-
document.getElementById("device_address").innerHTML = "";
|
|
209
|
-
document.getElementById("device_online").innerHTML = "";
|
|
210
|
-
document.getElementById("deviceStatsTable").style.display = "inline-table";
|
|
199
|
+
document.getElementById("headerInfo").innerHTML = "Unable to connect to the Protect controller.<br>Check your settings for this controller in the settings tab to verify they are correct.<br><code class=\"text-danger\">" + (await homebridge.request("/getErrorMessage")) + "</code>";
|
|
200
|
+
document.getElementById("headerInfo").style.display = "";
|
|
201
|
+
document.getElementById("deviceStatsTable").style.display = "none";
|
|
211
202
|
|
|
212
203
|
homebridge.hideSpinner();
|
|
213
204
|
return;
|
|
214
205
|
}
|
|
215
206
|
|
|
207
|
+
// Initialize our informational header.
|
|
208
|
+
document.getElementById("headerInfo").innerHTML = "Feature options are applied in prioritized order, from global to device-specific options:<br><i class=\"text-warning\">Global options</i> (lowest priority) → <i class=\"text-success\">Protect controller options</i> → <i class=\"text-info\">Protect device options</i> (highest priority)"
|
|
209
|
+
|
|
216
210
|
// Make the UI visible.
|
|
217
|
-
document.getElementById("sidebar").style.display = "";
|
|
218
211
|
document.getElementById("headerInfo").style.display = "";
|
|
212
|
+
document.getElementById("sidebar").style.display = "";
|
|
213
|
+
document.getElementById("deviceStatsTable").style.display = "";
|
|
219
214
|
|
|
220
215
|
const modelKeys = [...new Set(this.ufpDevices.map(x => x.modelKey))];
|
|
221
216
|
this.deviceList = [];
|
|
@@ -314,7 +309,6 @@ export class ProtectFeatureOptions extends FeatureOptions {
|
|
|
314
309
|
// Ensure we have a controller or device. The only time this won't be the case is when we're looking at global options.
|
|
315
310
|
if(ufpDevice) {
|
|
316
311
|
|
|
317
|
-
document.getElementById("deviceStatsHeader").style.display = "";
|
|
318
312
|
document.getElementById("device_model").classList.remove("text-center");
|
|
319
313
|
document.getElementById("device_model").colSpan = 1;
|
|
320
314
|
document.getElementById("device_model").style.fontWeight = "normal";
|
|
@@ -322,13 +316,10 @@ export class ProtectFeatureOptions extends FeatureOptions {
|
|
|
322
316
|
document.getElementById("device_mac").innerHTML = ufpDevice.mac;
|
|
323
317
|
document.getElementById("device_address").innerHTML = ufpDevice.host ?? (ufpDevice.modelKey === "sensor" ? "Bluetooth Device" : "None");
|
|
324
318
|
document.getElementById("device_online").innerHTML = ("state" in ufpDevice) ? (ufpDevice.state.charAt(0).toUpperCase() + ufpDevice.state.slice(1).toLowerCase()) : "Connected";
|
|
325
|
-
|
|
326
|
-
document.getElementById("deviceStatsTable").style.display = "inline-table";
|
|
319
|
+
document.getElementById("deviceStatsTable").style.display = "";
|
|
327
320
|
} else {
|
|
328
321
|
|
|
329
322
|
document.getElementById("deviceStatsTable").style.display = "none";
|
|
330
|
-
|
|
331
|
-
document.getElementById("deviceStatsHeader").style.display = "";
|
|
332
323
|
document.getElementById("device_model").classList.remove("text-center");
|
|
333
324
|
document.getElementById("device_model").colSpan = 1;
|
|
334
325
|
document.getElementById("device_model").style.fontWeight = "normal";
|
|
@@ -9,49 +9,85 @@ import { ProtectFeatureOptions } from "./protect-featureoptions.mjs";
|
|
|
9
9
|
// Keep a list of all the feature options and option groups.
|
|
10
10
|
const featureOptions = new ProtectFeatureOptions();
|
|
11
11
|
|
|
12
|
-
//
|
|
13
|
-
|
|
12
|
+
// Show the first run user experience if we don't have valid login credentials.
|
|
13
|
+
function showFirstRun () {
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
const buttonFirstRun = document.getElementById("firstRun");
|
|
16
|
+
const inputAddress = document.getElementById("address");
|
|
17
|
+
const inputUsername = document.getElementById("username");
|
|
18
|
+
const inputPassword = document.getElementById("password");
|
|
19
|
+
const tdLoginError = document.getElementById("loginError");
|
|
17
20
|
|
|
18
|
-
//
|
|
19
|
-
|
|
20
|
-
featureOptions.currentConfig[0].disablePlugin = false;
|
|
21
|
+
// If we don't have any controllers configured, initialize the list.
|
|
22
|
+
if(!featureOptions.currentConfig[0].controllers) {
|
|
21
23
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
+
featureOptions.currentConfig[0].controllers = [ {} ];
|
|
25
|
+
}
|
|
24
26
|
|
|
25
|
-
//
|
|
26
|
-
|
|
27
|
-
|
|
27
|
+
// Pre-populate with anything we might already have in our configuration.
|
|
28
|
+
inputAddress.value = featureOptions.currentConfig[0].controllers[0].address ?? "";
|
|
29
|
+
inputUsername.value = featureOptions.currentConfig[0].controllers[0].username ?? "";
|
|
30
|
+
inputPassword.value = featureOptions.currentConfig[0].controllers[0].password ?? "";
|
|
28
31
|
|
|
29
|
-
//
|
|
30
|
-
|
|
32
|
+
// Clear login error messages when the login credentials change.
|
|
33
|
+
inputAddress.addEventListener("input", () => {
|
|
31
34
|
|
|
32
|
-
|
|
33
|
-
}
|
|
35
|
+
tdLoginError.innerHTML = " ";
|
|
36
|
+
});
|
|
34
37
|
|
|
35
|
-
|
|
36
|
-
function showIntro () {
|
|
38
|
+
inputUsername.addEventListener("input", () => {
|
|
37
39
|
|
|
38
|
-
|
|
40
|
+
tdLoginError.innerHTML = " ";
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
inputPassword.addEventListener("input", () => {
|
|
44
|
+
|
|
45
|
+
tdLoginError.innerHTML = " ";
|
|
46
|
+
});
|
|
39
47
|
|
|
40
|
-
|
|
48
|
+
// First run user experience.
|
|
49
|
+
buttonFirstRun.addEventListener("click", async () => {
|
|
41
50
|
|
|
42
51
|
// Show the beachball while we setup.
|
|
43
52
|
homebridge.showSpinner();
|
|
44
53
|
|
|
54
|
+
const address = inputAddress.value;
|
|
55
|
+
const username = inputUsername.value;
|
|
56
|
+
const password = inputPassword.value;
|
|
57
|
+
|
|
58
|
+
tdLoginError.innerHTML = " ";
|
|
59
|
+
|
|
60
|
+
if(!address?.length || !username?.length || !password?.length) {
|
|
61
|
+
|
|
62
|
+
tdLoginError.appendChild(document.createTextNode("Please enter a valid UniFi Protect controller address, username and password."));
|
|
63
|
+
homebridge.hideSpinner();
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const ufpDevices = await homebridge.request("/getDevices", { address: address, username: username, password: password });
|
|
68
|
+
|
|
69
|
+
// Couldn't connect to the Protect controller for some reason.
|
|
70
|
+
if(!ufpDevices?.length) {
|
|
71
|
+
|
|
72
|
+
tdLoginError.innerHTML = "Unable to login to the UniFi Protect controller.<br>Please check your controller address, username, and password.<br><code class=\"text-danger\">" + (await homebridge.request("/getErrorMessage")) + "</code>";
|
|
73
|
+
homebridge.hideSpinner();
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Save the login credentials to our configuration.
|
|
78
|
+
featureOptions.currentConfig[0].controllers[0].address = address;
|
|
79
|
+
featureOptions.currentConfig[0].controllers[0].username = username;
|
|
80
|
+
featureOptions.currentConfig[0].controllers[0].password = password;
|
|
81
|
+
|
|
82
|
+
await homebridge.updatePluginConfig(featureOptions.currentConfig);
|
|
83
|
+
|
|
45
84
|
// Create our UI.
|
|
46
|
-
document.getElementById("
|
|
85
|
+
document.getElementById("pageFirstRun").style.display = "none";
|
|
47
86
|
document.getElementById("menuWrapper").style.display = "inline-flex";
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
// All done. Let the user interact with us.
|
|
51
|
-
homebridge.hideSpinner();
|
|
87
|
+
featureOptions.showUI();
|
|
52
88
|
});
|
|
53
89
|
|
|
54
|
-
document.getElementById("
|
|
90
|
+
document.getElementById("pageFirstRun").style.display = "block";
|
|
55
91
|
}
|
|
56
92
|
|
|
57
93
|
// Show the main plugin configuration tab.
|
|
@@ -109,31 +145,35 @@ async function launchWebUI() {
|
|
|
109
145
|
menuHome.addEventListener("click", () => showSupport());
|
|
110
146
|
menuFeatureOptions.addEventListener("click", () => featureOptions.showUI());
|
|
111
147
|
menuSettings.addEventListener("click", () => showSettings());
|
|
112
|
-
disabledEnable.addEventListener("click", () => enablePlugin());
|
|
113
148
|
|
|
114
|
-
|
|
149
|
+
// If we've got a valid Protect controller, username, and password configured, we launch our feature option UI. Otherwise, we launch our first run UI.
|
|
150
|
+
if(featureOptions.currentConfig.length && featureOptions.currentConfig[0].controllers?.length && featureOptions.currentConfig[0].controllers[0]?.address?.length && featureOptions.currentConfig[0].controllers[0]?.username?.length && featureOptions.currentConfig[0].controllers[0]?.password?.length) {
|
|
115
151
|
|
|
116
|
-
document.getElementById("menuWrapper").style.display = "inline-flex"
|
|
117
|
-
|
|
152
|
+
document.getElementById("menuWrapper").style.display = "inline-flex";
|
|
153
|
+
featureOptions.showUI();
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
118
156
|
|
|
119
|
-
|
|
120
|
-
|
|
157
|
+
// If we have no configuration, let's create one.
|
|
158
|
+
if(!featureOptions.currentConfig.length) {
|
|
121
159
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
} else {
|
|
160
|
+
featureOptions.currentConfig.push({ controllers: [ {} ], name: "UniFi Protect" });
|
|
161
|
+
} else if(!("name" in featureOptions.currentConfig[0])) {
|
|
125
162
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
showIntro();
|
|
163
|
+
// If we haven't set the name, let's do so now.
|
|
164
|
+
featureOptions.currentConfig[0].name = "UniFi Protect";
|
|
129
165
|
}
|
|
166
|
+
|
|
167
|
+
// Update the plugin configuration and launch the first run UI.
|
|
168
|
+
await homebridge.updatePluginConfig(featureOptions.currentConfig);
|
|
169
|
+
showFirstRun();
|
|
130
170
|
}
|
|
131
171
|
|
|
132
172
|
// Fire off our UI, catching errors along the way.
|
|
133
173
|
try {
|
|
134
174
|
|
|
135
175
|
launchWebUI();
|
|
136
|
-
} catch
|
|
176
|
+
} catch(err) {
|
|
137
177
|
|
|
138
178
|
// If we had an error instantiating or updating the UI, notify the user.
|
|
139
179
|
homebridge.toast.error(err.message, "Error");
|
package/homebridge-ui/server.js
CHANGED
|
@@ -1,33 +1,80 @@
|
|
|
1
1
|
/* Copyright(C) 2017-2023, HJD (https://github.com/hjdhjd). All rights reserved.
|
|
2
2
|
*
|
|
3
|
-
* server.js:
|
|
3
|
+
* server.js: homebridge-unifi-protect webUI server API.
|
|
4
4
|
*
|
|
5
5
|
* This module is heavily inspired by the homebridge-config-ui-x source code and borrows from both.
|
|
6
6
|
* Thank you oznu for your contributions to the HomeKit world.
|
|
7
7
|
*/
|
|
8
|
-
/* eslint-disable no-undef */
|
|
9
|
-
/* eslint-disable @typescript-eslint/no-var-requires */
|
|
10
|
-
/* jshint node: true,esversion: 9, -W014, -W033 */
|
|
11
|
-
/* eslint-disable new-cap */
|
|
12
8
|
"use strict";
|
|
13
9
|
|
|
14
10
|
import { featureOptionCategories, featureOptions, isOptionEnabled } from "../dist/protect-options.js";
|
|
15
11
|
import { HomebridgePluginUiServer } from "@homebridge/plugin-ui-utils";
|
|
16
12
|
import { ProtectApi } from "unifi-protect";
|
|
17
|
-
import
|
|
13
|
+
import util from "node:util";
|
|
18
14
|
|
|
19
15
|
class PluginUiServer extends HomebridgePluginUiServer {
|
|
20
16
|
|
|
17
|
+
errorInfo;
|
|
18
|
+
|
|
21
19
|
constructor () {
|
|
22
20
|
super();
|
|
23
21
|
|
|
22
|
+
this.errorInfo = "";
|
|
23
|
+
|
|
24
|
+
// Register getErrorMessage() with the Homebridge server API.
|
|
25
|
+
this.#registerGetErrorMessage();
|
|
26
|
+
|
|
27
|
+
// Register getDevices() with the Homebridge server API.
|
|
28
|
+
this.#registerGetDevices();
|
|
29
|
+
|
|
30
|
+
// Register getOptions() with the Homebridge server API.
|
|
31
|
+
this.#registerGetOptions();
|
|
32
|
+
|
|
33
|
+
this.ready();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Register the getErrorMessage() webUI server API endpoint.
|
|
37
|
+
#registerGetErrorMessage() {
|
|
38
|
+
|
|
39
|
+
// Return the list of Protect devices.
|
|
40
|
+
this.onRequest("/getErrorMessage", async () => {
|
|
41
|
+
|
|
42
|
+
try {
|
|
43
|
+
|
|
44
|
+
return this.errorInfo;
|
|
45
|
+
} catch(err) {
|
|
46
|
+
|
|
47
|
+
console.log(err);
|
|
48
|
+
|
|
49
|
+
// Return nothing if we error out for some reason.
|
|
50
|
+
return "";
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Register the getDevices() webUI server API endpoint.
|
|
56
|
+
#registerGetDevices() {
|
|
57
|
+
|
|
24
58
|
// Return the list of Protect devices.
|
|
25
59
|
this.onRequest("/getDevices", async (controller) => {
|
|
26
60
|
|
|
27
61
|
try {
|
|
28
62
|
|
|
63
|
+
const log = {
|
|
64
|
+
|
|
65
|
+
debug: (message, parameters) => {},
|
|
66
|
+
error: (message, parameters = []) => {
|
|
67
|
+
|
|
68
|
+
// Save the error to inform the user in the webUI.
|
|
69
|
+
this.errorInfo = util.format(message, ...parameters);
|
|
70
|
+
console.log(this.errorInfo);
|
|
71
|
+
},
|
|
72
|
+
info: (message, parameters) => {},
|
|
73
|
+
warn: (message, parameters = []) => console.log(util.format(message, ...parameters))
|
|
74
|
+
};
|
|
75
|
+
|
|
29
76
|
// Connect to the Protect controller.
|
|
30
|
-
const ufpApi = new ProtectApi();
|
|
77
|
+
const ufpApi = new ProtectApi(log);
|
|
31
78
|
|
|
32
79
|
if(!(await ufpApi.login(controller.address, controller.username, controller.password))) {
|
|
33
80
|
|
|
@@ -91,13 +138,16 @@ class PluginUiServer extends HomebridgePluginUiServer {
|
|
|
91
138
|
return [ ufpApi.bootstrap.nvr, ...ufpApi.bootstrap.cameras, ...ufpApi.bootstrap.chimes, ...ufpApi.bootstrap.lights, ...ufpApi.bootstrap.sensors, ...ufpApi.bootstrap.viewers ];
|
|
92
139
|
} catch(err) {
|
|
93
140
|
|
|
94
|
-
console.log("ERRORING OUT FOR " + controller.address);
|
|
95
141
|
console.log(err);
|
|
96
142
|
|
|
97
143
|
// Return nothing if we error out for some reason.
|
|
98
144
|
return [];
|
|
99
145
|
}
|
|
100
146
|
});
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Register the getOptions() webUI server API endpoint.
|
|
150
|
+
#registerGetOptions() {
|
|
101
151
|
|
|
102
152
|
// Return the list of options configured for a given Protect device.
|
|
103
153
|
this.onRequest("/getOptions", async(request) => {
|
|
@@ -126,8 +176,6 @@ class PluginUiServer extends HomebridgePluginUiServer {
|
|
|
126
176
|
return {};
|
|
127
177
|
}
|
|
128
178
|
});
|
|
129
|
-
|
|
130
|
-
this.ready();
|
|
131
179
|
}
|
|
132
180
|
}
|
|
133
181
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "homebridge-unifi-protect",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.13.1",
|
|
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": {
|
|
@@ -82,15 +82,15 @@
|
|
|
82
82
|
"ws": "8.13.0"
|
|
83
83
|
},
|
|
84
84
|
"devDependencies": {
|
|
85
|
-
"@types/node": "20.5.
|
|
86
|
-
"@types/readable-stream": "4.0.
|
|
85
|
+
"@types/node": "20.5.6",
|
|
86
|
+
"@types/readable-stream": "4.0.2",
|
|
87
87
|
"@types/ws": "8.5.5",
|
|
88
|
-
"@typescript-eslint/eslint-plugin": "6.4.
|
|
89
|
-
"@typescript-eslint/parser": "6.4.
|
|
90
|
-
"eslint": "8.
|
|
88
|
+
"@typescript-eslint/eslint-plugin": "6.4.1",
|
|
89
|
+
"@typescript-eslint/parser": "6.4.1",
|
|
90
|
+
"eslint": "8.48.0",
|
|
91
91
|
"homebridge": "1.6.1",
|
|
92
92
|
"nodemon": "3.0.1",
|
|
93
93
|
"rimraf": "5.0.1",
|
|
94
|
-
"typescript": "5.
|
|
94
|
+
"typescript": "5.2.2"
|
|
95
95
|
}
|
|
96
96
|
}
|