@homebridge-plugins/homebridge-eufy-security 5.0.0-beta.15 → 5.0.0-beta.17
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/config.schema.json +4 -0
- package/dist/configuration.js +11 -1
- package/dist/configuration.js.map +1 -1
- package/dist/diagnostics.js +105 -19
- package/dist/diagnostics.js.map +1 -1
- package/dist/homekit/adapters/lock.js +27 -11
- package/dist/homekit/adapters/lock.js.map +1 -1
- package/dist/homekit/adapters/package-sensor.js +63 -0
- package/dist/homekit/adapters/package-sensor.js.map +1 -0
- package/dist/homekit/adapters/registry.js +2 -0
- package/dist/homekit/adapters/registry.js.map +1 -1
- package/dist/homekit/adapters/security-system.js +10 -0
- package/dist/homekit/adapters/security-system.js.map +1 -1
- package/dist/homekit/coverage-matrix.js +0 -2
- package/dist/homekit/coverage-matrix.js.map +1 -1
- package/dist/homekit/reconciler.js +1 -0
- package/dist/homekit/reconciler.js.map +1 -1
- package/dist/platform.js +1 -0
- package/dist/platform.js.map +1 -1
- package/dist/runtime/channel.js +6 -2
- package/dist/runtime/channel.js.map +1 -1
- package/dist/runtime/owner.js +3 -0
- package/dist/runtime/owner.js.map +1 -1
- package/dist/ui/dashboard.js +7 -1
- package/dist/ui/dashboard.js.map +1 -1
- package/dist/ui/server.js +1 -0
- package/dist/ui/server.js.map +1 -1
- package/homebridge-ui/public/app.css +117 -229
- package/homebridge-ui/public/i18n/en.json +86 -79
- package/homebridge-ui/public/i18n/fr.json +87 -80
- package/homebridge-ui/public/index.html +84 -94
- package/homebridge-ui/public/js/app.js +246 -423
- package/homebridge-ui/public/js/dashboard.js +58 -9
- package/homebridge-ui/public/js/profile-wizard.js +6 -55
- package/i18n/runtime/en.json +1 -1
- package/package.json +1 -1
|
@@ -37,8 +37,10 @@
|
|
|
37
37
|
*
|
|
38
38
|
* A row is the whole control — the state names itself and the select beside it says what it means. An
|
|
39
39
|
* unassigned state shows a dash, which is what keeps Night out of HomeKit until it is given a mode.
|
|
40
|
+
*
|
|
41
|
+
* It is withheld while the security system it maps is turned off, and the stored map is kept for when it is back.
|
|
40
42
|
*/
|
|
41
|
-
function armingModeControl(device, assigned, messages) {
|
|
43
|
+
function armingModeControl(device, assigned, messages, securitySystem) {
|
|
42
44
|
const summary = ARMING_SLOTS.map(
|
|
43
45
|
({ slot, label }) => `${messages[label]} → ${EUFY_MODES.find((mode) => mode.value === assigned[slot])?.label ?? '—'}`,
|
|
44
46
|
).join(' · ');
|
|
@@ -52,17 +54,18 @@
|
|
|
52
54
|
return `<tr><th scope="row"><label for="${id}">${escapeHtml(messages[label])}</label></th><td><select id="${id}" data-preference="armingModes" data-slot="${slot}" data-serial="${escapeHtml(device.serial)}" data-original="${escapeHtml(value)}"><option value=""${value === '' ? ' selected' : ''}>—</option>${options}</select></td></tr>`;
|
|
53
55
|
}).join('');
|
|
54
56
|
const assignedItself = ARMING_SLOTS.some(({ slot }) => assigned[slot] !== DEFAULT_ARMING_MODES[slot]);
|
|
55
|
-
return `<details class="arming-setting" data-setting${assignedItself ? ' open' : ''}${device.represented ? '' : ' hidden'} data-requires-representation><summary><span class="setting-label">${escapeHtml(messages.preferenceArmingModes)}</span><span class="arming-summary">${escapeHtml(summary)}</span></summary><table class="arming-table"><thead><tr><th scope="col">HomeKit</th><th scope="col">eufy</th></tr></thead><tbody>${rows}</tbody></table><p class="snapshot-help">${escapeHtml(messages.armingModesHelp)}</p></details>`;
|
|
57
|
+
return `<details class="arming-setting" data-setting${assignedItself ? ' open' : ''}${device.represented && securitySystem ? '' : ' hidden'} data-requires-representation data-requires-security-system><summary><span class="setting-label">${escapeHtml(messages.preferenceArmingModes)}</span><span class="arming-summary">${escapeHtml(summary)}</span></summary><table class="arming-table"><thead><tr><th scope="col">HomeKit</th><th scope="col">eufy</th></tr></thead><tbody>${rows}</tbody></table><p class="snapshot-help">${escapeHtml(messages.armingModesHelp)}</p></details>`;
|
|
56
58
|
}
|
|
57
59
|
|
|
58
60
|
function preferenceControl(device, key, preference, messages) {
|
|
59
61
|
const labels = {
|
|
60
62
|
represented: messages.preferenceRepresented,
|
|
61
63
|
audio: messages.preferenceAudio,
|
|
64
|
+
securitySystem: messages.preferenceSecuritySystem,
|
|
62
65
|
snapshotMode: messages.preferenceSnapshotMode,
|
|
63
66
|
};
|
|
64
67
|
if (key === 'armingModes') {
|
|
65
|
-
return armingModeControl(device, preference.armingModes, messages);
|
|
68
|
+
return armingModeControl(device, preference.armingModes, messages, preference.securitySystem);
|
|
66
69
|
}
|
|
67
70
|
const represented = preference.represented;
|
|
68
71
|
const dependent = key === 'represented' ? '' : ` data-requires-representation${represented ? '' : ' hidden'}`;
|
|
@@ -260,16 +263,53 @@
|
|
|
260
263
|
*/
|
|
261
264
|
const DIAGNOSABLE = new Set(['incomplete', 'missing', 'stale']);
|
|
262
265
|
|
|
266
|
+
/** The diagnostics area a condition's capture belongs to, by its code. A code no entry matches is `other`. */
|
|
267
|
+
const CONDITION_AREAS = [
|
|
268
|
+
[/^camera-recording/, 'hksv-recording'],
|
|
269
|
+
[/^camera-(?:live|media|streaming|snapshot|talkback)/, 'live-media'],
|
|
270
|
+
[/lock|light|arming|siren|contact|camera-control/, 'control-state'],
|
|
271
|
+
[/transport-degraded/, 'startup-authentication'],
|
|
272
|
+
];
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* The problem the diagnostics action points at, or nothing: a state a run explains, then the first active condition
|
|
276
|
+
* this page has words for, then the first device nothing can reach. Each names the diagnostics area a run on it is
|
|
277
|
+
* about. A condition is worded under its log keys; the ones left unworded are those the page answers on its own,
|
|
278
|
+
* such as a sign-in or another owner, and the devices no action makes supported.
|
|
279
|
+
*/
|
|
280
|
+
function attention(result, state, messages) {
|
|
281
|
+
if (DIAGNOSABLE.has(result.state)) return { ...state, profile: 'startup-authentication' };
|
|
282
|
+
const condition = (result.conditions ?? []).find(
|
|
283
|
+
({ summaryKey, actionKey }) => messages[summaryKey] && messages[actionKey],
|
|
284
|
+
);
|
|
285
|
+
if (condition) {
|
|
286
|
+
return {
|
|
287
|
+
title: messages[condition.summaryKey],
|
|
288
|
+
summary: messages[condition.actionKey],
|
|
289
|
+
profile: CONDITION_AREAS.find(([code]) => code.test(condition.code))?.[1] ?? 'other',
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
const device = result.devices.find((candidate) => candidate.availability === 'unavailable');
|
|
293
|
+
return (
|
|
294
|
+
device && {
|
|
295
|
+
title: (messages.attentionUnreachableTitle ?? '').replace('{device}', device.name),
|
|
296
|
+
summary: messages.attentionUnreachableSummary ?? '',
|
|
297
|
+
profile: 'device-representation',
|
|
298
|
+
}
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/** Draws the dashboard, and answers the problem it shows the diagnostics action, if any. */
|
|
263
303
|
function render(result, config, messages, elements) {
|
|
264
304
|
const suffix = result.state
|
|
265
305
|
.split('-')
|
|
266
306
|
.map((part) => part[0].toUpperCase() + part.slice(1))
|
|
267
307
|
.join('');
|
|
268
|
-
|
|
269
|
-
|
|
308
|
+
const title = messages[`dashboard${suffix}Title`] ?? messages.dashboardIncompleteTitle;
|
|
309
|
+
const summary = messages[`dashboard${suffix}Summary`] ?? messages.dashboardIncompleteSummary;
|
|
310
|
+
elements.title.textContent = title;
|
|
311
|
+
elements.summary.textContent = summary;
|
|
270
312
|
elements.dashboard.dataset.state = result.state;
|
|
271
|
-
// Offered rather than announced: a run is worth suggesting only where the state is one a run explains.
|
|
272
|
-
if (elements.diagnose) elements.diagnose.hidden = !DIAGNOSABLE.has(result.state);
|
|
273
313
|
elements.dashboard.hidden = false;
|
|
274
314
|
elements.masthead.hidden = true;
|
|
275
315
|
renderUpdatePending(result, messages, elements);
|
|
@@ -281,6 +321,7 @@
|
|
|
281
321
|
} else if (result.state === 'authentication-required') {
|
|
282
322
|
elements.setup.hidden = false;
|
|
283
323
|
}
|
|
324
|
+
return attention(result, { title, summary }, messages);
|
|
284
325
|
}
|
|
285
326
|
|
|
286
327
|
function bindPreferences(elements, getConfig, saveConfig, getMessages, openDevice) {
|
|
@@ -311,7 +352,7 @@
|
|
|
311
352
|
const key = control?.dataset?.preference;
|
|
312
353
|
const existing = getConfig();
|
|
313
354
|
if (!existing || !serial || !key) return;
|
|
314
|
-
const defaults = { represented: true, audio: true, snapshotMode: 'Refresh' };
|
|
355
|
+
const defaults = { represented: true, audio: true, securitySystem: true, snapshotMode: 'Refresh' };
|
|
315
356
|
if (key === 'armingModes') {
|
|
316
357
|
await saveArmingMode(control, serial, existing, saveConfig, getMessages, elements);
|
|
317
358
|
return;
|
|
@@ -337,9 +378,16 @@
|
|
|
337
378
|
(candidate) => candidate.dataset.serial === serial,
|
|
338
379
|
);
|
|
339
380
|
tile?.classList.toggle('device-tile-disabled', !value);
|
|
381
|
+
const grid = control.closest('.preference-grid');
|
|
382
|
+
const securityOff = grid?.querySelector('[data-preference="securitySystem"]')?.checked === false;
|
|
383
|
+
grid?.querySelectorAll('[data-requires-representation]').forEach((setting) => {
|
|
384
|
+
setting.hidden = !value || (securityOff && setting.hasAttribute('data-requires-security-system'));
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
if (key === 'securitySystem') {
|
|
340
388
|
control
|
|
341
389
|
.closest('.preference-grid')
|
|
342
|
-
?.querySelectorAll('[data-requires-
|
|
390
|
+
?.querySelectorAll('[data-requires-security-system]')
|
|
343
391
|
.forEach((setting) => {
|
|
344
392
|
setting.hidden = !value;
|
|
345
393
|
});
|
|
@@ -391,6 +439,7 @@
|
|
|
391
439
|
const preference = {
|
|
392
440
|
represented: preferences[device.serial]?.represented ?? true,
|
|
393
441
|
audio: preferences[device.serial]?.audio ?? true,
|
|
442
|
+
securitySystem: preferences[device.serial]?.securitySystem ?? true,
|
|
394
443
|
snapshotMode: preferences[device.serial]?.snapshotMode ?? 'Refresh',
|
|
395
444
|
armingModes: { ...DEFAULT_ARMING_MODES, ...preferences[device.serial]?.armingModes },
|
|
396
445
|
};
|
|
@@ -9,55 +9,14 @@
|
|
|
9
9
|
'other',
|
|
10
10
|
];
|
|
11
11
|
|
|
12
|
-
const deviceProfiles = ['device-representation', 'control-state', 'live-media', 'hksv-recording'];
|
|
13
|
-
|
|
14
12
|
/**
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* A device the fault cannot happen on is not an answer to which ones are affected: offering a HomeBase for a
|
|
18
|
-
* live view fault invites a report about a stream that device never had. Missing from the list means every
|
|
19
|
-
* device qualifies, which is what a device missing from HomeKit needs — the ones with no representation at
|
|
20
|
-
* all are exactly the subject there.
|
|
13
|
+
* Which screen a session puts on the panel: its file while one can be downloaded, its capture while one runs, and
|
|
14
|
+
* otherwise the question, since picking an area replaces the session with a capture of that area.
|
|
21
15
|
*/
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
'
|
|
25
|
-
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
function affectable(profile, devices) {
|
|
29
|
-
return devices.filter(affected[profile] ?? (() => true));
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function start() {
|
|
33
|
-
return { mode: 'tiles', profile: undefined };
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function select(state, profile) {
|
|
37
|
-
return { ...state, mode: deviceProfiles.includes(profile) ? 'devices' : 'frequency', profile };
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function chooseDevices(state, devices) {
|
|
41
|
-
return { ...state, mode: 'frequency', devices };
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function reject() {
|
|
45
|
-
return start();
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function chooseReproductionMode(state, reproductionMode) {
|
|
49
|
-
return { ...state, mode: 'match', reproductionMode };
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function backFromFrequency(state) {
|
|
53
|
-
return deviceProfiles.includes(state.profile) ? { ...state, mode: 'devices' } : start();
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function screen(session, startingAnother) {
|
|
57
|
-
if (session.partialExportAvailable && !startingAnother) return 'review';
|
|
58
|
-
if (startingAnother || session.status === 'inactive' || session.status === 'expired') return 'choose';
|
|
59
|
-
if (session.status === 'authorized' || session.status === 'reproducing') return 'reproduce';
|
|
60
|
-
return 'status';
|
|
16
|
+
function screen(session) {
|
|
17
|
+
if (session.partialExportAvailable) return 'review';
|
|
18
|
+
if (session.status === 'reproducing') return 'reproduce';
|
|
19
|
+
return 'choose';
|
|
61
20
|
}
|
|
62
21
|
|
|
63
22
|
function backgroundActive(session) {
|
|
@@ -65,16 +24,8 @@
|
|
|
65
24
|
}
|
|
66
25
|
|
|
67
26
|
global.HomebridgeEufyDiagnosticsWizard = {
|
|
68
|
-
affectable,
|
|
69
|
-
backFromFrequency,
|
|
70
27
|
backgroundActive,
|
|
71
|
-
chooseDevices,
|
|
72
|
-
chooseReproductionMode,
|
|
73
|
-
deviceProfiles,
|
|
74
28
|
profiles,
|
|
75
|
-
reject,
|
|
76
29
|
screen,
|
|
77
|
-
select,
|
|
78
|
-
start,
|
|
79
30
|
};
|
|
80
31
|
})(window);
|
package/i18n/runtime/en.json
CHANGED
|
@@ -87,7 +87,7 @@
|
|
|
87
87
|
"log.notice.stateSubscriberFailed": "A runtime state consumer failed; review Homebridge Eufy health in the dashboard.",
|
|
88
88
|
"log.notice.statusPublicationFailed": "Runtime status could not be published; dashboard status may be stale.",
|
|
89
89
|
"log.notice.channelServeFailed": "Live runtime status could not be served to the plugin settings page; it will show the last published status instead.",
|
|
90
|
-
"log.notice.diagnosticsAuthorizationArmed": "
|
|
90
|
+
"log.notice.diagnosticsAuthorizationArmed": "A diagnostics capture started; finish it from the plugin settings before {finishBy}.",
|
|
91
91
|
"log.notice.standDownNotRearmed": "Homebridge Eufy stood down for an authentication and could not take the account session back; restart Homebridge to bring it back.",
|
|
92
92
|
"log.notice.standDownAccountReplaced": "Homebridge Eufy stood down for an authentication that replaced the account; restart Homebridge to use the new account.",
|
|
93
93
|
"log.runtime.authenticationRequired": "Eufy authentication is required",
|
package/package.json
CHANGED