@camstack/system 1.2.304 → 1.2.305
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/addon-runner.js +1 -1
- package/dist/addon-runner.mjs +1 -1
- package/dist/addon-utils.d.ts +1 -1
- package/dist/addon-utils.js +3 -1
- package/dist/addon-utils.mjs +2 -2
- package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
- package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
- package/dist/builtins/alerts/alerts.addon.js +1 -1
- package/dist/builtins/alerts/alerts.addon.mjs +1 -1
- package/dist/builtins/autotrack/index.js +1 -1
- package/dist/builtins/autotrack/index.mjs +1 -1
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +1 -1
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +1 -1
- package/dist/builtins/camera-grid/index.js +1 -1
- package/dist/builtins/camera-grid/index.mjs +1 -1
- package/dist/builtins/console-logging/index.js +1 -1
- package/dist/builtins/console-logging/index.mjs +1 -1
- package/dist/builtins/core-blocks/core-blocks.addon.js +1 -1
- package/dist/builtins/core-blocks/core-blocks.addon.mjs +1 -1
- package/dist/builtins/device-manager/device-extension-linked-slot.d.ts +8 -0
- package/dist/builtins/device-manager/device-linked-devices.d.ts +34 -0
- package/dist/builtins/device-manager/device-manager.addon.js +188 -22
- package/dist/builtins/device-manager/device-manager.addon.mjs +188 -22
- package/dist/builtins/doorbell/binding-mirror.d.ts +24 -0
- package/dist/builtins/doorbell/doorbell-extension-slot.d.ts +66 -0
- package/dist/builtins/doorbell/virtual-doorbell.addon.d.ts +14 -0
- package/dist/builtins/doorbell/virtual-doorbell.addon.js +307 -38
- package/dist/builtins/doorbell/virtual-doorbell.addon.mjs +307 -38
- package/dist/builtins/hub-forwarder/index.js +1 -1
- package/dist/builtins/hub-forwarder/index.mjs +1 -1
- package/dist/builtins/liveness-monitor/liveness-monitor.addon.js +1 -1
- package/dist/builtins/liveness-monitor/liveness-monitor.addon.mjs +1 -1
- package/dist/builtins/local-auth/local-auth.addon.js +1 -1
- package/dist/builtins/local-auth/local-auth.addon.mjs +1 -1
- package/dist/builtins/local-network/local-network.addon.js +1 -1
- package/dist/builtins/local-network/local-network.addon.mjs +1 -1
- package/dist/builtins/loki-logging/index.js +1 -1
- package/dist/builtins/loki-logging/index.mjs +1 -1
- package/dist/builtins/native-metrics/native-metrics.addon.js +1 -1
- package/dist/builtins/native-metrics/native-metrics.addon.mjs +1 -1
- package/dist/builtins/platform-probe/index.js +1 -1
- package/dist/builtins/platform-probe/index.mjs +1 -1
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
- package/dist/builtins/snapshot/index.js +1 -1
- package/dist/builtins/snapshot/index.mjs +1 -1
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +0 -0
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +0 -0
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +1 -1
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +1 -1
- package/dist/builtins/system-config/system-config.addon.js +1 -1
- package/dist/builtins/system-config/system-config.addon.mjs +1 -1
- package/dist/builtins/winston-logging/index.js +1 -1
- package/dist/builtins/winston-logging/index.mjs +1 -1
- package/dist/{child-cap-dispatch-C1c3rWaX.mjs → child-cap-dispatch-BCJjUtXV.mjs} +9 -1
- package/dist/{child-cap-dispatch-De5_HcvR.js → child-cap-dispatch-Be8e2cBP.js} +9 -1
- package/dist/{dist-C571Y95Z.js → dist-BK4iP6ab.js} +1069 -310
- package/dist/{dist-Duqf80QM.mjs → dist-Df1NhKxm.mjs} +1046 -311
- package/dist/download/model-downloader.d.ts +15 -0
- package/dist/{event-loop-stall-monitor-GPrVvx14.mjs → event-loop-stall-monitor-EXjzIxDz.mjs} +25 -3
- package/dist/{event-loop-stall-monitor-CAsHdMwP.js → event-loop-stall-monitor-ey4mbofz.js} +36 -2
- package/dist/index.js +21 -3
- package/dist/index.mjs +21 -3
- package/dist/kernel/config-manager.d.ts +3 -1
- package/dist/{retired-settings-keys-0j8rNoa8.js → retired-settings-keys-DwWMph6F.js} +1 -1
- package/dist/{retired-settings-keys-CeO_dBHU.mjs → retired-settings-keys-dC4ckbXJ.mjs} +1 -1
- package/package.json +7 -1
|
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
require("../../chunk-Cek0wNdY.js");
|
|
6
|
-
const require_dist = require("../../dist-
|
|
6
|
+
const require_dist = require("../../dist-BK4iP6ab.js");
|
|
7
7
|
//#region src/builtins/doorbell/trigger-engine.ts
|
|
8
8
|
/**
|
|
9
9
|
* Pure trigger logic for the virtual-doorbell builtin — edge detection over
|
|
@@ -210,7 +210,7 @@ function nextBindingState(previous, boundNow) {
|
|
|
210
210
|
}
|
|
211
211
|
/** May a ring proceed? Only a corroborated unbind stops one. */
|
|
212
212
|
function allowsRing(state) {
|
|
213
|
-
return state
|
|
213
|
+
return !needsDurableRead(state);
|
|
214
214
|
}
|
|
215
215
|
/** True only when a read has positively confirmed the binding. */
|
|
216
216
|
function hasConfirmedBinding(state) {
|
|
@@ -262,6 +262,29 @@ function readDurableWrapperBinding(record, deviceId, capName, ownAddonId) {
|
|
|
262
262
|
if (typeof wrapperAddonId !== "string") return "unbound";
|
|
263
263
|
return require_dist.isSameAddonId(wrapperAddonId, ownAddonId) ? "bound" : "unbound";
|
|
264
264
|
}
|
|
265
|
+
/** Does this mirror state need the durable authority before a verdict? Only `unbound` does. */
|
|
266
|
+
function needsDurableRead(state) {
|
|
267
|
+
return state === "unbound";
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* THE decision — the ring gate (`fireDoorbell` / `ringsDespiteUnboundMirror`)
|
|
271
|
+
* and the Extensions card's activation both call it, so what the card says
|
|
272
|
+
* and what a press does cannot disagree. `durable` is `null` when it was not
|
|
273
|
+
* read, which {@link needsDurableRead} allows for every state but `unbound`.
|
|
274
|
+
* Pure: it reads nothing and writes nothing.
|
|
275
|
+
*/
|
|
276
|
+
function ringVerdict(state, durable) {
|
|
277
|
+
if (state === "bound") return "rings";
|
|
278
|
+
if (state === "unbind-pending") return "rings-pending";
|
|
279
|
+
if (state === void 0) return "rings-unconfirmed";
|
|
280
|
+
if (durable === "bound") return "rings";
|
|
281
|
+
if (durable === "unbound") return "silent";
|
|
282
|
+
return "unknown";
|
|
283
|
+
}
|
|
284
|
+
/** Whether a press proceeds under this verdict. */
|
|
285
|
+
function verdictRings(verdict) {
|
|
286
|
+
return verdict !== "silent";
|
|
287
|
+
}
|
|
265
288
|
//#endregion
|
|
266
289
|
//#region src/builtins/doorbell/doorbell-settings.ts
|
|
267
290
|
/**
|
|
@@ -354,6 +377,203 @@ function applyDoorbellSourcesPatch(current, patchValue) {
|
|
|
354
377
|
return next;
|
|
355
378
|
}
|
|
356
379
|
//#endregion
|
|
380
|
+
//#region src/builtins/doorbell/doorbell-extension-slot.ts
|
|
381
|
+
/**
|
|
382
|
+
* The virtual doorbell's side of `device-extension` (D648): the
|
|
383
|
+
* `doorbell-source` slot, served over the `doorbellSources` store the
|
|
384
|
+
* "Virtual doorbell" settings section already writes.
|
|
385
|
+
*
|
|
386
|
+
* - `list` reads the store through `parseDoorbellSources` — the parser the
|
|
387
|
+
* trigger engine's assignments are built from — so the slot's value is
|
|
388
|
+
* exactly what rings. It also REPORTS whether the wrapper is bound to the
|
|
389
|
+
* camera (`activation`), named as `setWrapperActive`'s: sources on an
|
|
390
|
+
* unbound camera ring nothing, and the card must say so rather than look
|
|
391
|
+
* configured. Nothing here writes it.
|
|
392
|
+
* - The activation costs NOTHING on the common path. A camera with no
|
|
393
|
+
* sources has no gate worth reporting and makes no read. A camera with
|
|
394
|
+
* sources is answered from the addon's D49 binding mirror — the in-memory
|
|
395
|
+
* verdict the ring path itself uses — through `ringVerdict`, the ring
|
|
396
|
+
* gate's own decision function. The card is READ-ONLY: it never refreshes
|
|
397
|
+
* or writes the mirror, and reads the durable authority only for an
|
|
398
|
+
* `unbound` mirror, exactly where the ring gate reads it.
|
|
399
|
+
* `getBindings` was measured at up to 5.24 s on the live hub (2026-08-30),
|
|
400
|
+
* and the dispatcher already paid one for this page.
|
|
401
|
+
* - The picked sources are labelled with ONE batched `listAll({deviceIds})`,
|
|
402
|
+
* never a `getDevice` per id.
|
|
403
|
+
* - `set` goes through the addon's `persistDoorbellSources`, the SAME
|
|
404
|
+
* normalise-and-store path the settings save uses, which also invalidates
|
|
405
|
+
* the assignment cache so the binding mirror is re-read off the ring path
|
|
406
|
+
* (D49). A `setSlot` call is NOT evidence that the wrapper is bound (the
|
|
407
|
+
* contract reaches this provider through `device-extension`'s own
|
|
408
|
+
* binding), so it never confirms the mirror. A NEW pick must be a device
|
|
409
|
+
* that carries a source cap (`SOURCE_CAPS`, the D636 ids-only read) — which
|
|
410
|
+
* also refuses a container, a device with no source cap and an id no
|
|
411
|
+
* device has — and never the camera itself.
|
|
412
|
+
* - `candidates` are those same source-capable devices, devices in the
|
|
413
|
+
* camera's location suggested.
|
|
414
|
+
*/
|
|
415
|
+
/** The routing prefix of this owner's `slotRef`s. */
|
|
416
|
+
var VIRTUAL_DOORBELL_EXTENSION_SOURCE = "virtual-doorbell";
|
|
417
|
+
/** The store the slot reads and writes, named (D62). */
|
|
418
|
+
var DOORBELL_SOURCES_AUTHORITY = `virtual-doorbell:${KEY_SOURCES}`;
|
|
419
|
+
/** The authority that decides whether the wrapper serves a camera at all. */
|
|
420
|
+
var WRAPPER_ACTIVATION_AUTHORITY = "device-manager:setWrapperActive";
|
|
421
|
+
var INACTIVE_DETAIL = "The virtual doorbell is not bound to this camera: these devices ring nothing until it is activated in the camera’s bindings.";
|
|
422
|
+
var PENDING_DETAIL = "One bindings read did not see the wrapper; a press still rings until a second read agrees.";
|
|
423
|
+
/** How the card SAYS a verdict. The verdict itself is `ringVerdict`'s, never the card's. */
|
|
424
|
+
function activationFromVerdict(verdict) {
|
|
425
|
+
const authority = WRAPPER_ACTIVATION_AUTHORITY;
|
|
426
|
+
switch (verdict) {
|
|
427
|
+
case "rings": return {
|
|
428
|
+
state: "active",
|
|
429
|
+
authority
|
|
430
|
+
};
|
|
431
|
+
case "rings-pending": return {
|
|
432
|
+
state: "active",
|
|
433
|
+
authority,
|
|
434
|
+
detail: PENDING_DETAIL
|
|
435
|
+
};
|
|
436
|
+
case "rings-unconfirmed": return {
|
|
437
|
+
state: "active",
|
|
438
|
+
authority,
|
|
439
|
+
detail: "No bindings read has confirmed the wrapper yet; a press rings meanwhile."
|
|
440
|
+
};
|
|
441
|
+
case "unknown": return {
|
|
442
|
+
state: "unknown",
|
|
443
|
+
authority,
|
|
444
|
+
detail: "Could not verify: the last reads did not see the wrapper and the bindings store cannot answer. A press still rings."
|
|
445
|
+
};
|
|
446
|
+
case "silent": return {
|
|
447
|
+
state: "inactive",
|
|
448
|
+
authority,
|
|
449
|
+
detail: INACTIVE_DETAIL
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
/**
|
|
454
|
+
* Concurrent card views of the same `unbound` camera share ONE durable read,
|
|
455
|
+
* per camera, for as long as it is in flight — a page with several viewers
|
|
456
|
+
* (or a refetch storm) must not multiply the store round trip. Only the
|
|
457
|
+
* CARD's reads are shared: the ring gate calls the addon's
|
|
458
|
+
* `readDurableAuthority` on its own path and never waits behind a card.
|
|
459
|
+
*/
|
|
460
|
+
function shareDurableReads(read) {
|
|
461
|
+
const inFlight = /* @__PURE__ */ new Map();
|
|
462
|
+
return (deviceId) => {
|
|
463
|
+
const pending = inFlight.get(deviceId);
|
|
464
|
+
if (pending !== void 0) return pending;
|
|
465
|
+
const started = read(deviceId).finally(() => {
|
|
466
|
+
inFlight.delete(deviceId);
|
|
467
|
+
});
|
|
468
|
+
inFlight.set(deviceId, started);
|
|
469
|
+
return started;
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
async function activationOf(deps, readDurable, deviceId) {
|
|
473
|
+
const mirrored = deps.readBindingMirror(deviceId);
|
|
474
|
+
return activationFromVerdict(ringVerdict(mirrored, needsDurableRead(mirrored) ? await readDurable(deviceId) : null));
|
|
475
|
+
}
|
|
476
|
+
function sourceOf(deviceId, byId) {
|
|
477
|
+
const device = byId.get(deviceId);
|
|
478
|
+
if (device === void 0) return {
|
|
479
|
+
deviceId,
|
|
480
|
+
name: null,
|
|
481
|
+
health: "missing",
|
|
482
|
+
detail: "No device has this id any more — it rings nothing until removed."
|
|
483
|
+
};
|
|
484
|
+
return {
|
|
485
|
+
deviceId,
|
|
486
|
+
name: device.name,
|
|
487
|
+
health: "ok"
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
async function candidatesFor(deps, deviceId) {
|
|
491
|
+
const [ids, fleet] = await Promise.all([deps.sourceCapableDeviceIds(), deps.listFleet()]);
|
|
492
|
+
const eligible = new Set(ids);
|
|
493
|
+
const byId = new Map(fleet.map((d) => [d.id, d]));
|
|
494
|
+
const location = byId.get(deviceId)?.location ?? null;
|
|
495
|
+
const out = [];
|
|
496
|
+
for (const device of fleet) {
|
|
497
|
+
if (device.id === deviceId || !eligible.has(device.id)) continue;
|
|
498
|
+
const suggested = location !== null && device.location === location;
|
|
499
|
+
const parent = device.parentDeviceId === null ? void 0 : byId.get(device.parentDeviceId);
|
|
500
|
+
out.push({
|
|
501
|
+
deviceId: device.id,
|
|
502
|
+
name: device.name,
|
|
503
|
+
type: device.type,
|
|
504
|
+
location: device.location,
|
|
505
|
+
parentName: parent?.name ?? null,
|
|
506
|
+
suggested,
|
|
507
|
+
reason: suggested ? "same location" : null
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
return out.toSorted((a, b) => Number(b.suggested) - Number(a.suggested) || a.name.localeCompare(b.name));
|
|
511
|
+
}
|
|
512
|
+
/** Why each NEW pick is refused, or [] when every one is a legitimate source. */
|
|
513
|
+
async function refusedPicks(deps, deviceId, picked) {
|
|
514
|
+
const persisted = new Set((await deps.readSources(deviceId)).map((s) => s.deviceId));
|
|
515
|
+
const added = picked.filter((id) => !persisted.has(id));
|
|
516
|
+
if (added.length === 0) return [];
|
|
517
|
+
const eligible = new Set(await deps.sourceCapableDeviceIds());
|
|
518
|
+
const refused = [];
|
|
519
|
+
for (const id of added) if (id === deviceId) refused.push(`${id} is the camera itself`);
|
|
520
|
+
else if (!eligible.has(id)) refused.push(`${id} carries no doorbell source capability (a binary sensor or switch)`);
|
|
521
|
+
return refused;
|
|
522
|
+
}
|
|
523
|
+
/** Build the `device-extension` provider the virtual doorbell registers. */
|
|
524
|
+
function createDoorbellExtensionProvider(deps) {
|
|
525
|
+
const readDurable = shareDurableReads(deps.readDurableBinding);
|
|
526
|
+
return require_dist.createDeviceExtensionProvider({
|
|
527
|
+
source: VIRTUAL_DOORBELL_EXTENSION_SOURCE,
|
|
528
|
+
label: "Virtual doorbell",
|
|
529
|
+
slots: [require_dist.defineExtensionSlotProvider("doorbell-source", {
|
|
530
|
+
list: async (deviceId) => {
|
|
531
|
+
const ids = (await deps.readSources(deviceId)).map((s) => s.deviceId);
|
|
532
|
+
if (ids.length === 0) return {
|
|
533
|
+
authority: DOORBELL_SOURCES_AUTHORITY,
|
|
534
|
+
enabled: null,
|
|
535
|
+
activation: null,
|
|
536
|
+
value: {
|
|
537
|
+
slot: "doorbell-source",
|
|
538
|
+
deviceIds: []
|
|
539
|
+
},
|
|
540
|
+
sources: []
|
|
541
|
+
};
|
|
542
|
+
const [devices, activation] = await Promise.all([deps.listDevices(ids), activationOf(deps, readDurable, deviceId)]);
|
|
543
|
+
const byId = new Map(devices.map((d) => [d.id, d]));
|
|
544
|
+
return {
|
|
545
|
+
authority: DOORBELL_SOURCES_AUTHORITY,
|
|
546
|
+
enabled: null,
|
|
547
|
+
activation,
|
|
548
|
+
value: {
|
|
549
|
+
slot: "doorbell-source",
|
|
550
|
+
deviceIds: ids
|
|
551
|
+
},
|
|
552
|
+
sources: ids.map((id) => sourceOf(id, byId))
|
|
553
|
+
};
|
|
554
|
+
},
|
|
555
|
+
set: async (deviceId, value) => {
|
|
556
|
+
const refused = await refusedPicks(deps, deviceId, value.deviceIds);
|
|
557
|
+
if (refused.length > 0) {
|
|
558
|
+
deps.logger.warn("virtual-doorbell: extension write refused — nothing persisted", {
|
|
559
|
+
tags: { deviceId },
|
|
560
|
+
meta: { refused }
|
|
561
|
+
});
|
|
562
|
+
throw new Error(`doorbell-source on ${deviceId}: refused — ${refused.join("; ")}`);
|
|
563
|
+
}
|
|
564
|
+
await deps.persistSources(deviceId, value.deviceIds);
|
|
565
|
+
return {
|
|
566
|
+
applied: true,
|
|
567
|
+
reload: "none"
|
|
568
|
+
};
|
|
569
|
+
},
|
|
570
|
+
candidates: (deviceId) => candidatesFor(deps, deviceId)
|
|
571
|
+
})],
|
|
572
|
+
hostTypeOf: async (deviceId) => (await deps.getDevice(deviceId))?.type ?? null,
|
|
573
|
+
logger: deps.logger
|
|
574
|
+
});
|
|
575
|
+
}
|
|
576
|
+
//#endregion
|
|
357
577
|
//#region src/builtins/doorbell/virtual-doorbell.addon.ts
|
|
358
578
|
/**
|
|
359
579
|
* The addon whose store IS the durable authority on wrapper activations.
|
|
@@ -457,8 +677,42 @@ var VirtualDoorbellAddon = class extends require_dist.BaseAddon {
|
|
|
457
677
|
getDeviceLiveContribution: async () => null,
|
|
458
678
|
applyDeviceSettingsPatch: (input) => this.saveDeviceSettingsPatch(input.deviceId, input.patch)
|
|
459
679
|
}
|
|
680
|
+
}, {
|
|
681
|
+
capability: require_dist.deviceExtensionCapability,
|
|
682
|
+
provider: this.buildExtensionProvider()
|
|
460
683
|
}];
|
|
461
684
|
}
|
|
685
|
+
/**
|
|
686
|
+
* The `device-extension` provider: the same store and the same save path as
|
|
687
|
+
* the settings section below (see `doorbell-extension-slot.ts`).
|
|
688
|
+
*/
|
|
689
|
+
buildExtensionProvider() {
|
|
690
|
+
const toSlotDevice = (d) => ({
|
|
691
|
+
id: d.id,
|
|
692
|
+
name: d.name,
|
|
693
|
+
type: d.type,
|
|
694
|
+
location: d.location,
|
|
695
|
+
parentDeviceId: d.parentDeviceId
|
|
696
|
+
});
|
|
697
|
+
return createDoorbellExtensionProvider({
|
|
698
|
+
ownAddonId: this.ownAddonId,
|
|
699
|
+
logger: this.ctx.logger,
|
|
700
|
+
readSources: (deviceId) => this.readDeviceSources(deviceId),
|
|
701
|
+
persistSources: (deviceId, deviceIds) => this.persistDoorbellSources(deviceId, deviceIds),
|
|
702
|
+
getDevice: async (deviceId) => {
|
|
703
|
+
const found = await this.ctx.api.deviceManager.getDevice.query({ deviceId });
|
|
704
|
+
return found ? toSlotDevice(found) : null;
|
|
705
|
+
},
|
|
706
|
+
listDevices: async (deviceIds) => (await this.ctx.api.deviceManager.listAll.query({
|
|
707
|
+
projection: "slim",
|
|
708
|
+
deviceIds: [...deviceIds]
|
|
709
|
+
})).map(toSlotDevice),
|
|
710
|
+
readBindingMirror: (deviceId) => this.bindingMirror.get(deviceId),
|
|
711
|
+
readDurableBinding: (deviceId) => this.readDurableAuthority(deviceId),
|
|
712
|
+
sourceCapableDeviceIds: () => this.ctx.api.deviceManager.listDeviceIdsByCaps.query({ capNames: [...require_dist.SOURCE_CAPS] }),
|
|
713
|
+
listFleet: async () => (await this.ctx.api.deviceManager.listAll.query({ projection: "slim" })).map(toSlotDevice)
|
|
714
|
+
});
|
|
715
|
+
}
|
|
462
716
|
async handleStateChanged(data) {
|
|
463
717
|
if (!require_dist.isSourceCap(data.capName)) return;
|
|
464
718
|
await this.ensureAssignments();
|
|
@@ -676,38 +930,43 @@ var VirtualDoorbellAddon = class extends require_dist.BaseAddon {
|
|
|
676
930
|
*/
|
|
677
931
|
async ringsDespiteUnboundMirror(cameraId, binding) {
|
|
678
932
|
const authority = await this.readDurableAuthority(cameraId);
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
933
|
+
const verdict = ringVerdict(binding, authority);
|
|
934
|
+
switch (verdict) {
|
|
935
|
+
case "rings":
|
|
936
|
+
this.bindingMirror.set(cameraId, "bound");
|
|
937
|
+
this.ctx.logger.warn("virtual-doorbell: ring RESCUED — the mirror said unbound but the durable bindings store still names this wrapper; the mirror has been repaired", {
|
|
938
|
+
tags: { deviceId: cameraId },
|
|
939
|
+
meta: {
|
|
940
|
+
bindingState: binding,
|
|
941
|
+
ownAddonId: this.ownAddonId
|
|
942
|
+
}
|
|
943
|
+
});
|
|
944
|
+
return verdictRings(verdict);
|
|
945
|
+
case "unknown":
|
|
946
|
+
this.ctx.logger.warn("virtual-doorbell: ringing on an unbound mirror — the durable bindings store could not answer, and an unanswerable store never destroys a one-shot press", {
|
|
947
|
+
tags: { deviceId: cameraId },
|
|
948
|
+
meta: {
|
|
949
|
+
bindingState: binding,
|
|
950
|
+
ownAddonId: this.ownAddonId
|
|
951
|
+
}
|
|
952
|
+
});
|
|
953
|
+
return verdictRings(verdict);
|
|
954
|
+
case "silent": {
|
|
955
|
+
const derived = await this.readDerivedBinding(cameraId);
|
|
956
|
+
this.ctx.logger.warn("virtual-doorbell: ring DROPPED — the wrapper is not bound to this camera, but a trigger source is still configured for it", {
|
|
957
|
+
tags: { deviceId: cameraId },
|
|
958
|
+
meta: {
|
|
959
|
+
bindingState: binding,
|
|
960
|
+
ownAddonId: this.ownAddonId,
|
|
961
|
+
durableAuthority: authority,
|
|
962
|
+
entries: derived === null ? "unreadable" : summariseEntries(derived.entries)
|
|
963
|
+
}
|
|
964
|
+
});
|
|
965
|
+
return verdictRings(verdict);
|
|
708
966
|
}
|
|
709
|
-
|
|
710
|
-
|
|
967
|
+
case "rings-pending":
|
|
968
|
+
case "rings-unconfirmed": return verdictRings(verdict);
|
|
969
|
+
}
|
|
711
970
|
}
|
|
712
971
|
/**
|
|
713
972
|
* Record a binding we observed FIRST-HAND. Both callers are cap methods
|
|
@@ -853,13 +1112,23 @@ var VirtualDoorbellAddon = class extends require_dist.BaseAddon {
|
|
|
853
1112
|
async saveDeviceSettingsPatch(deviceId, patch) {
|
|
854
1113
|
if (!this.ctx.settings) throw new Error("[virtual-doorbell] settings store unavailable — cannot persist per-device settings");
|
|
855
1114
|
this.confirmBindingFromCapCall(deviceId);
|
|
856
|
-
if ("doorbellSources" in patch)
|
|
857
|
-
const next = applyDoorbellSourcesPatch(await this.ctx.settings.readDeviceStore(deviceId), patch[KEY_SOURCES]);
|
|
858
|
-
await this.ctx.settings.writeDeviceStore(deviceId, next);
|
|
859
|
-
this.invalidateAssignments();
|
|
860
|
-
}
|
|
1115
|
+
if ("doorbellSources" in patch) await this.persistDoorbellSources(deviceId, patch[KEY_SOURCES]);
|
|
861
1116
|
return { success: true };
|
|
862
1117
|
}
|
|
1118
|
+
/**
|
|
1119
|
+
* THE write path for a camera's trigger sources — the settings save above
|
|
1120
|
+
* and the `doorbell-source` extension slot both land here (D648), so the
|
|
1121
|
+
* two surfaces cannot store different shapes: normalise to
|
|
1122
|
+
* `doorbellSources: [{deviceId}]`, strip the legacy keys, and invalidate the
|
|
1123
|
+
* assignment cache so the next rise (and the binding mirror's next
|
|
1124
|
+
* refresh, off the ring path) sees the new wiring.
|
|
1125
|
+
*/
|
|
1126
|
+
async persistDoorbellSources(deviceId, value) {
|
|
1127
|
+
if (!this.ctx.settings) throw new Error("[virtual-doorbell] settings store unavailable — cannot persist per-device settings");
|
|
1128
|
+
const current = await this.ctx.settings.readDeviceStore(deviceId);
|
|
1129
|
+
await this.ctx.settings.writeDeviceStore(deviceId, applyDoorbellSourcesPatch(current, value));
|
|
1130
|
+
this.invalidateAssignments();
|
|
1131
|
+
}
|
|
863
1132
|
};
|
|
864
1133
|
//#endregion
|
|
865
1134
|
exports.VirtualDoorbellAddon = VirtualDoorbellAddon;
|