@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.
@@ -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 || 'Unknowm',
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()] || 'Unknowm',
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 || 'Unknowm',
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() || 'Unknowm',
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() || 'Unknowm',
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(30);
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.logPropertyKeys();
122
+ this.logDeviceProperties();
93
123
  }
94
- // Function to extract and log keys
95
- logPropertyKeys() {
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
- // silently ignore errors during cache refresh
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
- Promise.resolve(setValue(value, characteristic, service));
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
- !safeServiceUUIDs.includes(service.UUID)) {
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;;;;;GAKG;AACH,SAAS,iBAAiB,CACxB,WAA+C;IAG/C,OAAO,OAAQ,WAAmB,KAAK,QAAQ,CAAC;AAClD,CAAC;AAKD,MAAM,OAAgB,aAAc,SAAQ,YAAY;IAsBpC;IACA;IAET;IAvBC,aAAa,CAAY;IACnB,EAAE,CAAS;IACX,IAAI,CAAS;IACb,GAAG,CAAkB;IAErC;;;;OAIG;IACK,iBAAiB,GAOnB,EAAE,CAAC;IAET,YACkB,QAA8B,EAC9B,SAA4B,EAErC,MAAW;QAElB,KAAK,EAAE,CAAC;QALQ,aAAQ,GAAR,QAAQ,CAAsB;QAC9B,cAAS,GAAT,SAAS,CAAmB;QAErC,WAAM,GAAN,MAAM,CAAK;QAIlB,IAAI,CAAC,MAAM,GAAG,MAA0B,CAAC;QAEzC,qEAAqE;QACrE,wEAAwE;QACxE,yEAAyE;QACzE,oCAAoC;QACpC,IAAI,CAAE,SAAiB,CAAC,cAAc,EAAE,CAAC;YACtC,SAAiB,CAAC,cAAc,GAAG,EAAE,CAAC;QACzC,CAAC;QACD,IAAI,CAAC,aAAa,GAAI,SAAiB,CAAC,cAAc,CAAC;QAEvD,IAAI,CAAC,EAAE,GAAG,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,CAAC;QAClC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QAElC,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC,YAAY,CAAC;YAC1B,IAAI,EAAE,EAAE;YACR,MAAM,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC;SACpB,CAAC,CAAC;QAEH,IAAI,CAAC,sBAAsB,CAAC;YAC1B,WAAW,EAAE,IAAI,CAAC,oBAAoB;YACtC,kBAAkB,EAAE,IAAI,CAAC,YAAY;YACrC,QAAQ,EAAE,GAAG,EAAE,CAAC,MAAM;SACvB,CAAC,CAAC;QACH,IAAI,CAAC,sBAAsB,CAAC;YAC1B,WAAW,EAAE,IAAI,CAAC,oBAAoB;YACtC,kBAAkB,EAAE,IAAI,CAAC,IAAI;YAC7B,QAAQ,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,IAAI,IAAI,SAAS;SACvC,CAAC,CAAC;QACH,IAAI,CAAC,sBAAsB,CAAC;YAC1B,WAAW,EAAE,IAAI,CAAC,oBAAoB;YACtC,kBAAkB,EAAE,IAAI,CAAC,KAAK;YAC9B,QAAQ,EAAE,GAAG,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,aAAa,EAAE,CAAC,IAAI,SAAS;SACrE,CAAC,CAAC;QACH,IAAI,CAAC,sBAAsB,CAAC;YAC1B,WAAW,EAAE,IAAI,CAAC,oBAAoB;YACtC,kBAAkB,EAAE,IAAI,CAAC,YAAY;YACrC,QAAQ,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,EAAE,IAAI,SAAS;SACrC,CAAC,CAAC;QACH,IAAI,CAAC,sBAAsB,CAAC;YAC1B,WAAW,EAAE,IAAI,CAAC,oBAAoB;YACtC,kBAAkB,EAAE,IAAI,CAAC,gBAAgB;YACzC,QAAQ,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,kBAAkB,EAAE,IAAI,SAAS;SAC9D,CAAC,CAAC;QACH,IAAI,CAAC,sBAAsB,CAAC;YAC1B,WAAW,EAAE,IAAI,CAAC,oBAAoB;YACtC,kBAAkB,EAAE,IAAI,CAAC,gBAAgB;YACzC,QAAQ,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,kBAAkB,EAAE,IAAI,SAAS;SAC9D,CAAC,CAAC;QAEH,sFAAsF;QACtF,kEAAkE;QAClE,IAAI,OAAO,IAAI,CAAC,MAAM,CAAC,eAAe,KAAK,UAAU,EAAE,CAAC;YACtD,IAAI,CAAC,MAAM,CAAC,eAAe,CAAC,EAAE,CAAC,CAAC;QAClC,CAAC;QAED,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,qBAAqB,EAAE,CAAC;YAC/C,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,sBAAsB,EAAE,IAAI,CAAC,uBAAuB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;YAChF,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,kBAAkB,EAAE,IAAI,CAAC,oBAAoB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;QAC3E,CAAC;QAED,sEAAsE;QACtE,oEAAoE;QACpE,mDAAmD;QACnD,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,kBAAkB,EAAE,IAAI,CAAC,mBAAmB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;QAExE,IAAI,CAAC,eAAe,EAAE,CAAC;IACzB,CAAC;IAED,mCAAmC;IAC3B,eAAe;QACrB,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,gBAAgB,EAAE,IAAI,CAAC,MAAM,CAAC,aAAa,EAAE,CAAC,CAAC;IAChE,CAAC;IAED;;;;OAIG;IACK,mBAAmB;QACzB,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,iBAAiB,EAAE,CAAC;YACzC,IAAI,CAAC;gBACH,MAAM,QAAQ,GAAG,GAAG,CAAC,QAAQ,CAAC,SAAS,EAAE,GAAG,CAAC,cAAc,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC;gBAC1E,IAAI,QAAQ,KAAK,GAAG,CAAC,SAAS,EAAE,CAAC;oBAC/B,GAAG,CAAC,SAAS,GAAG,QAAQ,CAAC;oBACzB,GAAG,CAAC,cAAc,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC;oBACzC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,UAAU,GAAG,CAAC,eAAe,MAAM,GAAG,CAAC,sBAAsB,IAAI,EAAE,QAAQ,CAAC,CAAC;gBAC9F,CAAC;YACH,CAAC;YAAC,MAAM,CAAC;gBACP,8CAA8C;YAChD,CAAC;QACH,CAAC;IACH,CAAC;IAGS,uBAAuB,CAAC,MAAW,EAAE,IAAY,EAAE,KAAa;QACxE,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,uBAAuB,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC;IACvD,CAAC;IAGS,oBAAoB,CAAC,MAAW,EAAE,IAAY,EAAE,KAAoB;QAC5E,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,mBAAmB,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC;IACnD,CAAC;IAED;;;;;;;OAOG;IACO,sBAAsB,CAAC,EAC/B,kBAAkB,EAClB,WAAW,EACX,QAAQ,EACR,QAAQ,EACR,OAAO,EACP,aAAa,EACb,eAAe,EACf,IAAI,EACJ,cAAc,EACd,oBAAoB,GAAG,CAAC,GAgBzB;QAEC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,2BAA2B,WAAW,CAAC,IAAI,MAAM,kBAAkB,CAAC,IAAI,MAAM,IAAI,EAAE,CAAC,CAAC;QAErG,MAAM,OAAO,GAAG,IAAI,CAAC,UAAU,CAAC,WAAW,EAAE,IAAI,EAAE,cAAc,CAAC,CAAC;QACnE,MAAM,cAAc,GAAG,OAAO,CAAC,iBAAiB,CAAC,kBAAkB,CAAC,CAAC;QAErE,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,4BAA4B,OAAO,CAAC,IAAI,QAAQ,cAAc,CAAC,IAAI,GAAG,CAAC,CAAC;QAEvF,IAAI,QAAQ,EAAE,CAAC;YACb,4DAA4D;YAC5D,iEAAiE;YACjE,sDAAsD;YACtD,iEAAiE;YACjE,uCAAuC;YACvC,IAAI,YAAiB,CAAC;YACtB,IAAI,CAAC;gBACH,YAAY,GAAG,QAAQ,CAAC,SAAS,EAAE,cAAc,EAAE,OAAO,CAAC,CAAC;gBAC5D,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,SAAS,WAAW,CAAC,IAAI,MAAM,kBAAkB,CAAC,IAAI,IAAI,EAAE,YAAY,CAAC,CAAC;YAC3F,CAAC;YAAC,OAAO,CAAC,EAAE,CAAC;gBACX,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,cAAc,WAAW,CAAC,IAAI,MAAM,kBAAkB,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,CAAC;YACrF,CAAC;YAED,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC;gBAC1B,QAAQ;gBACR,cAAc;gBACd,OAAO;gBACP,eAAe,EAAE,WAAW,CAAC,IAAI,IAAI,SAAS;gBAC9C,sBAAsB,EAAE,kBAAkB,CAAC,IAAI,IAAI,SAAS;gBAC5D,SAAS,EAAE,YAAY;aACxB,CAAC,CAAC;YAEH,IAAI,YAAY,KAAK,SAAS,IAAI,YAAY,KAAK,IAAI,EAAE,CAAC;gBACxD,cAAc,CAAC,WAAW,CAAC,YAAY,CAAC,CAAC;YAC3C,CAAC;QACH,CAAC;QAED,IAAI,QAAQ,IAAI,oBAAoB,EAAE,CAAC;YAErC,IAAI,SAAS,GAA0B,IAAI,CAAC;YAE5C,cAAc,CAAC,KAAK,CAAC,KAAK,EAAE,KAA0B,EAAE,EAAE;gBACxD,IAAI,SAAS,EAAE,CAAC;oBACd,YAAY,CAAC,SAAS,CAAC,CAAC;gBAC1B,CAAC;gBAED,SAAS,GAAG,UAAU,CAAC,GAAG,EAAE;oBAC1B,SAAS,GAAG,IAAI,CAAC;oBACjB,QAAQ,CAAC,KAAK,EAAE,cAAc,EAAE,OAAO,CAAC,CAAC;gBAC3C,CAAC,EAAE,oBAAoB,CAAC,CAAC;YAC3B,CAAC,CAAC,CAAC;QAEL,CAAC;aAAM,IAAI,QAAQ,EAAE,CAAC;YACpB,cAAc,CAAC,KAAK,CAAC,KAAK,EAAE,KAA0B,EAAE,EAAE;gBACxD,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,KAAK,EAAE,cAAc,EAAE,OAAO,CAAC,CAAC,CAAC;YAC5D,CAAC,CAAC,CAAC;QACL,CAAC;QAED,IAAI,aAAa,EAAE,CAAC;YAElB,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,aAAa,EAAE,CAAC,MAAW,EAAE,KAAU,EAAE,EAAE;gBACxD,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,WAAW,CAAC,IAAI,MAAM,kBAAkB,CAAC,IAAI,MAAM,aAAa,IAAI,EAAE,KAAK,CAAC,CAAC;gBAClG,cAAc,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;YACpC,CAAC,CAAC,CAAC;QACL,CAAC;QAED,IAAI,OAAO,EAAE,CAAC;YACZ,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,IAAI,MAAM,kBAAkB,CAAC,IAAI,GAAG,CAAC,CAAC;YACxE,OAAO,CAAC,OAAO,EAAE,cAAc,CAAC,CAAC;QACnC,CAAC;QAED,IAAI,eAAe,EAAE,CAAC;YACpB,qDAAqD;YACrD,eAAe,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE;gBAElC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,SAAsB,EAAE,CAAC,MAAW,EAAE,KAAU,EAAE,EAAE;oBACjE,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,WAAW,CAAC,IAAI,MAAM,kBAAkB,CAAC,IAAI,MAAM,SAAS,IAAI,EAAE,KAAK,CAAC,CAAC;oBAC9F,cAAc,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;gBACpC,CAAC,CAAC,CAAC;YACL,CAAC,CAAC,CAAC;QACL,CAAC;IAEH,CAAC;IAED;;;;;;;;OAQG;IACI,UAAU,CACf,WAAwB,EACxB,IAAI,GAAG,IAAI,CAAC,IAAI,EAChB,OAAgB;QAGhB,IAAI,iBAAiB,CAAC,WAAW,CAAC,EAAE,CAAC;YACnC,OAAO,WAAW,CAAC;QACrB,CAAC;QAED,MAAM,eAAe,GAAG,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,cAAc,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC;QAE/H,MAAM,OAAO,GAAG,eAAe;YAC7B,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,WAAW,EAAE,IAAI,EAAE,OAAQ,CAAC,CAAC;QAEzD,IACE,eAAe;YACf,eAAe,CAAC,WAAW;YAC3B,IAAI,KAAK,eAAe,CAAC,WAAW,EACpC,CAAC;YACD,MAAM,IAAI,KAAK,CACb,mCAAmC,IAAI,CAAC,IAAI,MAAM,IAAI,OAAO,eAAe,CAAC,WAAW,MAAM,WAAW,EAAE,CAC5G,CAAC;QACJ,CAAC;QAED,IAAI,CAAC,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;YAC1C,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACnC,CAAC;QAED,OAAO,OAAO,CAAC;IACjB,CAAC;IAES,mBAAmB;QAC3B,6DAA6D;QAC7D,4EAA4E;QAC5E,6CAA6C;QAC7C,MAAM,gBAAgB,GAAG;YACvB,IAAI,CAAC,yBAAyB,CAAC,IAAI;YACnC,IAAI,CAAC,mBAAmB,CAAC,IAAI;YAC7B,IAAI,CAAC,yBAAyB,CAAC,IAAI;YACnC,IAAI,CAAC,6BAA6B,CAAC,IAAI;YACvC,IAAI,CAAC,UAAU,CAAC,IAAI;YACpB,IAAI,CAAC,OAAO,CAAC,IAAI;SAClB,CAAC;QAEF,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;YAC1C,IACE,CAAC,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,OAAO,CAAC;gBACrC,CAAC,gBAAgB,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,EACxC,CAAC;gBACD,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,0BAA0B,OAAO,CAAC,IAAI,IAAI,OAAO,CAAC,WAAW,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC;gBAChG,IAAI,CAAC,SAAS,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;YACxC,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC;CAEF"}
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";
1
+ export const LIB_VERSION = "4.4.4-beta.11";
2
2
  //# sourceMappingURL=version.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"version.js","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,WAAW,GAAG,cAAc,CAAC"}
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 mt-4" role="alert">
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>.
@@ -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.jpg';
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.jpg';
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.jpg';
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.jpg';
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.jpg';
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
- <input type="text" class="form-control" id="login-device" value="" placeholder="e.g. My Homebridge">
233
- <div class="form-text">A name to identify this Homebridge instance to Eufy. Can be left blank.</div>
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 password.');
298
+ if (!email || !password || !deviceName) {
299
+ this._showError(body, 'Please enter your email, password, and device name.');
286
300
  return;
287
301
  }
288
302
 
@@ -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; // generated dynamically with timestamp
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}]`, // Name prefix for log messages
144
- prettyLogTemplate: '[{{mm}}/{{dd}}/{{yyyy}}, {{hh}}:{{MM}}:{{ss}}]\t{{name}}\t{{logLevelName}}\t', // Template for pretty log output
145
- prettyErrorTemplate: '\n{{errorName}} {{errorMessage}}\nerror stack:\n{{errorStack}}', // Template for pretty error output
146
- prettyErrorStackTemplate: ' • {{fileName}}\t{{method}}\n\t{{fileNameWithLine}}', // Template for error stack trace
147
- prettyErrorParentNamesSeparator: '', // Separator for parent names in error messages
148
- prettyErrorLoggerNameDelimiter: '\t', // Delimiter for logger name in error messages
149
- stylePrettyLogs: true, // Enable styling for logs
150
- minLevel: 2, // Minimum log level to display (3 corresponds to INFO)
151
- prettyLogTimeZone: 'local', // Time zone for log timestamps
152
- prettyLogStyles: { // Styles for different log elements
153
- logLevelName: { // Styles for log level names
154
- '*': ['bold', 'black', 'bgWhiteBright', 'dim'], // Default style
155
- SILLY: ['bold', 'white'], // Style for SILLY level
156
- TRACE: ['bold', 'whiteBright'], // Style for TRACE level
157
- DEBUG: ['bold', 'green'], // Style for DEBUG level
158
- INFO: ['bold', 'blue'], // Style for INFO level
159
- WARN: ['bold', 'yellow'], // Style for WARN level
160
- ERROR: ['bold', 'red'], // Style for ERROR level
161
- FATAL: ['bold', 'redBright'], // Style for FATAL level
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', // Style for ISO date strings
164
- filePathWithLine: 'white', // Style for file paths with line numbers
165
- name: 'green', // Style for logger names
166
- nameWithDelimiterPrefix: ['white', 'bold'], // Style for logger names with delimiter prefix
167
- nameWithDelimiterSuffix: ['white', 'bold'], // Style for logger names with delimiter suffix
168
- errorName: ['bold', 'bgRedBright', 'whiteBright'], // Style for error names
169
- fileName: ['yellow'], // Style for file names
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: [ // Keys whose values should be masked in logs
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
- // --- Plugin heartbeat safeguard ---
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
- // Clear any pending timeouts from a previous login attempt
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 against known list
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.addStation.bind(this));
381
- this.eufyClient?.on('device added', this.addDevice.bind(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._registerAuthHandlers();
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._registerAuthHandlers();
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._registerAuthHandlers();
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
- * Register one-time auth outcome handlers on the eufy client.
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._startDiscoveryInactivityTimeout();
464
+ this._startDiscoveryInactivityTimer();
482
465
  });
483
466
  }
484
467
 
485
- /**
486
- * Start the discovery inactivity timeout.
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._cancelDiscoveryInactivityTimeout();
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
- * Cancel the discovery inactivity timeout (called when a station or device is discovered).
539
- */
540
- _cancelDiscoveryInactivityTimeout() {
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 addStation(station) {
617
- // Check if creds are guest admin
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._cancelDiscoveryInactivityTimeout();
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.resetDiscoveryDebounce();
652
+ this._restartDiscoveryDebounce();
648
653
  }
649
654
 
650
- async addDevice(device) {
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._cancelDiscoveryInactivityTimeout();
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.resetDiscoveryDebounce();
678
+ this._restartDiscoveryDebounce();
674
679
  }
675
680
 
676
- /**
677
- * Resets the discovery debounce timer.
678
- * Each time a station or device is emitted, the timer restarts.
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.processPendingAccessories().catch(error => this.log.error('Error processing pending accessories:', error));
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
- async processPendingAccessories() {
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
- // --- Collect unsupported items (stations + devices) upfront ---
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
- // If unsupported items exist, notify UI and wait (user can skip via /skipIntelWait)
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
- // Cancellable wait — check _skipIntelWait every second, ticking progress 50 → 95
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.1",
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",