homebridge-eufy-security 4.4.2-beta.2 → 4.4.2-beta.20

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.
Files changed (147) hide show
  1. package/CHANGELOG.md +1 -1
  2. package/FUNDING.yml +1 -0
  3. package/README.md +14 -17
  4. package/dist/accessories/AutoSyncStationAccessory.js +26 -30
  5. package/dist/accessories/AutoSyncStationAccessory.js.map +1 -1
  6. package/dist/accessories/BaseAccessory.js +33 -46
  7. package/dist/accessories/BaseAccessory.js.map +1 -1
  8. package/dist/accessories/CameraAccessory.js +92 -167
  9. package/dist/accessories/CameraAccessory.js.map +1 -1
  10. package/dist/accessories/Device.js +11 -15
  11. package/dist/accessories/Device.js.map +1 -1
  12. package/dist/accessories/EntrySensorAccessory.js +7 -12
  13. package/dist/accessories/EntrySensorAccessory.js.map +1 -1
  14. package/dist/accessories/LockAccessory.js +20 -24
  15. package/dist/accessories/LockAccessory.js.map +1 -1
  16. package/dist/accessories/MotionSensorAccessory.js +7 -12
  17. package/dist/accessories/MotionSensorAccessory.js.map +1 -1
  18. package/dist/accessories/StationAccessory.js +40 -45
  19. package/dist/accessories/StationAccessory.js.map +1 -1
  20. package/dist/config.js +1 -4
  21. package/dist/config.js.map +1 -1
  22. package/dist/controller/LocalLivestreamManager.js +85 -89
  23. package/dist/controller/LocalLivestreamManager.js.map +1 -1
  24. package/dist/controller/recordingDelegate.js +116 -108
  25. package/dist/controller/recordingDelegate.js.map +1 -1
  26. package/dist/controller/snapshotDelegate.js +379 -0
  27. package/dist/controller/snapshotDelegate.js.map +1 -0
  28. package/dist/controller/streamingDelegate.js +202 -207
  29. package/dist/controller/streamingDelegate.js.map +1 -1
  30. package/dist/index.js +9 -7
  31. package/dist/index.js.map +1 -1
  32. package/dist/interfaces.js +1 -2
  33. package/dist/interfaces.js.map +1 -1
  34. package/dist/platform.js +145 -165
  35. package/dist/platform.js.map +1 -1
  36. package/dist/settings.js +26 -19
  37. package/dist/settings.js.map +1 -1
  38. package/dist/utils/Talkback.js +9 -13
  39. package/dist/utils/Talkback.js.map +1 -1
  40. package/dist/utils/configTypes.js +12 -7
  41. package/dist/utils/configTypes.js.map +1 -1
  42. package/dist/utils/ffmpeg.js +44 -52
  43. package/dist/utils/ffmpeg.js.map +1 -1
  44. package/dist/utils/interfaces.js +1 -5
  45. package/dist/utils/interfaces.js.map +1 -1
  46. package/dist/utils/utils.js +32 -134
  47. package/dist/utils/utils.js.map +1 -1
  48. package/dist/version.js +1 -4
  49. package/dist/version.js.map +1 -1
  50. package/eslint.config.mjs +25 -47
  51. package/homebridge-eufy-security.png +0 -0
  52. package/homebridge-ui/public/app.js +221 -0
  53. package/homebridge-ui/public/assets/devices/eufycam3pro_large.png +0 -0
  54. package/homebridge-ui/public/assets/devices/homebase3_large.png +0 -0
  55. package/homebridge-ui/public/assets/devices/homebasemini_large.jpg +0 -0
  56. package/homebridge-ui/public/assets/devices/indoorcamC210_large.png +0 -0
  57. package/homebridge-ui/public/assets/devices/indoorcamC220_large.png +0 -0
  58. package/homebridge-ui/public/assets/devices/indoorcamE30_large.png +0 -0
  59. package/homebridge-ui/public/assets/devices/solocamc35_large.png +0 -0
  60. package/homebridge-ui/public/assets/devices/solocame30_large.png +0 -0
  61. package/homebridge-ui/public/components/device-card.js +141 -0
  62. package/homebridge-ui/public/components/guard-modes.js +88 -0
  63. package/homebridge-ui/public/components/number-input.js +121 -0
  64. package/homebridge-ui/public/components/select.js +73 -0
  65. package/homebridge-ui/public/components/toggle.js +68 -0
  66. package/homebridge-ui/public/index.html +24 -14
  67. package/homebridge-ui/public/services/api.js +129 -0
  68. package/homebridge-ui/public/services/config.js +144 -0
  69. package/homebridge-ui/public/style.css +705 -0
  70. package/homebridge-ui/public/utils/countries.js +73 -0
  71. package/homebridge-ui/public/utils/device-images.js +69 -0
  72. package/homebridge-ui/public/utils/helpers.js +47 -0
  73. package/homebridge-ui/public/views/dashboard.js +211 -0
  74. package/homebridge-ui/public/views/device-detail.js +631 -0
  75. package/homebridge-ui/public/views/login.js +625 -0
  76. package/homebridge-ui/public/views/settings.js +389 -0
  77. package/homebridge-ui/public/views/unsupported-detail.js +216 -0
  78. package/homebridge-ui/server.js +567 -450
  79. package/package.json +19 -59
  80. package/dist/accessories/AutoSyncStationAccessory.d.ts +0 -42
  81. package/dist/accessories/AutoSyncStationAccessory.d.ts.map +0 -1
  82. package/dist/accessories/BaseAccessory.d.ts +0 -56
  83. package/dist/accessories/BaseAccessory.d.ts.map +0 -1
  84. package/dist/accessories/CameraAccessory.d.ts +0 -84
  85. package/dist/accessories/CameraAccessory.d.ts.map +0 -1
  86. package/dist/accessories/Device.d.ts +0 -19
  87. package/dist/accessories/Device.d.ts.map +0 -1
  88. package/dist/accessories/EntrySensorAccessory.d.ts +0 -25
  89. package/dist/accessories/EntrySensorAccessory.d.ts.map +0 -1
  90. package/dist/accessories/LockAccessory.d.ts +0 -45
  91. package/dist/accessories/LockAccessory.d.ts.map +0 -1
  92. package/dist/accessories/MotionSensorAccessory.d.ts +0 -25
  93. package/dist/accessories/MotionSensorAccessory.d.ts.map +0 -1
  94. package/dist/accessories/StationAccessory.d.ts +0 -86
  95. package/dist/accessories/StationAccessory.d.ts.map +0 -1
  96. package/dist/config.d.ts +0 -31
  97. package/dist/config.d.ts.map +0 -1
  98. package/dist/controller/LocalLivestreamManager.d.ts +0 -31
  99. package/dist/controller/LocalLivestreamManager.d.ts.map +0 -1
  100. package/dist/controller/SnapshotManager.d.ts +0 -64
  101. package/dist/controller/SnapshotManager.d.ts.map +0 -1
  102. package/dist/controller/SnapshotManager.js +0 -434
  103. package/dist/controller/SnapshotManager.js.map +0 -1
  104. package/dist/controller/recordingDelegate.d.ts +0 -27
  105. package/dist/controller/recordingDelegate.d.ts.map +0 -1
  106. package/dist/controller/streamingDelegate.d.ts +0 -52
  107. package/dist/controller/streamingDelegate.d.ts.map +0 -1
  108. package/dist/index.d.ts +0 -7
  109. package/dist/index.d.ts.map +0 -1
  110. package/dist/interfaces.d.ts +0 -15
  111. package/dist/interfaces.d.ts.map +0 -1
  112. package/dist/platform.d.ts +0 -94
  113. package/dist/platform.d.ts.map +0 -1
  114. package/dist/settings.d.ts +0 -23
  115. package/dist/settings.d.ts.map +0 -1
  116. package/dist/utils/Talkback.d.ts +0 -21
  117. package/dist/utils/Talkback.d.ts.map +0 -1
  118. package/dist/utils/configTypes.d.ts +0 -68
  119. package/dist/utils/configTypes.d.ts.map +0 -1
  120. package/dist/utils/ffmpeg.d.ts +0 -111
  121. package/dist/utils/ffmpeg.d.ts.map +0 -1
  122. package/dist/utils/interfaces.d.ts +0 -8
  123. package/dist/utils/interfaces.d.ts.map +0 -1
  124. package/dist/utils/utils.d.ts +0 -35
  125. package/dist/utils/utils.d.ts.map +0 -1
  126. package/dist/version.d.ts +0 -2
  127. package/dist/version.d.ts.map +0 -1
  128. package/homebridge-ui/configui/app/util/types.d.ts +0 -65
  129. package/homebridge-ui/configui/app/util/types.d.ts.map +0 -1
  130. package/homebridge-ui/configui/app/util/types.js +0 -18
  131. package/homebridge-ui/configui/app/util/types.js.map +0 -1
  132. package/homebridge-ui/public/3rdpartylicenses.txt +0 -561
  133. package/homebridge-ui/public/assets/devices/homebase2_large.jpg +0 -0
  134. package/homebridge-ui/public/assets/devices/homebase3_large.jpg +0 -0
  135. package/homebridge-ui/public/assets/images/homebridge-eufy-security.png +0 -0
  136. package/homebridge-ui/public/favicon.ico +0 -0
  137. package/homebridge-ui/public/main.f6423287e0411c87.js +0 -1
  138. package/homebridge-ui/public/polyfills.efa2f09ba3e49167.js +0 -1
  139. package/homebridge-ui/public/runtime.ccde331d62c423b3.js +0 -1
  140. package/homebridge-ui/public/styles.13e635bf7a488639.css +0 -5
  141. package/homebridge-ui/server.d.ts +0 -2
  142. package/homebridge-ui/server.d.ts.map +0 -1
  143. package/homebridge-ui/server.js.map +0 -1
  144. package/homebridge-ui/version.d.ts +0 -2
  145. package/homebridge-ui/version.d.ts.map +0 -1
  146. package/homebridge-ui/version.js +0 -5
  147. package/homebridge-ui/version.js.map +0 -1
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Toggle component — renders an on/off switch with label and optional help tooltip.
3
+ *
4
+ * Usage:
5
+ * Toggle.render(container, {
6
+ * id: 'enable-camera',
7
+ * label: 'Enable Camera',
8
+ * help: 'Show this camera in HomeKit',
9
+ * checked: true,
10
+ * onChange: (checked) => { ... }
11
+ * });
12
+ */
13
+ // eslint-disable-next-line no-unused-vars
14
+ const Toggle = {
15
+ /**
16
+ * @param {HTMLElement} container - parent element to append into
17
+ * @param {object} opts
18
+ * @param {string} opts.id - unique id for the input
19
+ * @param {string} opts.label - display label
20
+ * @param {string} [opts.help] - tooltip text
21
+ * @param {boolean} opts.checked - initial state
22
+ * @param {boolean} [opts.disabled] - disabled state
23
+ * @param {function} opts.onChange - callback(checked: boolean)
24
+ * @returns {HTMLElement}
25
+ */
26
+ render(container, opts) {
27
+ const row = document.createElement('div');
28
+ row.className = 'eufy-toggle';
29
+
30
+ const labelWrap = document.createElement('div');
31
+ labelWrap.className = 'eufy-toggle__label';
32
+
33
+ const labelEl = document.createElement('label');
34
+ labelEl.setAttribute('for', opts.id);
35
+ labelEl.textContent = opts.label;
36
+ labelWrap.appendChild(labelEl);
37
+
38
+ if (opts.help) {
39
+ const helpEl = document.createElement('span');
40
+ helpEl.className = 'eufy-toggle__help';
41
+ helpEl.textContent = '?';
42
+ helpEl.title = opts.help;
43
+ labelWrap.appendChild(helpEl);
44
+ }
45
+
46
+ const switchWrap = document.createElement('div');
47
+ switchWrap.className = 'form-check form-switch mb-0';
48
+
49
+ const input = document.createElement('input');
50
+ input.type = 'checkbox';
51
+ input.className = 'form-check-input';
52
+ input.id = opts.id;
53
+ input.checked = !!opts.checked;
54
+ input.disabled = !!opts.disabled;
55
+ input.role = 'switch';
56
+
57
+ input.addEventListener('change', () => {
58
+ if (opts.onChange) opts.onChange(input.checked);
59
+ });
60
+
61
+ switchWrap.appendChild(input);
62
+ row.appendChild(labelWrap);
63
+ row.appendChild(switchWrap);
64
+
65
+ if (container) container.appendChild(row);
66
+ return row;
67
+ },
68
+ };
@@ -1,16 +1,26 @@
1
- <!doctype html>
2
- <html lang="en">
1
+ <link rel="stylesheet" href="style.css">
3
2
 
4
- <head>
5
- <meta charset="utf-8">
6
- <title>ConfigUi</title>
7
- <base href="/api/plugins/settings-ui/homebridge-eufy-security/">
8
- <meta name="viewport" content="width=device-width, initial-scale=1">
9
- <link rel="icon" type="image/x-icon" href="favicon.ico">
10
- <link rel="stylesheet" href="styles.13e635bf7a488639.css"></head>
3
+ <div id="app">
4
+ <div class="d-flex justify-content-center align-items-center" style="min-height: 200px;">
5
+ <div class="spinner-border text-primary" role="status">
6
+ <span class="visually-hidden">Loading...</span>
7
+ </div>
8
+ </div>
9
+ </div>
11
10
 
12
- <body>
13
- <app-root></app-root>
14
- <script src="runtime.ccde331d62c423b3.js" type="module"></script><script src="polyfills.efa2f09ba3e49167.js" type="module"></script><script src="main.f6423287e0411c87.js" type="module"></script></body>
15
-
16
- </html>
11
+ <script src="utils/countries.js"></script>
12
+ <script src="utils/device-images.js"></script>
13
+ <script src="utils/helpers.js"></script>
14
+ <script src="services/api.js"></script>
15
+ <script src="services/config.js"></script>
16
+ <script src="components/toggle.js"></script>
17
+ <script src="components/select.js"></script>
18
+ <script src="components/number-input.js"></script>
19
+ <script src="components/guard-modes.js"></script>
20
+ <script src="components/device-card.js"></script>
21
+ <script src="views/login.js"></script>
22
+ <script src="views/dashboard.js"></script>
23
+ <script src="views/device-detail.js"></script>
24
+ <script src="views/unsupported-detail.js"></script>
25
+ <script src="views/settings.js"></script>
26
+ <script src="app.js"></script>
@@ -0,0 +1,129 @@
1
+ /**
2
+ * API service — wraps homebridge.request() and push event listeners.
3
+ * Communicates with server.ts endpoints.
4
+ */
5
+ // eslint-disable-next-line no-unused-vars
6
+ const Api = {
7
+ /** @private Track registered listeners to prevent stacking on re-render */
8
+ _listeners: {},
9
+
10
+ /**
11
+ * Register an event listener, replacing any previous listener for the same event.
12
+ * Prevents listener stacking when views re-render.
13
+ * @param {string} event
14
+ * @param {function} handler
15
+ */
16
+ _on(event, handler) {
17
+ if (this._listeners[event]) {
18
+ homebridge.removeEventListener(event, this._listeners[event]);
19
+ }
20
+ this._listeners[event] = handler;
21
+ homebridge.addEventListener(event, handler);
22
+ },
23
+
24
+ /**
25
+ * Remove a previously registered listener for an event.
26
+ * Views can call this on teardown to explicitly detach their callbacks.
27
+ * @param {string} event
28
+ */
29
+ _off(event) {
30
+ if (this._listeners[event]) {
31
+ homebridge.removeEventListener(event, this._listeners[event]);
32
+ delete this._listeners[event];
33
+ }
34
+ },
35
+
36
+ /**
37
+ * Login with credentials, TFA code, or captcha
38
+ * @param {object} options - { username, password, country, deviceName } | { verifyCode } | { captcha: { captchaCode, captchaId } }
39
+ * @returns {Promise<{success: boolean, failReason?: number, data?: any}>}
40
+ */
41
+ async login(options) {
42
+ return homebridge.request('/login', options);
43
+ },
44
+
45
+ /**
46
+ * Check if a valid persistent cache file exists on the server.
47
+ * @returns {Promise<{valid: boolean}>}
48
+ */
49
+ async checkCache() {
50
+ return homebridge.request('/checkCache');
51
+ },
52
+
53
+ /**
54
+ * Load stored accessories from server (cached from last login)
55
+ * @returns {Promise<Array>} Array of L_Station objects
56
+ */
57
+ async loadStoredAccessories() {
58
+ return homebridge.request('/storedAccessories');
59
+ },
60
+
61
+ /**
62
+ * Reset plugin data (removes persistent storage)
63
+ * @returns {Promise<{result: number}>}
64
+ */
65
+ async resetPlugin() {
66
+ return homebridge.request('/reset');
67
+ },
68
+
69
+ /**
70
+ * Download compressed log files
71
+ * @returns {Promise<Buffer>}
72
+ */
73
+ async downloadLogs() {
74
+ return homebridge.request('/downloadLogs');
75
+ },
76
+
77
+ /**
78
+ * Register a listener for the 'addAccessory' push event.
79
+ * Fired by server after batch processing completes (~45s after login).
80
+ * Replaces any previously registered listener.
81
+ * @param {function} callback - receives array of L_Station objects
82
+ */
83
+ onAccessoriesReady(callback) {
84
+ this._on('addAccessory', (event) => {
85
+ callback(event.data);
86
+ });
87
+ },
88
+
89
+ /**
90
+ * Register a listener for admin account error.
91
+ * Replaces any previously registered listener.
92
+ * @param {function} callback
93
+ */
94
+ onAdminAccountUsed(callback) {
95
+ this._on('AdminAccountUsed', () => {
96
+ callback();
97
+ });
98
+ },
99
+
100
+ /**
101
+ * Register a listener for cache warnings (stale, version mismatch).
102
+ * Replaces any previously registered listener.
103
+ * @param {function} callback - receives { reason, ageDays?, currentVersion?, storedVersion? }
104
+ */
105
+ onCacheWarning(callback) {
106
+ this._on('cacheWarning', (event) => {
107
+ callback(event.data);
108
+ });
109
+ },
110
+
111
+ /**
112
+ * Register a listener for log download progress.
113
+ * Replaces any previously registered listener.
114
+ * @param {function} callback - receives { progress, status }
115
+ */
116
+ onDownloadLogsProgress(callback) {
117
+ this._on('downloadLogsProgress', (event) => {
118
+ callback(event.data);
119
+ });
120
+ },
121
+
122
+ /**
123
+ * Get system and environment information for issue reporting
124
+ * @returns {Promise<{pluginVersion: string, eufyClientVersion: string, homebridgeVersion: string, nodeVersion: string, os: string, devices: Array}>}
125
+ */
126
+ async getSystemInfo() {
127
+ return homebridge.request('/systemInfo');
128
+ },
129
+ };
@@ -0,0 +1,144 @@
1
+ /**
2
+ * Config service — wraps homebridge plugin config CRUD operations.
3
+ * Manages reading/writing plugin configuration via the Homebridge API.
4
+ */
5
+ // eslint-disable-next-line no-unused-vars
6
+ const Config = {
7
+ _cache: null,
8
+
9
+ /**
10
+ * Get the current plugin config (first block).
11
+ * Uses cache if available; call load() to force a fresh read.
12
+ * @returns {Promise<object>}
13
+ */
14
+ async get() {
15
+ if (this._cache) return this._cache;
16
+ return this.load();
17
+ },
18
+
19
+ /**
20
+ * Load config from Homebridge (always fetches fresh, no side-effects).
21
+ * @returns {Promise<object>}
22
+ */
23
+ async load() {
24
+ const configs = await homebridge.getPluginConfig();
25
+ this._cache = configs.length > 0 ? configs[0] : {};
26
+ return this._cache;
27
+ },
28
+
29
+ /**
30
+ * Update config in memory (does NOT save to disk).
31
+ * @param {object} config - full config object
32
+ */
33
+ async update(config) {
34
+ this._cache = config;
35
+ await homebridge.updatePluginConfig([config]);
36
+ },
37
+
38
+ /**
39
+ * Save current config to disk (config.json).
40
+ * Call sparingly — only after login or explicit user save.
41
+ */
42
+ async save() {
43
+ await homebridge.savePluginConfig();
44
+ },
45
+
46
+ /**
47
+ * Update config and immediately save to disk.
48
+ * @param {object} config
49
+ */
50
+ async updateAndSave(config) {
51
+ await this.update(config);
52
+ await this.save();
53
+ },
54
+
55
+ /**
56
+ * Get device config for a specific device by serial number.
57
+ * @param {string} serialNumber
58
+ * @returns {object|undefined}
59
+ */
60
+ getDeviceConfig(serialNumber) {
61
+ if (!this._cache) return undefined;
62
+ const cameras = this._cache.cameras || [];
63
+ return cameras.find((c) => c.serialNumber === serialNumber);
64
+ },
65
+
66
+ /**
67
+ * Get station config for a specific station by serial number.
68
+ * @param {string} serialNumber
69
+ * @returns {object|undefined}
70
+ */
71
+ getStationConfig(serialNumber) {
72
+ if (!this._cache) return undefined;
73
+ const stations = this._cache.stations || [];
74
+ return stations.find((s) => s.serialNumber === serialNumber);
75
+ },
76
+
77
+ /**
78
+ * Update or create camera config for a device.
79
+ * @param {string} serialNumber
80
+ * @param {object} options - config properties to merge
81
+ */
82
+ async updateDeviceConfig(serialNumber, options) {
83
+ const config = this._cache || await this.get();
84
+ if (!config.cameras) config.cameras = [];
85
+
86
+ const idx = config.cameras.findIndex((c) => c.serialNumber === serialNumber);
87
+ if (idx !== -1) {
88
+ Object.assign(config.cameras[idx], options);
89
+ } else {
90
+ config.cameras.push({ serialNumber, ...options });
91
+ }
92
+
93
+ await this.update(config);
94
+ },
95
+
96
+ /**
97
+ * Update or create station config.
98
+ * @param {string} serialNumber
99
+ * @param {object} options - config properties to merge
100
+ */
101
+ async updateStationConfig(serialNumber, options) {
102
+ const config = this._cache || await this.get();
103
+ if (!config.stations) config.stations = [];
104
+
105
+ const idx = config.stations.findIndex((s) => s.serialNumber === serialNumber);
106
+ if (idx !== -1) {
107
+ Object.assign(config.stations[idx], options);
108
+ } else {
109
+ config.stations.push({ serialNumber, ...options });
110
+ }
111
+
112
+ await this.update(config);
113
+ },
114
+
115
+ /**
116
+ * Update global config options (top-level properties).
117
+ * @param {object} options - properties to merge into top-level config
118
+ */
119
+ async updateGlobal(options) {
120
+ const config = this._cache || await this.get();
121
+ Object.assign(config, options);
122
+ await this.update(config);
123
+ },
124
+
125
+ /**
126
+ * Toggle a device in the ignore list.
127
+ * @param {string} serialNumber
128
+ * @param {boolean} ignored - true to ignore, false to un-ignore
129
+ * @param {'device'|'station'} type
130
+ */
131
+ async toggleIgnore(serialNumber, ignored, type) {
132
+ const config = this._cache || await this.get();
133
+ const key = type === 'device' ? 'ignoreDevices' : 'ignoreStations';
134
+ if (!config[key]) config[key] = [];
135
+
136
+ if (ignored && !config[key].includes(serialNumber)) {
137
+ config[key].push(serialNumber);
138
+ } else if (!ignored) {
139
+ config[key] = config[key].filter((id) => id !== serialNumber);
140
+ }
141
+
142
+ await this.update(config);
143
+ },
144
+ };