@homebridge-plugins/homebridge-eufy-security 4.4.4-beta.1 → 4.4.4-beta.11
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/accessories/BaseAccessory.js +47 -29
- package/dist/accessories/BaseAccessory.js.map +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/homebridge-ui/public/app.js +1 -1
- package/homebridge-ui/public/assets/devices/garage_camera_t8452_large.png +0 -0
- package/homebridge-ui/public/assets/devices/homebasemini_large.png +0 -0
- package/homebridge-ui/public/assets/devices/indoorcams350_large.png +0 -0
- package/homebridge-ui/public/assets/devices/solocame42_large.png +0 -0
- package/homebridge-ui/public/assets/devices/walllight_s100_large.png +0 -0
- package/homebridge-ui/public/assets/devices/walllight_s120_large.png +0 -0
- package/homebridge-ui/public/style.css +14 -0
- package/homebridge-ui/public/utils/device-images.js +6 -5
- package/homebridge-ui/public/utils/helpers.js +28 -0
- package/homebridge-ui/public/views/login.js +19 -5
- package/homebridge-ui/server.js +129 -148
- package/package.json +1 -1
- package/homebridge-ui/public/assets/devices/garage_camera_t8452_large.jpg +0 -0
- package/homebridge-ui/public/assets/devices/homebasemini_large.jpg +0 -0
- package/homebridge-ui/public/assets/devices/indoorcams350_large.jpg +0 -0
- package/homebridge-ui/public/assets/devices/walllight_s100_large.jpg +0 -0
- package/homebridge-ui/public/assets/devices/walllight_s120_large.jpg +0 -0
|
@@ -4,18 +4,50 @@ import { DeviceType } from 'eufy-security-client';
|
|
|
4
4
|
import { EventEmitter } from 'events';
|
|
5
5
|
import { CHAR, SERV, log } from '../utils/utils.js';
|
|
6
6
|
/**
|
|
7
|
-
* Determine if the serviceType is an instance of Service
|
|
7
|
+
* Determine if the serviceType is an instance of Service (as opposed to a
|
|
8
|
+
* Service *constructor*). Constructors are functions, while instances are
|
|
9
|
+
* objects that carry a `characteristics` array.
|
|
8
10
|
*
|
|
9
11
|
* @param {WithUUID<typeof Service> | Service} serviceType - The service type to be checked.
|
|
10
12
|
* @returns {boolean} Returns true if the serviceType is an instance of Service, otherwise false.
|
|
11
13
|
*/
|
|
12
14
|
function isServiceInstance(serviceType) {
|
|
13
|
-
return typeof serviceType === 'object'
|
|
15
|
+
return (typeof serviceType === 'object' &&
|
|
16
|
+
serviceType !== null &&
|
|
17
|
+
'characteristics' in serviceType);
|
|
14
18
|
}
|
|
15
19
|
export class BaseAccessory extends EventEmitter {
|
|
16
20
|
platform;
|
|
17
21
|
accessory;
|
|
18
22
|
device;
|
|
23
|
+
/**
|
|
24
|
+
* Cameras accumulate many listeners (property changes, events, snapshots,
|
|
25
|
+
* streaming). Raise the limit to prevent MaxListenersExceededWarning in
|
|
26
|
+
* Node 22+.
|
|
27
|
+
*/
|
|
28
|
+
static MAX_DEVICE_LISTENERS = 30;
|
|
29
|
+
/**
|
|
30
|
+
* Service UUIDs managed by CameraController that must never be pruned.
|
|
31
|
+
* CameraController creates these automatically during configureController()
|
|
32
|
+
* and they are not tracked in servicesInUse.
|
|
33
|
+
*
|
|
34
|
+
* Built lazily because SERV is only available after setHap() has been called,
|
|
35
|
+
* which happens after module import.
|
|
36
|
+
*/
|
|
37
|
+
static _cameraControllerServiceUUIDs;
|
|
38
|
+
static get CAMERA_CONTROLLER_SERVICE_UUIDS() {
|
|
39
|
+
if (!BaseAccessory._cameraControllerServiceUUIDs) {
|
|
40
|
+
BaseAccessory._cameraControllerServiceUUIDs = new Set([
|
|
41
|
+
SERV.CameraRTPStreamManagement.UUID,
|
|
42
|
+
SERV.CameraOperatingMode.UUID,
|
|
43
|
+
SERV.CameraRecordingManagement.UUID,
|
|
44
|
+
SERV.DataStreamTransportManagement.UUID,
|
|
45
|
+
SERV.Microphone.UUID,
|
|
46
|
+
SERV.Speaker.UUID,
|
|
47
|
+
]);
|
|
48
|
+
}
|
|
49
|
+
return BaseAccessory._cameraControllerServiceUUIDs;
|
|
50
|
+
}
|
|
19
51
|
servicesInUse;
|
|
20
52
|
SN;
|
|
21
53
|
name;
|
|
@@ -54,32 +86,30 @@ export class BaseAccessory extends EventEmitter {
|
|
|
54
86
|
this.registerCharacteristic({
|
|
55
87
|
serviceType: SERV.AccessoryInformation,
|
|
56
88
|
characteristicType: CHAR.Name,
|
|
57
|
-
getValue: () => this.name || '
|
|
89
|
+
getValue: () => this.name || 'Unknown',
|
|
58
90
|
});
|
|
59
91
|
this.registerCharacteristic({
|
|
60
92
|
serviceType: SERV.AccessoryInformation,
|
|
61
93
|
characteristicType: CHAR.Model,
|
|
62
|
-
getValue: () => DeviceType[this.device.getDeviceType()] || '
|
|
94
|
+
getValue: () => DeviceType[this.device.getDeviceType()] || 'Unknown',
|
|
63
95
|
});
|
|
64
96
|
this.registerCharacteristic({
|
|
65
97
|
serviceType: SERV.AccessoryInformation,
|
|
66
98
|
characteristicType: CHAR.SerialNumber,
|
|
67
|
-
getValue: () => this.SN || '
|
|
99
|
+
getValue: () => this.SN || 'Unknown',
|
|
68
100
|
});
|
|
69
101
|
this.registerCharacteristic({
|
|
70
102
|
serviceType: SERV.AccessoryInformation,
|
|
71
103
|
characteristicType: CHAR.FirmwareRevision,
|
|
72
|
-
getValue: () => this.device.getSoftwareVersion() || '
|
|
104
|
+
getValue: () => this.device.getSoftwareVersion() || 'Unknown',
|
|
73
105
|
});
|
|
74
106
|
this.registerCharacteristic({
|
|
75
107
|
serviceType: SERV.AccessoryInformation,
|
|
76
108
|
characteristicType: CHAR.HardwareRevision,
|
|
77
|
-
getValue: () => this.device.getHardwareVersion() || '
|
|
109
|
+
getValue: () => this.device.getHardwareVersion() || 'Unknown',
|
|
78
110
|
});
|
|
79
|
-
// Cameras accumulate many listeners (property changes, events, snapshots, streaming).
|
|
80
|
-
// Raise limit to prevent MaxListenersExceededWarning in Node 22+.
|
|
81
111
|
if (typeof this.device.setMaxListeners === 'function') {
|
|
82
|
-
this.device.setMaxListeners(
|
|
112
|
+
this.device.setMaxListeners(BaseAccessory.MAX_DEVICE_LISTENERS);
|
|
83
113
|
}
|
|
84
114
|
if (this.platform.config.enableDetailedLogging) {
|
|
85
115
|
this.device.on('raw property changed', this.handleRawPropertyChange.bind(this));
|
|
@@ -89,11 +119,10 @@ export class BaseAccessory extends EventEmitter {
|
|
|
89
119
|
// This keeps all getValue-based characteristics up-to-date via push
|
|
90
120
|
// without requiring HomeKit to poll through onGet.
|
|
91
121
|
this.device.on('property changed', this.refreshCachedValues.bind(this));
|
|
92
|
-
this.
|
|
122
|
+
this.logDeviceProperties();
|
|
93
123
|
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
this.log.debug(`Property Keys:`, this.device.getProperties());
|
|
124
|
+
logDeviceProperties() {
|
|
125
|
+
this.log.debug(`Device Properties:`, this.device.getProperties());
|
|
97
126
|
}
|
|
98
127
|
/**
|
|
99
128
|
* Re-evaluate every registered getValue and push updates to HomeKit
|
|
@@ -110,8 +139,8 @@ export class BaseAccessory extends EventEmitter {
|
|
|
110
139
|
this.log.debug(`CACHE '${reg.serviceTypeName} / ${reg.characteristicTypeName}':`, newValue);
|
|
111
140
|
}
|
|
112
141
|
}
|
|
113
|
-
catch {
|
|
114
|
-
|
|
142
|
+
catch (e) {
|
|
143
|
+
this.log.debug(`Cache refresh error for '${reg.serviceTypeName} / ${reg.characteristicTypeName}':`, e);
|
|
115
144
|
}
|
|
116
145
|
}
|
|
117
146
|
}
|
|
@@ -174,7 +203,7 @@ export class BaseAccessory extends EventEmitter {
|
|
|
174
203
|
}
|
|
175
204
|
else if (setValue) {
|
|
176
205
|
characteristic.onSet(async (value) => {
|
|
177
|
-
|
|
206
|
+
await setValue(value, characteristic, service);
|
|
178
207
|
});
|
|
179
208
|
}
|
|
180
209
|
if (onSimpleValue) {
|
|
@@ -224,20 +253,9 @@ export class BaseAccessory extends EventEmitter {
|
|
|
224
253
|
return service;
|
|
225
254
|
}
|
|
226
255
|
pruneUnusedServices() {
|
|
227
|
-
// Services managed by CameraController must never be pruned.
|
|
228
|
-
// CameraController creates these automatically during configureController()
|
|
229
|
-
// and they are not tracked in servicesInUse.
|
|
230
|
-
const safeServiceUUIDs = [
|
|
231
|
-
SERV.CameraRTPStreamManagement.UUID,
|
|
232
|
-
SERV.CameraOperatingMode.UUID,
|
|
233
|
-
SERV.CameraRecordingManagement.UUID,
|
|
234
|
-
SERV.DataStreamTransportManagement.UUID,
|
|
235
|
-
SERV.Microphone.UUID,
|
|
236
|
-
SERV.Speaker.UUID,
|
|
237
|
-
];
|
|
238
256
|
this.accessory.services.forEach((service) => {
|
|
239
257
|
if (!this.servicesInUse.includes(service) &&
|
|
240
|
-
!
|
|
258
|
+
!BaseAccessory.CAMERA_CONTROLLER_SERVICE_UUIDS.has(service.UUID)) {
|
|
241
259
|
this.log.debug(`Pruning unused service ${service.UUID} ${service.displayName || service.name}`);
|
|
242
260
|
this.accessory.removeService(service);
|
|
243
261
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BaseAccessory.js","sourceRoot":"","sources":["../../src/accessories/BaseAccessory.ts"],"names":[],"mappings":"AAAA,2CAA2C;AAC3C,sDAAsD;AAUtD,OAAO,EAAE,UAAU,EAA+D,MAAM,sBAAsB,CAAC;AAC/G,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,mBAAmB,CAAC;AAGpD
|
|
1
|
+
{"version":3,"file":"BaseAccessory.js","sourceRoot":"","sources":["../../src/accessories/BaseAccessory.ts"],"names":[],"mappings":"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"}
|
package/dist/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export const LIB_VERSION = "4.4.4-beta.
|
|
1
|
+
export const LIB_VERSION = "4.4.4-beta.11";
|
|
2
2
|
//# sourceMappingURL=version.js.map
|
package/dist/version.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"version.js","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,WAAW,GAAG,
|
|
1
|
+
{"version":3,"file":"version.js","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,WAAW,GAAG,eAAe,CAAC"}
|
|
@@ -170,7 +170,7 @@ const App = {
|
|
|
170
170
|
*/
|
|
171
171
|
_showAdminError() {
|
|
172
172
|
this._root.innerHTML = `
|
|
173
|
-
<div class="alert alert-danger alert-admin
|
|
173
|
+
<div class="alert alert-danger alert-admin" role="alert">
|
|
174
174
|
<h5 class="alert-heading">${Helpers.iconHtml('warning.svg')} Admin Account Detected</h5>
|
|
175
175
|
<p>
|
|
176
176
|
You are not using a <strong>dedicated guest admin account</strong>.
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -404,6 +404,16 @@
|
|
|
404
404
|
margin: 0px auto;
|
|
405
405
|
}
|
|
406
406
|
|
|
407
|
+
.login-card .input-group {
|
|
408
|
+
overflow: visible;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
.login-card .input-group > .eufy-tooltip::after {
|
|
412
|
+
left: auto;
|
|
413
|
+
right: 0;
|
|
414
|
+
transform: none;
|
|
415
|
+
}
|
|
416
|
+
|
|
407
417
|
.login-section-title {
|
|
408
418
|
font-size: 1.2rem;
|
|
409
419
|
font-weight: 600;
|
|
@@ -537,6 +547,10 @@
|
|
|
537
547
|
/* ===== Alert overrides ===== */
|
|
538
548
|
.alert-admin {
|
|
539
549
|
border-radius: var(--eufy-border-radius);
|
|
550
|
+
overflow-wrap: break-word;
|
|
551
|
+
word-break: break-word;
|
|
552
|
+
max-width: 100%;
|
|
553
|
+
box-sizing: border-box;
|
|
540
554
|
}
|
|
541
555
|
|
|
542
556
|
/* ===== Node.js Version Warning Banner ===== */
|
|
@@ -24,7 +24,7 @@ const DeviceImages = {
|
|
|
24
24
|
case 24: return 'eufycame330_large.jpg';
|
|
25
25
|
case 25: return 'minibase_chime_T8023_large.jpg';
|
|
26
26
|
case 26: return 'eufycam3pro_large.png';
|
|
27
|
-
case 28: return 'homebasemini_large.
|
|
27
|
+
case 28: return 'homebasemini_large.png';
|
|
28
28
|
case 30: return 'indoorcamc120_large.png';
|
|
29
29
|
case 31: case 35: return 'indoorcamp24_large.png';
|
|
30
30
|
case 32: case 33: return 'solocame20_large.jpg';
|
|
@@ -58,23 +58,24 @@ const DeviceImages = {
|
|
|
58
58
|
case 94: return 'batterydoorbell_e340_large.png';
|
|
59
59
|
case 95: return 'BATTERY_DOORBELL_C30.png';
|
|
60
60
|
case 96: return 'BATTERY_DOORBELL_C31.png';
|
|
61
|
+
case 98: return 'solocame42_large.png';
|
|
61
62
|
case 100: return 'indoorcammini_large.jpg';
|
|
62
|
-
case 104: return 'indoorcams350_large.
|
|
63
|
+
case 104: return 'indoorcams350_large.png';
|
|
63
64
|
case 105: return 'indoorcamE30_large.png';
|
|
64
65
|
case 101: case 102:
|
|
65
66
|
case 131: case 132: case 133:
|
|
66
|
-
return 'garage_camera_t8452_large.
|
|
67
|
+
return 'garage_camera_t8452_large.png';
|
|
67
68
|
case 110: return '4g_lte_starlight_large.jpg';
|
|
68
69
|
case 126: return 'sensor_large.png';
|
|
69
70
|
case 140: return 'smartsafe_s10_t7400_large.png';
|
|
70
71
|
case 141: return 'smartsafe_s12_t7401_large.png';
|
|
71
72
|
case 142: case 143: return 'smartsafe_s10_t7400_large.png';
|
|
72
|
-
case 151: return 'walllight_s100_large.
|
|
73
|
+
case 151: return 'walllight_s100_large.png';
|
|
73
74
|
case 157: return 'smarttrack_link_t87B0_large.png';
|
|
74
75
|
case 159: return 'smarttrack_card_t87B2_large.png';
|
|
75
76
|
case 180: return 'smartlock_touch_and_wifi_t8502_large.png';
|
|
76
77
|
case 184: return 'smartlock_touch_and_wifi_t8506_large.png';
|
|
77
|
-
case 10005: return 'walllight_s120_large.
|
|
78
|
+
case 10005: return 'walllight_s120_large.png';
|
|
78
79
|
case 10008: return 'indoorcamC220_large.png';
|
|
79
80
|
case 10009: return 'indoorcamC210_large.png';
|
|
80
81
|
case 10010: return 'indoorcamC220_large.png';
|
|
@@ -84,4 +84,32 @@ const Helpers = {
|
|
|
84
84
|
const level = Math.max(0, Math.min(6, Math.round((pct / 100) * 6)));
|
|
85
85
|
return 'battery_' + level + '.svg';
|
|
86
86
|
},
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Generate a random device name to identify this Homebridge instance to Eufy.
|
|
90
|
+
* @returns {string}
|
|
91
|
+
*/
|
|
92
|
+
generateDeviceName() {
|
|
93
|
+
const _d = (s) => atob(s).split('|');
|
|
94
|
+
const _p = (a) => a[Math.floor(Math.random() * a.length)];
|
|
95
|
+
const style = Math.floor(Math.random() * 3);
|
|
96
|
+
|
|
97
|
+
if (style === 1) {
|
|
98
|
+
return _p(_d('Sm9obnxFbW1hfEphbWVzfE9saXZpYXxXaWxsaWFtfFNvcGhpYXxBbGV4fE1pYXxEYW5pZWx8RWxsYXxEYXZpZHxHcmFjZXxDaHJpc3xMaWx5fFNhbQ=='))
|
|
99
|
+
+ '\'s '
|
|
100
|
+
+ _p(_d('aVBob25lfGlQaG9uZSAxM3xpUGhvbmUgMTMgUHJvfGlQaG9uZSAxNHxpUGhvbmUgMTQgUHJvfGlQaG9uZSAxNXxpUGhvbmUgMTUgUHJvfGlQaG9uZSAxNSBQcm8gTWF4fGlQaG9uZSAxNnxpUGhvbmUgMTYgUHJvfGlQaG9uZSAxNiBQcm8gTWF4fGlQYWR8aVBhZCBBaXJ8aVBhZCBQcm8='));
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (style === 2) {
|
|
104
|
+
return _p(_d('Sm9obnxNYXJpYXxDYXJsb3N8U2FyYWh8QWxleHxQcml5YXxMZW98TmluYXxPbWFyfFphcmF8S2FpfFl1a2l8QmVufEF2YXxNYXg='))
|
|
105
|
+
+ '\'s '
|
|
106
|
+
+ _p(_d('R2FsYXh5IFMyM3xHYWxheHkgUzI0fEdhbGF4eSBTMjQgVWx0cmF8R2FsYXh5IEE1NHxHYWxheHkgWiBGbGlwNXxHYWxheHkgWiBGb2xkNXxQaXhlbCA3fFBpeGVsIDh8UGl4ZWwgOCBQcm98UGl4ZWwgOXxQaXhlbCA5IFByb3xPbmVQbHVzIDEyfEdhbGF4eSBUYWIgUzl8UGl4ZWwgVGFibGV0'));
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return _p(_d('Q29yYWx8THVuYXJ8U29sYXJ8U3RlbGxhcnxBcmN0aWN8QW1iZXJ8QXp1cmV8Q3JpbXNvbnxHb2xkZW58SXZvcnl8SmFkZXxNYXBsZXxPbnl4fFBlYXJsfFF1YXJ0enxSdWJ5fFNpbHZlcnxUb3BhenxWZWx2ZXR8Q2VkYXI='))
|
|
110
|
+
+ ' '
|
|
111
|
+
+ _p(_d('QnJpZGdlfEh1YnxMaW5rfE5vZGV8R2F0ZXxSZWxheXxWYXVsdHxUb3dlcnxCZWFjb258TmV4dXN8UG9ydHxDb3JlfEFyY3xTcGFyaw=='))
|
|
112
|
+
+ ' '
|
|
113
|
+
+ Math.floor(Math.random() * 100);
|
|
114
|
+
},
|
|
87
115
|
};
|
|
@@ -229,8 +229,13 @@ const LoginView = {
|
|
|
229
229
|
</div>
|
|
230
230
|
<div class="mb-3">
|
|
231
231
|
<label for="login-device" class="form-label">Device Name</label>
|
|
232
|
-
<
|
|
233
|
-
|
|
232
|
+
<div class="input-group">
|
|
233
|
+
<input type="text" class="form-control" id="login-device" value="" placeholder="e.g. My Homebridge" required>
|
|
234
|
+
<button class="btn btn-outline-secondary eufy-tooltip" type="button" id="btn-generate-name" data-tooltip="Generate random name">
|
|
235
|
+
${Helpers.iconHtml('refresh.svg', 16, 'Generate name')}
|
|
236
|
+
</button>
|
|
237
|
+
</div>
|
|
238
|
+
<div class="form-text">A name to identify this Homebridge instance to Eufy.</div>
|
|
234
239
|
</div>
|
|
235
240
|
<div id="login-error" class="alert alert-danger d-none" role="alert"></div>
|
|
236
241
|
<button class="btn btn-primary w-100" id="btn-login" type="button">
|
|
@@ -252,6 +257,15 @@ const LoginView = {
|
|
|
252
257
|
// Pre-fill from existing config if available
|
|
253
258
|
this._prefillCredentials(body);
|
|
254
259
|
|
|
260
|
+
// Generate random device name (default if not already set by config)
|
|
261
|
+
const deviceInput = body.querySelector('#login-device');
|
|
262
|
+
if (!deviceInput.value) {
|
|
263
|
+
deviceInput.value = Helpers.generateDeviceName();
|
|
264
|
+
}
|
|
265
|
+
body.querySelector('#btn-generate-name').addEventListener('click', () => {
|
|
266
|
+
deviceInput.value = Helpers.generateDeviceName();
|
|
267
|
+
});
|
|
268
|
+
|
|
255
269
|
// Submit
|
|
256
270
|
body.querySelector('#btn-login').addEventListener('click', () => this._doLogin(body));
|
|
257
271
|
|
|
@@ -279,10 +293,10 @@ const LoginView = {
|
|
|
279
293
|
const email = body.querySelector('#login-email').value.trim();
|
|
280
294
|
const password = body.querySelector('#login-password').value;
|
|
281
295
|
const country = body.querySelector('#login-country').value;
|
|
282
|
-
const deviceName = body.querySelector('#login-device').value.trim()
|
|
296
|
+
const deviceName = body.querySelector('#login-device').value.trim();
|
|
283
297
|
|
|
284
|
-
if (!email || !password) {
|
|
285
|
-
this._showError(body, 'Please enter your email and
|
|
298
|
+
if (!email || !password || !deviceName) {
|
|
299
|
+
this._showError(body, 'Please enter your email, password, and device name.');
|
|
286
300
|
return;
|
|
287
301
|
}
|
|
288
302
|
|
package/homebridge-ui/server.js
CHANGED
|
@@ -65,7 +65,7 @@ class UiServer extends HomebridgePluginUiServer {
|
|
|
65
65
|
this.storagePath = this.homebridgeStoragePath + '/eufysecurity';
|
|
66
66
|
this.storedAccessories_file = this.storagePath + '/accessories.json';
|
|
67
67
|
this.unsupported_file = this.storagePath + '/unsupported.json';
|
|
68
|
-
this.diagnosticsZipFilePath = null;
|
|
68
|
+
this.diagnosticsZipFilePath = null;
|
|
69
69
|
this.config.persistentDir = this.storagePath;
|
|
70
70
|
|
|
71
71
|
this.initLogger();
|
|
@@ -74,17 +74,7 @@ class UiServer extends HomebridgePluginUiServer {
|
|
|
74
74
|
this.ready();
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
-
/**
|
|
78
|
-
* Compute a unified power descriptor from a properties object.
|
|
79
|
-
* Works for both devices and stations.
|
|
80
|
-
* @param {object} props - the properties object (from device.getProperties() or station.getProperties())
|
|
81
|
-
* @returns {{ source: string, icon: string, label: string, battery?: number, batteryLow?: boolean }}
|
|
82
|
-
* source: 'battery' | 'solar' | 'plugged' | null
|
|
83
|
-
* icon: icon filename for the UI
|
|
84
|
-
* label: display text for the UI
|
|
85
|
-
* battery: percentage (0-100) if available
|
|
86
|
-
* batteryLow: true/false for simple sensors without percentage
|
|
87
|
-
*/
|
|
77
|
+
/** Build a unified power descriptor (source, icon, label, battery) from a properties object. */
|
|
88
78
|
_computePower(props) {
|
|
89
79
|
const power = { source: null, icon: null, label: null };
|
|
90
80
|
|
|
@@ -140,35 +130,35 @@ class UiServer extends HomebridgePluginUiServer {
|
|
|
140
130
|
|
|
141
131
|
initLogger() {
|
|
142
132
|
const logOptions = {
|
|
143
|
-
name: `[UI-${LIB_VERSION}]`,
|
|
144
|
-
prettyLogTemplate: '[{{mm}}/{{dd}}/{{yyyy}}, {{hh}}:{{MM}}:{{ss}}]\t{{name}}\t{{logLevelName}}\t',
|
|
145
|
-
prettyErrorTemplate: '\n{{errorName}} {{errorMessage}}\nerror stack:\n{{errorStack}}',
|
|
146
|
-
prettyErrorStackTemplate: ' • {{fileName}}\t{{method}}\n\t{{fileNameWithLine}}',
|
|
147
|
-
prettyErrorParentNamesSeparator: '',
|
|
148
|
-
prettyErrorLoggerNameDelimiter: '\t',
|
|
149
|
-
stylePrettyLogs: true,
|
|
150
|
-
minLevel: 2,
|
|
151
|
-
prettyLogTimeZone: 'local',
|
|
152
|
-
prettyLogStyles: {
|
|
153
|
-
logLevelName: {
|
|
154
|
-
'*': ['bold', 'black', 'bgWhiteBright', 'dim'],
|
|
155
|
-
SILLY: ['bold', 'white'],
|
|
156
|
-
TRACE: ['bold', 'whiteBright'],
|
|
157
|
-
DEBUG: ['bold', 'green'],
|
|
158
|
-
INFO: ['bold', 'blue'],
|
|
159
|
-
WARN: ['bold', 'yellow'],
|
|
160
|
-
ERROR: ['bold', 'red'],
|
|
161
|
-
FATAL: ['bold', 'redBright'],
|
|
133
|
+
name: `[UI-${LIB_VERSION}]`,
|
|
134
|
+
prettyLogTemplate: '[{{mm}}/{{dd}}/{{yyyy}}, {{hh}}:{{MM}}:{{ss}}]\t{{name}}\t{{logLevelName}}\t',
|
|
135
|
+
prettyErrorTemplate: '\n{{errorName}} {{errorMessage}}\nerror stack:\n{{errorStack}}',
|
|
136
|
+
prettyErrorStackTemplate: ' • {{fileName}}\t{{method}}\n\t{{fileNameWithLine}}',
|
|
137
|
+
prettyErrorParentNamesSeparator: '',
|
|
138
|
+
prettyErrorLoggerNameDelimiter: '\t',
|
|
139
|
+
stylePrettyLogs: true,
|
|
140
|
+
minLevel: 2,
|
|
141
|
+
prettyLogTimeZone: 'local',
|
|
142
|
+
prettyLogStyles: {
|
|
143
|
+
logLevelName: {
|
|
144
|
+
'*': ['bold', 'black', 'bgWhiteBright', 'dim'],
|
|
145
|
+
SILLY: ['bold', 'white'],
|
|
146
|
+
TRACE: ['bold', 'whiteBright'],
|
|
147
|
+
DEBUG: ['bold', 'green'],
|
|
148
|
+
INFO: ['bold', 'blue'],
|
|
149
|
+
WARN: ['bold', 'yellow'],
|
|
150
|
+
ERROR: ['bold', 'red'],
|
|
151
|
+
FATAL: ['bold', 'redBright'],
|
|
162
152
|
},
|
|
163
|
-
dateIsoStr: 'gray',
|
|
164
|
-
filePathWithLine: 'white',
|
|
165
|
-
name: 'green',
|
|
166
|
-
nameWithDelimiterPrefix: ['white', 'bold'],
|
|
167
|
-
nameWithDelimiterSuffix: ['white', 'bold'],
|
|
168
|
-
errorName: ['bold', 'bgRedBright', 'whiteBright'],
|
|
169
|
-
fileName: ['yellow'],
|
|
153
|
+
dateIsoStr: 'gray',
|
|
154
|
+
filePathWithLine: 'white',
|
|
155
|
+
name: 'green',
|
|
156
|
+
nameWithDelimiterPrefix: ['white', 'bold'],
|
|
157
|
+
nameWithDelimiterSuffix: ['white', 'bold'],
|
|
158
|
+
errorName: ['bold', 'bgRedBright', 'whiteBright'],
|
|
159
|
+
fileName: ['yellow'],
|
|
170
160
|
},
|
|
171
|
-
maskValuesOfKeys: [
|
|
161
|
+
maskValuesOfKeys: [
|
|
172
162
|
'username',
|
|
173
163
|
'password',
|
|
174
164
|
'token',
|
|
@@ -248,11 +238,7 @@ class UiServer extends HomebridgePluginUiServer {
|
|
|
248
238
|
return { ok: true };
|
|
249
239
|
}
|
|
250
240
|
|
|
251
|
-
/**
|
|
252
|
-
* Load valid country codes from the shared countries.js file.
|
|
253
|
-
* Parsed lazily and cached for subsequent calls.
|
|
254
|
-
* @returns {Set<string>}
|
|
255
|
-
*/
|
|
241
|
+
/** Lazily load and cache valid ISO 3166-1 alpha-2 country codes from countries.js. */
|
|
256
242
|
_getValidCountryCodes() {
|
|
257
243
|
if (!this._validCountryCodes) {
|
|
258
244
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
@@ -310,9 +296,7 @@ class UiServer extends HomebridgePluginUiServer {
|
|
|
310
296
|
}
|
|
311
297
|
|
|
312
298
|
async login(options) {
|
|
313
|
-
//
|
|
314
|
-
// If the plugin is running (accessories.json updated within the last 90s),
|
|
315
|
-
// block login to prevent a competing eufy-security-client instance.
|
|
299
|
+
// Block login if the plugin is already running (accessories updated within 90s)
|
|
316
300
|
if (!this.eufyClient) {
|
|
317
301
|
try {
|
|
318
302
|
if (fs.existsSync(this.storedAccessories_file)) {
|
|
@@ -336,6 +320,13 @@ class UiServer extends HomebridgePluginUiServer {
|
|
|
336
320
|
try {
|
|
337
321
|
if (options && options.username && options.password && !options.reconnect) {
|
|
338
322
|
this.log.info('deleting persistent.json and accessories due to new login');
|
|
323
|
+
// Tear down any existing client so the new device name and credentials take effect
|
|
324
|
+
if (this.eufyClient) {
|
|
325
|
+
this.log.debug('Tearing down previous eufy client before fresh login');
|
|
326
|
+
this.eufyClient.removeAllListeners();
|
|
327
|
+
this.eufyClient.close();
|
|
328
|
+
this.eufyClient = null;
|
|
329
|
+
}
|
|
339
330
|
await this.resetAccessoryData();
|
|
340
331
|
await this.resetPersistentData();
|
|
341
332
|
} else if (options && options.reconnect) {
|
|
@@ -346,22 +337,14 @@ class UiServer extends HomebridgePluginUiServer {
|
|
|
346
337
|
}
|
|
347
338
|
|
|
348
339
|
if (!this.eufyClient && options && options.username && options.password && options.country) {
|
|
349
|
-
|
|
350
|
-
if (this.processingTimeout) {
|
|
351
|
-
clearTimeout(this.processingTimeout);
|
|
352
|
-
this.processingTimeout = null;
|
|
353
|
-
}
|
|
354
|
-
if (this._closeTimeout) {
|
|
355
|
-
clearTimeout(this._closeTimeout);
|
|
356
|
-
this._closeTimeout = null;
|
|
357
|
-
}
|
|
340
|
+
this._clearAllTimers();
|
|
358
341
|
this.stations = [];
|
|
359
342
|
this.pendingStations = [];
|
|
360
343
|
this.pendingDevices = [];
|
|
361
344
|
this._discoveryPhase = 'authenticating';
|
|
362
345
|
this.log.debug('init eufyClient');
|
|
363
346
|
|
|
364
|
-
// Validate country code
|
|
347
|
+
// Validate country code
|
|
365
348
|
const country = typeof options.country === 'string' ? options.country.trim().toUpperCase() : '';
|
|
366
349
|
if (!this._getValidCountryCodes().has(country)) {
|
|
367
350
|
const raw = typeof options.country === 'object' ? JSON.stringify(options.country) : String(options.country);
|
|
@@ -374,11 +357,11 @@ class UiServer extends HomebridgePluginUiServer {
|
|
|
374
357
|
this.config.username = options.username;
|
|
375
358
|
this.config.password = options.password;
|
|
376
359
|
this.config.country = country;
|
|
377
|
-
this.config.trustedDeviceName = options.deviceName;
|
|
360
|
+
this.config.trustedDeviceName = (typeof options.deviceName === 'string' && options.deviceName.trim()) || this.config.trustedDeviceName;
|
|
378
361
|
try {
|
|
379
362
|
this.eufyClient = await EufySecurity.initialize(this.config, this.tsLog);
|
|
380
|
-
this.eufyClient?.on('station added', this.
|
|
381
|
-
this.eufyClient?.on('device added', this.
|
|
363
|
+
this.eufyClient?.on('station added', this._onStationDiscovered.bind(this));
|
|
364
|
+
this.eufyClient?.on('device added', this._onDeviceDiscovered.bind(this));
|
|
382
365
|
this.eufyClient?.on('push connect', () => this.log.debug('Push Connected!'));
|
|
383
366
|
this.eufyClient?.on('push close', () => this.log.debug('Push Closed!'));
|
|
384
367
|
this.eufyClient?.on('connect', () => this.log.debug('Connected!'));
|
|
@@ -399,7 +382,7 @@ class UiServer extends HomebridgePluginUiServer {
|
|
|
399
382
|
if (options && options.username && options.password && options.country) {
|
|
400
383
|
this.log.debug('login with credentials');
|
|
401
384
|
try {
|
|
402
|
-
this.
|
|
385
|
+
this._registerOneTimeAuthHandlers();
|
|
403
386
|
this.eufyClient?.connect()
|
|
404
387
|
.then(() => this.log.debug('connected?: ' + this.eufyClient?.isConnected()))
|
|
405
388
|
.catch((error) => this.log.error(error));
|
|
@@ -416,7 +399,7 @@ class UiServer extends HomebridgePluginUiServer {
|
|
|
416
399
|
message: 'Verifying TFA code...',
|
|
417
400
|
});
|
|
418
401
|
try {
|
|
419
|
-
this.
|
|
402
|
+
this._registerOneTimeAuthHandlers();
|
|
420
403
|
this.eufyClient?.connect({ verifyCode: options.verifyCode, force: false })
|
|
421
404
|
.then(() => this.log.debug('TFA connect resolved, connected?: ' + this.eufyClient?.isConnected()))
|
|
422
405
|
.catch((error) => {
|
|
@@ -436,7 +419,7 @@ class UiServer extends HomebridgePluginUiServer {
|
|
|
436
419
|
message: 'Verifying captcha...',
|
|
437
420
|
});
|
|
438
421
|
try {
|
|
439
|
-
this.
|
|
422
|
+
this._registerOneTimeAuthHandlers();
|
|
440
423
|
this.eufyClient?.connect({ captcha: { captchaCode: options.captcha.captchaCode, captchaId: options.captcha.captchaId }, force: false })
|
|
441
424
|
.then(() => this.log.debug('Captcha connect resolved, connected?: ' + this.eufyClient?.isConnected()))
|
|
442
425
|
.catch((error) => {
|
|
@@ -457,11 +440,8 @@ class UiServer extends HomebridgePluginUiServer {
|
|
|
457
440
|
return { pending: true };
|
|
458
441
|
}
|
|
459
442
|
|
|
460
|
-
/**
|
|
461
|
-
|
|
462
|
-
* All outcomes are delivered to the UI via push events.
|
|
463
|
-
*/
|
|
464
|
-
_registerAuthHandlers() {
|
|
443
|
+
/** Register once-only auth event handlers (TFA, captcha, connect) on the eufy client. */
|
|
444
|
+
_registerOneTimeAuthHandlers() {
|
|
465
445
|
this.eufyClient?.once('tfa request', () => {
|
|
466
446
|
clearTimeout(this._loginTimeout);
|
|
467
447
|
this.pushEvent('tfaRequest', {});
|
|
@@ -472,32 +452,29 @@ class UiServer extends HomebridgePluginUiServer {
|
|
|
472
452
|
});
|
|
473
453
|
this.eufyClient?.once('connect', () => {
|
|
474
454
|
clearTimeout(this._loginTimeout);
|
|
455
|
+
if (this.adminAccountUsed) {
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
475
458
|
this.pushEvent('authSuccess', {});
|
|
476
459
|
this.pushEvent('discoveryProgress', {
|
|
477
460
|
phase: 'authenticating',
|
|
478
461
|
progress: 15,
|
|
479
462
|
message: 'Authenticated — waiting for devices...',
|
|
480
463
|
});
|
|
481
|
-
this.
|
|
464
|
+
this._startDiscoveryInactivityTimer();
|
|
482
465
|
});
|
|
483
466
|
}
|
|
484
467
|
|
|
485
|
-
/**
|
|
486
|
-
|
|
487
|
-
* If no station or device is discovered within DISCOVERY_INACTIVITY_SEC seconds
|
|
488
|
-
* after authentication, save the account and send an empty result to the UI.
|
|
489
|
-
*/
|
|
490
|
-
_startDiscoveryInactivityTimeout() {
|
|
491
|
-
// If stations or devices were already discovered before connect fired, skip
|
|
468
|
+
/** Start a timer that gives up on device discovery after DISCOVERY_INACTIVITY_SEC seconds. */
|
|
469
|
+
_startDiscoveryInactivityTimer() {
|
|
492
470
|
if (this.pendingStations.length > 0 || this.pendingDevices.length > 0) {
|
|
493
471
|
this.log.debug('Devices already discovered before connect event — skipping inactivity timeout');
|
|
494
472
|
return;
|
|
495
473
|
}
|
|
496
|
-
this.
|
|
474
|
+
this._clearDiscoveryInactivityTimer();
|
|
497
475
|
const totalSec = UiServer.DISCOVERY_INACTIVITY_SEC;
|
|
498
476
|
const start = Date.now();
|
|
499
477
|
|
|
500
|
-
// Tick every second: progress 15 → 95 during the wait, with countdown
|
|
501
478
|
this._discoveryInactivityTickInterval = setInterval(() => {
|
|
502
479
|
const elapsed = Math.floor((Date.now() - start) / 1000);
|
|
503
480
|
const remaining = Math.max(0, totalSec - elapsed);
|
|
@@ -534,10 +511,52 @@ class UiServer extends HomebridgePluginUiServer {
|
|
|
534
511
|
}, totalSec * 1000);
|
|
535
512
|
}
|
|
536
513
|
|
|
537
|
-
/**
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
514
|
+
/** Return true if the station's account is the main admin (not a guest admin). */
|
|
515
|
+
_isMainAdminAccount(station) {
|
|
516
|
+
const rawStation = station.getRawStation();
|
|
517
|
+
return rawStation.member.member_type !== UserType.ADMIN;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
/** Abort login: tear down client, cancel timers, notify UI, and reset plugin storage. */
|
|
521
|
+
_abortNonGuestAdminLogin() {
|
|
522
|
+
this.adminAccountUsed = true;
|
|
523
|
+
this._clearAllTimers();
|
|
524
|
+
this.eufyClient?.removeAllListeners();
|
|
525
|
+
this.eufyClient?.close();
|
|
526
|
+
this.pushEvent('AdminAccountUsed', true);
|
|
527
|
+
this.resetPlugin();
|
|
528
|
+
this.log.error(`
|
|
529
|
+
#########################
|
|
530
|
+
######### ERROR #########
|
|
531
|
+
#########################
|
|
532
|
+
You're not using a guest admin account with this plugin! You must use a guest admin account!
|
|
533
|
+
Please look here for more details:
|
|
534
|
+
https://github.com/homebridge-plugins/homebridge-eufy-security/wiki/Create-a-dedicated-admin-account-for-Homebridge-Eufy-Security-Plugin
|
|
535
|
+
#########################
|
|
536
|
+
`);
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
/** Clear all pending timers (login, processing, close, debounce tick, discovery inactivity). */
|
|
540
|
+
_clearAllTimers() {
|
|
541
|
+
clearTimeout(this._loginTimeout);
|
|
542
|
+
this._loginTimeout = null;
|
|
543
|
+
if (this.processingTimeout) {
|
|
544
|
+
clearTimeout(this.processingTimeout);
|
|
545
|
+
this.processingTimeout = null;
|
|
546
|
+
}
|
|
547
|
+
if (this._closeTimeout) {
|
|
548
|
+
clearTimeout(this._closeTimeout);
|
|
549
|
+
this._closeTimeout = null;
|
|
550
|
+
}
|
|
551
|
+
if (this._debounceTickInterval) {
|
|
552
|
+
clearInterval(this._debounceTickInterval);
|
|
553
|
+
this._debounceTickInterval = null;
|
|
554
|
+
}
|
|
555
|
+
this._clearDiscoveryInactivityTimer();
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
/** Clear the post-auth discovery inactivity timer. */
|
|
559
|
+
_clearDiscoveryInactivityTimer() {
|
|
541
560
|
if (this._discoveryInactivityTickInterval) {
|
|
542
561
|
clearInterval(this._discoveryInactivityTickInterval);
|
|
543
562
|
this._discoveryInactivityTickInterval = null;
|
|
@@ -548,11 +567,7 @@ class UiServer extends HomebridgePluginUiServer {
|
|
|
548
567
|
}
|
|
549
568
|
}
|
|
550
569
|
|
|
551
|
-
/**
|
|
552
|
-
* Parse a semver string into [major, minor, patch].
|
|
553
|
-
* @param {string} ver - e.g. '4.4.2-beta.18'
|
|
554
|
-
* @returns {number[]}
|
|
555
|
-
*/
|
|
570
|
+
/** Parse a semver string (e.g. '4.4.2-beta.18') into [major, minor, patch]. */
|
|
556
571
|
_parseSemver(ver) {
|
|
557
572
|
return (ver || '0.0.0').replace(/-.*$/, '').split('.').map(Number);
|
|
558
573
|
}
|
|
@@ -613,27 +628,17 @@ class UiServer extends HomebridgePluginUiServer {
|
|
|
613
628
|
return new Promise(resolve => setTimeout(resolve, ms));
|
|
614
629
|
}
|
|
615
630
|
|
|
616
|
-
async
|
|
617
|
-
|
|
618
|
-
const rawStation = station.getRawStation();
|
|
619
|
-
if (rawStation.member.member_type !== UserType.ADMIN) {
|
|
620
|
-
this.adminAccountUsed = true;
|
|
621
|
-
this.eufyClient?.close();
|
|
622
|
-
this.pushEvent('AdminAccountUsed', true);
|
|
623
|
-
this.resetPlugin();
|
|
624
|
-
this.log.error(`
|
|
625
|
-
#########################
|
|
626
|
-
######### ERROR #########
|
|
627
|
-
#########################
|
|
628
|
-
You're not using a guest admin account with this plugin! You must use a guest admin account!
|
|
629
|
-
Please look here for more details:
|
|
630
|
-
https://github.com/homebridge-plugins/homebridge-eufy-security/wiki/Create-a-dedicated-admin-account-for-Homebridge-Eufy-Security-Plugin
|
|
631
|
-
#########################
|
|
632
|
-
`);
|
|
631
|
+
async _onStationDiscovered(station) {
|
|
632
|
+
if (this.adminAccountUsed) {
|
|
633
633
|
return;
|
|
634
634
|
}
|
|
635
635
|
|
|
636
|
-
this.
|
|
636
|
+
if (this._isMainAdminAccount(station)) {
|
|
637
|
+
this._abortNonGuestAdminLogin();
|
|
638
|
+
return;
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
this._clearDiscoveryInactivityTimer();
|
|
637
642
|
this.pendingStations.push(station);
|
|
638
643
|
this.log.debug(`${station.getName()}: Station queued for processing`);
|
|
639
644
|
this._discoveryPhase = 'queuing';
|
|
@@ -644,10 +649,10 @@ class UiServer extends HomebridgePluginUiServer {
|
|
|
644
649
|
devices: this.pendingDevices.length,
|
|
645
650
|
message: `Discovered ${this.pendingStations.length} station(s), ${this.pendingDevices.length} device(s)...`,
|
|
646
651
|
});
|
|
647
|
-
this.
|
|
652
|
+
this._restartDiscoveryDebounce();
|
|
648
653
|
}
|
|
649
654
|
|
|
650
|
-
async
|
|
655
|
+
async _onDeviceDiscovered(device) {
|
|
651
656
|
if (this.adminAccountUsed) {
|
|
652
657
|
this.pushEvent('AdminAccountUsed', true);
|
|
653
658
|
return;
|
|
@@ -659,7 +664,7 @@ class UiServer extends HomebridgePluginUiServer {
|
|
|
659
664
|
return;
|
|
660
665
|
}
|
|
661
666
|
|
|
662
|
-
this.
|
|
667
|
+
this._clearDiscoveryInactivityTimer();
|
|
663
668
|
this.pendingDevices.push(device);
|
|
664
669
|
this.log.debug(`${device.getName()}: Device queued for processing`);
|
|
665
670
|
this._discoveryPhase = 'queuing';
|
|
@@ -670,24 +675,12 @@ class UiServer extends HomebridgePluginUiServer {
|
|
|
670
675
|
devices: this.pendingDevices.length,
|
|
671
676
|
message: `Discovered ${this.pendingStations.length} station(s), ${this.pendingDevices.length} device(s)...`,
|
|
672
677
|
});
|
|
673
|
-
this.
|
|
678
|
+
this._restartDiscoveryDebounce();
|
|
674
679
|
}
|
|
675
680
|
|
|
676
|
-
/**
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
* Processing begins once no new events arrive for DISCOVERY_DEBOUNCE_SEC seconds.
|
|
680
|
-
*/
|
|
681
|
-
resetDiscoveryDebounce() {
|
|
682
|
-
if (this.processingTimeout) {
|
|
683
|
-
clearTimeout(this.processingTimeout);
|
|
684
|
-
}
|
|
685
|
-
if (this._closeTimeout) {
|
|
686
|
-
clearTimeout(this._closeTimeout);
|
|
687
|
-
}
|
|
688
|
-
if (this._debounceTickInterval) {
|
|
689
|
-
clearInterval(this._debounceTickInterval);
|
|
690
|
-
}
|
|
681
|
+
/** Restart the debounce timer — processing fires after DISCOVERY_DEBOUNCE_SEC of silence. */
|
|
682
|
+
_restartDiscoveryDebounce() {
|
|
683
|
+
this._clearAllTimers();
|
|
691
684
|
const delaySec = UiServer.DISCOVERY_DEBOUNCE_SEC;
|
|
692
685
|
this.log.debug(
|
|
693
686
|
`Discovery debounce reset — will process in ${delaySec}s if no more devices arrive ` +
|
|
@@ -712,7 +705,7 @@ class UiServer extends HomebridgePluginUiServer {
|
|
|
712
705
|
this.processingTimeout = setTimeout(() => {
|
|
713
706
|
clearInterval(this._debounceTickInterval);
|
|
714
707
|
this._debounceTickInterval = null;
|
|
715
|
-
this.
|
|
708
|
+
this._processPendingAccessories().catch(error => this.log.error('Error processing pending accessories:', error));
|
|
716
709
|
}, delaySec * 1000);
|
|
717
710
|
// Close connection after processing + potential 2-min unsupported intel wait
|
|
718
711
|
const closeAfterSec = delaySec + (UNSUPPORTED_INTEL_WAIT_MS / 1000) + 15;
|
|
@@ -722,7 +715,8 @@ class UiServer extends HomebridgePluginUiServer {
|
|
|
722
715
|
}, closeAfterSec * 1000);
|
|
723
716
|
}
|
|
724
717
|
|
|
725
|
-
|
|
718
|
+
/** Process all queued stations and devices after the debounce window closes. */
|
|
719
|
+
async _processPendingAccessories() {
|
|
726
720
|
this.log.debug(`Processing ${this.pendingStations.length} stations and ${this.pendingDevices.length} devices`);
|
|
727
721
|
|
|
728
722
|
this._discoveryPhase = 'processing';
|
|
@@ -741,9 +735,7 @@ class UiServer extends HomebridgePluginUiServer {
|
|
|
741
735
|
);
|
|
742
736
|
}
|
|
743
737
|
|
|
744
|
-
//
|
|
745
|
-
// Hub/base stations (type 0, HB3, etc.) are not in DeviceProperties so
|
|
746
|
-
// Device.isSupported() returns false for them — exclude known station types.
|
|
738
|
+
// Collect unsupported items (exclude hub/base station types that aren't in DeviceProperties)
|
|
747
739
|
const unsupportedItems = [];
|
|
748
740
|
|
|
749
741
|
for (const station of this.pendingStations) {
|
|
@@ -758,7 +750,7 @@ class UiServer extends HomebridgePluginUiServer {
|
|
|
758
750
|
} catch (e) { /* ignore */ }
|
|
759
751
|
}
|
|
760
752
|
|
|
761
|
-
//
|
|
753
|
+
// Wait for raw data on unsupported items (user can skip via /skipIntelWait)
|
|
762
754
|
if (unsupportedItems.length > 0) {
|
|
763
755
|
const names = unsupportedItems.map(i => `${i.getName()} (type ${i.getDeviceType()})`).join(', ');
|
|
764
756
|
this._skipIntelWait = false;
|
|
@@ -772,7 +764,7 @@ class UiServer extends HomebridgePluginUiServer {
|
|
|
772
764
|
|
|
773
765
|
this.log.info(`Unsupported intel: waiting up to ${UNSUPPORTED_INTEL_WAIT_MS / 1000}s for raw data (user can skip)`);
|
|
774
766
|
|
|
775
|
-
//
|
|
767
|
+
// Poll every second, ticking progress 50 → 95
|
|
776
768
|
const pollMs = 1000;
|
|
777
769
|
let waited = 0;
|
|
778
770
|
while (waited < UNSUPPORTED_INTEL_WAIT_MS && !this._skipIntelWait) {
|
|
@@ -962,6 +954,7 @@ class UiServer extends HomebridgePluginUiServer {
|
|
|
962
954
|
this.pushEvent('addAccessory', { stations: this.stations, extendedDiscovery: unsupportedItems.length > 0 });
|
|
963
955
|
}
|
|
964
956
|
|
|
957
|
+
/** Persist discovered stations/devices to accessories.json. */
|
|
965
958
|
storeAccessories() {
|
|
966
959
|
if (!fs.existsSync(this.storagePath)) {
|
|
967
960
|
fs.mkdirSync(this.storagePath, { recursive: true });
|
|
@@ -971,11 +964,8 @@ class UiServer extends HomebridgePluginUiServer {
|
|
|
971
964
|
}
|
|
972
965
|
|
|
973
966
|
// ── Sensitive-field redaction ──────────────────────────────────────────────
|
|
974
|
-
// Keys whose string values must be partially masked before persisting to
|
|
975
|
-
// unsupported.json. Values are [keepStart, keepEnd] — the number of
|
|
976
|
-
// characters to leave visible at the beginning and end of the string.
|
|
977
|
-
// An empty / falsy string is left as-is so we can tell the field is blank.
|
|
978
967
|
|
|
968
|
+
/** Keys whose string values are partially masked before persisting to unsupported.json. */
|
|
979
969
|
static SENSITIVE_KEYS = new Map([
|
|
980
970
|
// Serial numbers — keep model prefix (e.g. T8170)
|
|
981
971
|
['station_sn', [5, 0]],
|
|
@@ -1065,10 +1055,7 @@ class UiServer extends HomebridgePluginUiServer {
|
|
|
1065
1055
|
|
|
1066
1056
|
// ── Unsupported device storage ──────────────────────────────────────────
|
|
1067
1057
|
|
|
1068
|
-
/**
|
|
1069
|
-
* Collect raw intel for all unsupported devices/stations and write to unsupported.json.
|
|
1070
|
-
* This data is only used by the Plugin UI for triage and diagnostics.
|
|
1071
|
-
*/
|
|
1058
|
+
/** Collect raw intel for unsupported devices/stations and write to unsupported.json. */
|
|
1072
1059
|
storeUnsupportedDevices(pendingStations, pendingDevices) {
|
|
1073
1060
|
const unsupportedEntries = [];
|
|
1074
1061
|
|
|
@@ -1096,9 +1083,7 @@ class UiServer extends HomebridgePluginUiServer {
|
|
|
1096
1083
|
this.log.debug(`Persisted ${unsupportedEntries.length} unsupported device(s) to unsupported.json`);
|
|
1097
1084
|
}
|
|
1098
1085
|
|
|
1099
|
-
/**
|
|
1100
|
-
* Build a triage-ready intel object for an unsupported device.
|
|
1101
|
-
*/
|
|
1086
|
+
/** Build a triage-ready intel object for an unsupported device. */
|
|
1102
1087
|
_buildUnsupportedDeviceEntry(device) {
|
|
1103
1088
|
const rawDevice = device.getRawDevice ? device.getRawDevice() : {};
|
|
1104
1089
|
const rawProps = device.getRawProperties ? device.getRawProperties() : {};
|
|
@@ -1122,9 +1107,7 @@ class UiServer extends HomebridgePluginUiServer {
|
|
|
1122
1107
|
};
|
|
1123
1108
|
}
|
|
1124
1109
|
|
|
1125
|
-
/**
|
|
1126
|
-
* Build a triage-ready intel object for an unsupported standalone station.
|
|
1127
|
-
*/
|
|
1110
|
+
/** Build a triage-ready intel object for an unsupported standalone station. */
|
|
1128
1111
|
_buildUnsupportedStationEntry(station) {
|
|
1129
1112
|
const rawStation = station.getRawStation ? station.getRawStation() : {};
|
|
1130
1113
|
const rawProps = station.getRawProperties ? station.getRawProperties() : {};
|
|
@@ -1148,9 +1131,7 @@ class UiServer extends HomebridgePluginUiServer {
|
|
|
1148
1131
|
};
|
|
1149
1132
|
}
|
|
1150
1133
|
|
|
1151
|
-
/**
|
|
1152
|
-
* Load unsupported device intel from disk.
|
|
1153
|
-
*/
|
|
1134
|
+
/** Load unsupported device intel from disk. */
|
|
1154
1135
|
async loadUnsupportedDevices() {
|
|
1155
1136
|
try {
|
|
1156
1137
|
if (!fs.existsSync(this.unsupported_file)) {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"displayName": "Homebridge Eufy Security",
|
|
3
3
|
"name": "@homebridge-plugins/homebridge-eufy-security",
|
|
4
|
-
"version": "4.4.4-beta.
|
|
4
|
+
"version": "4.4.4-beta.11",
|
|
5
5
|
"description": "Control Eufy Security from homebridge.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"license": "Apache-2.0",
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|