@camstack/system 1.2.313 → 1.2.315
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/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/composer/claim-gate.d.ts +32 -0
- package/dist/builtins/composer/composed-device-set.d.ts +38 -0
- package/dist/builtins/composer/composed-device.d.ts +4 -3
- package/dist/builtins/composer/composer-grafts.d.ts +54 -0
- package/dist/builtins/composer/composer-plan.d.ts +96 -0
- package/dist/builtins/composer/composer-verdict.d.ts +19 -0
- package/dist/builtins/composer/composer.addon.js +2717 -477
- package/dist/builtins/composer/composer.addon.mjs +2722 -482
- package/dist/builtins/composer/composer.d.ts +71 -52
- package/dist/builtins/composer/composition-runtime.d.ts +93 -9
- package/dist/builtins/composer/confirmed-seed.d.ts +32 -0
- package/dist/builtins/composer/existing-target.d.ts +96 -0
- package/dist/builtins/composer/field-record.d.ts +50 -0
- package/dist/builtins/composer/graft-host.d.ts +47 -0
- package/dist/builtins/composer/held-claims.d.ts +12 -0
- package/dist/builtins/composer/owned-fields-target.d.ts +55 -0
- package/dist/builtins/composer/slice-assembler.d.ts +10 -0
- package/dist/builtins/composer/source-readings.d.ts +122 -0
- package/dist/builtins/composer/source-tracker.d.ts +35 -42
- package/dist/builtins/console-logging/index.js +1 -1
- package/dist/builtins/console-logging/index.mjs +1 -1
- package/dist/builtins/core-blocks/composition-api.d.ts +7 -4
- package/dist/builtins/core-blocks/composition-peers.d.ts +9 -0
- package/dist/builtins/core-blocks/composition-sources.d.ts +16 -3
- package/dist/builtins/core-blocks/core-block-store.d.ts +8 -1
- package/dist/builtins/core-blocks/core-blocks.addon.d.ts +4 -3
- package/dist/builtins/core-blocks/core-blocks.addon.js +255 -79
- package/dist/builtins/core-blocks/core-blocks.addon.mjs +255 -79
- package/dist/builtins/device-manager/claimed-status-overlay.d.ts +12 -0
- package/dist/builtins/device-manager/device-manager.addon.d.ts +16 -0
- package/dist/builtins/device-manager/device-manager.addon.js +1825 -317
- package/dist/builtins/device-manager/device-manager.addon.mjs +1825 -317
- package/dist/builtins/device-manager/device-provider-context.d.ts +24 -2
- package/dist/builtins/device-manager/device-row-store.d.ts +2 -0
- package/dist/builtins/device-manager/device-state-claim-views.d.ts +77 -0
- package/dist/builtins/device-manager/device-state-claims.d.ts +43 -0
- package/dist/builtins/device-manager/device-state-mirror.d.ts +145 -61
- package/dist/builtins/device-manager/field-claims-index.d.ts +69 -0
- package/dist/builtins/device-manager/field-ownership.d.ts +53 -0
- package/dist/builtins/device-manager/migrate-device.d.ts +27 -0
- package/dist/builtins/device-manager/migrate-guard.d.ts +7 -0
- package/dist/builtins/device-manager/migrate-hardware-state.d.ts +55 -0
- package/dist/builtins/device-manager/migration-refused.d.ts +23 -0
- package/dist/builtins/device-manager/mirror-row-writer.d.ts +138 -0
- package/dist/builtins/device-manager/runtime-state-persist-gate.d.ts +4 -0
- package/dist/builtins/doorbell/binding-mirror.d.ts +1 -0
- package/dist/builtins/doorbell/doorbell-composition-migration.d.ts +59 -0
- package/dist/builtins/doorbell/virtual-doorbell.addon.d.ts +3 -0
- package/dist/builtins/doorbell/virtual-doorbell.addon.js +315 -10
- package/dist/builtins/doorbell/virtual-doorbell.addon.mjs +315 -10
- 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-CHTiHbsv.mjs → child-cap-dispatch-AAltfBUH.mjs} +1484 -1417
- package/dist/{child-cap-dispatch-Dyn_7ezH.js → child-cap-dispatch-DzcUd_ct.js} +1503 -1424
- package/dist/composition-sources-DYqQLRzF.js +122 -0
- package/dist/composition-sources-yonsRneP.mjs +111 -0
- package/dist/{dist-CkRwnhbo.js → dist-DVKv5i-e.js} +5396 -4331
- package/dist/{dist-Cj7pJmXq.mjs → dist-MzYJVCLE.mjs} +5330 -4331
- package/dist/index.js +137 -8
- package/dist/index.mjs +133 -8
- package/dist/kernel/capability-registry.d.ts +43 -1
- package/dist/kernel/index.d.ts +3 -1
- package/dist/kernel/moleculer/device-cap-proxy.d.ts +0 -10
- package/dist/kernel/status-overlay.d.ts +18 -0
- package/dist/kernel/transport/claimed-get-status.d.ts +15 -0
- package/dist/kernel/transport/index.d.ts +2 -0
- package/dist/kernel/transport/local-child-registry.d.ts +23 -0
- package/dist/kernel/transport/parent-unowned-call.d.ts +25 -0
- package/dist/{retired-settings-keys-C1duPikd.mjs → retired-settings-keys-8yY2sTc-.mjs} +1 -1
- package/dist/{retired-settings-keys-CGSuXhP8.js → retired-settings-keys-iY5I0nKM.js} +1 -1
- package/package.json +1 -1
|
@@ -3,8 +3,9 @@ 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-DVKv5i-e.js");
|
|
7
7
|
const require_device_state_changed = require("../../device-state-changed-Bk7cvTnz.js");
|
|
8
|
+
let zod = require("zod");
|
|
8
9
|
//#region src/builtins/doorbell/trigger-engine.ts
|
|
9
10
|
/**
|
|
10
11
|
* Pure trigger logic for the virtual-doorbell builtin — edge detection over
|
|
@@ -242,7 +243,7 @@ function entriesBindWrapper(entries, capName, ownAddonId) {
|
|
|
242
243
|
function summariseEntries(entries) {
|
|
243
244
|
return entries.map((entry) => `${entry.capName}:${entry.kind}:${entry.providerAddonId}`);
|
|
244
245
|
}
|
|
245
|
-
function isRecord$
|
|
246
|
+
function isRecord$2(value) {
|
|
246
247
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
247
248
|
}
|
|
248
249
|
/**
|
|
@@ -258,13 +259,13 @@ function isRecord$1(value) {
|
|
|
258
259
|
* from an empty snapshot, whereas a genuine unbind leaves the key present.
|
|
259
260
|
*/
|
|
260
261
|
function readDurableWrapperBinding(record, deviceId, capName, ownAddonId) {
|
|
261
|
-
if (!isRecord$
|
|
262
|
+
if (!isRecord$2(record)) return "unreadable";
|
|
262
263
|
const deviceBindings = record["deviceBindings"];
|
|
263
|
-
if (!isRecord$
|
|
264
|
+
if (!isRecord$2(deviceBindings)) return "unreadable";
|
|
264
265
|
const perDevice = deviceBindings[String(deviceId)];
|
|
265
|
-
if (!isRecord$
|
|
266
|
+
if (!isRecord$2(perDevice)) return "unbound";
|
|
266
267
|
const activation = perDevice[capName];
|
|
267
|
-
if (!isRecord$
|
|
268
|
+
if (!isRecord$2(activation)) return "unbound";
|
|
268
269
|
const wrapperAddonId = activation["wrapperAddonId"];
|
|
269
270
|
if (typeof wrapperAddonId !== "string") return "unbound";
|
|
270
271
|
return require_dist.isSameAddonId(wrapperAddonId, ownAddonId) ? "bound" : "unbound";
|
|
@@ -319,7 +320,7 @@ var KEY_SOURCES = "doorbellSources";
|
|
|
319
320
|
var LEGACY_KEY_SOURCE = "doorbellSourceDeviceId";
|
|
320
321
|
var LEGACY_KEY_TRIGGER = "doorbellTrigger";
|
|
321
322
|
var LEGACY_KEY_EVENT_TYPE = "doorbellEventType";
|
|
322
|
-
function isRecord(value) {
|
|
323
|
+
function isRecord$1(value) {
|
|
323
324
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
324
325
|
}
|
|
325
326
|
/** Coerce a persisted / form-supplied id (number or numeric string) to a
|
|
@@ -337,7 +338,7 @@ function collectSources(items) {
|
|
|
337
338
|
const out = [];
|
|
338
339
|
const seen = /* @__PURE__ */ new Set();
|
|
339
340
|
for (const item of items) {
|
|
340
|
-
const id = isRecord(item) ? coerceDeviceId(item["deviceId"]) : coerceDeviceId(item);
|
|
341
|
+
const id = isRecord$1(item) ? coerceDeviceId(item["deviceId"]) : coerceDeviceId(item);
|
|
341
342
|
if (id !== null && !seen.has(id)) {
|
|
342
343
|
seen.add(id);
|
|
343
344
|
out.push({ deviceId: id });
|
|
@@ -538,7 +539,6 @@ function createDoorbellExtensionProvider(deps) {
|
|
|
538
539
|
const ids = (await deps.readSources(deviceId)).map((s) => s.deviceId);
|
|
539
540
|
if (ids.length === 0) return {
|
|
540
541
|
authority: DOORBELL_SOURCES_AUTHORITY,
|
|
541
|
-
enabled: null,
|
|
542
542
|
activation: null,
|
|
543
543
|
value: {
|
|
544
544
|
slot: "doorbell-source",
|
|
@@ -550,7 +550,6 @@ function createDoorbellExtensionProvider(deps) {
|
|
|
550
550
|
const byId = new Map(devices.map((d) => [d.id, d]));
|
|
551
551
|
return {
|
|
552
552
|
authority: DOORBELL_SOURCES_AUTHORITY,
|
|
553
|
-
enabled: null,
|
|
554
553
|
activation,
|
|
555
554
|
value: {
|
|
556
555
|
slot: "doorbell-source",
|
|
@@ -581,6 +580,278 @@ function createDoorbellExtensionProvider(deps) {
|
|
|
581
580
|
});
|
|
582
581
|
}
|
|
583
582
|
//#endregion
|
|
583
|
+
//#region src/builtins/doorbell/doorbell-composition-migration.ts
|
|
584
|
+
var DOORBELL_COMPOSITION_MIGRATION_STATE_KEY = "doorbell-source-composition-migration-v1";
|
|
585
|
+
var DOORBELL_COMPOSITION_MIGRATION_CALLER = {
|
|
586
|
+
userId: "system:doorbell-source-migration",
|
|
587
|
+
isAdmin: true
|
|
588
|
+
};
|
|
589
|
+
function buildDoorbellCompositionFeature(input) {
|
|
590
|
+
const sources = [...input.sources].toSorted((a, b) => `${a.source.addonId}/${a.source.stableId}#${a.cap}`.localeCompare(`${b.source.addonId}/${b.source.stableId}#${b.cap}`));
|
|
591
|
+
if (sources.length === 0) throw new Error("doorbell composition needs at least one source");
|
|
592
|
+
const edgeBindings = {};
|
|
593
|
+
sources.forEach((entry, index) => {
|
|
594
|
+
edgeBindings[`active${index}`] = {
|
|
595
|
+
kind: "from",
|
|
596
|
+
source: entry.source,
|
|
597
|
+
cap: entry.cap,
|
|
598
|
+
fieldPath: entry.activeField
|
|
599
|
+
};
|
|
600
|
+
edgeBindings[`changed${index}`] = {
|
|
601
|
+
kind: "from",
|
|
602
|
+
source: entry.source,
|
|
603
|
+
cap: entry.cap,
|
|
604
|
+
fieldPath: entry.changedAtField
|
|
605
|
+
};
|
|
606
|
+
});
|
|
607
|
+
return {
|
|
608
|
+
kind: "fields",
|
|
609
|
+
cap: "doorbell",
|
|
610
|
+
enabled: input.enabled,
|
|
611
|
+
fields: {
|
|
612
|
+
lastPressedAt: {
|
|
613
|
+
kind: "expression",
|
|
614
|
+
expr: `latest(${sources.map((_, index) => `rose(active${index}, changed${index}, 2000)`).join(", ")})`,
|
|
615
|
+
bindings: edgeBindings
|
|
616
|
+
},
|
|
617
|
+
pressCountSinceStart: {
|
|
618
|
+
kind: "expression",
|
|
619
|
+
expr: "count(lastPressedAt)",
|
|
620
|
+
bindings: { lastPressedAt: {
|
|
621
|
+
kind: "from",
|
|
622
|
+
source: {
|
|
623
|
+
addonId: input.camera.addonId,
|
|
624
|
+
stableId: input.camera.stableId
|
|
625
|
+
},
|
|
626
|
+
cap: "doorbell",
|
|
627
|
+
fieldPath: "lastPressedAt"
|
|
628
|
+
} }
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
};
|
|
632
|
+
}
|
|
633
|
+
function isRecord(value) {
|
|
634
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
635
|
+
}
|
|
636
|
+
function stableValue(value) {
|
|
637
|
+
if (Array.isArray(value)) return value.map(stableValue);
|
|
638
|
+
if (!isRecord(value)) return value;
|
|
639
|
+
return Object.fromEntries(Object.keys(value).toSorted().map((key) => [key, stableValue(value[key])]));
|
|
640
|
+
}
|
|
641
|
+
function sameJson(a, b) {
|
|
642
|
+
return JSON.stringify(stableValue(a)) === JSON.stringify(stableValue(b));
|
|
643
|
+
}
|
|
644
|
+
function withoutEnabled(feature) {
|
|
645
|
+
const { enabled: _enabled, ...rest } = feature;
|
|
646
|
+
return rest;
|
|
647
|
+
}
|
|
648
|
+
function bindingHasCap(entries, cap) {
|
|
649
|
+
return entries.some((entry) => entry.capName === cap);
|
|
650
|
+
}
|
|
651
|
+
function migrationSources(camera, configured, devicesById, bindingsById) {
|
|
652
|
+
const sources = [];
|
|
653
|
+
for (const configuredSource of configured) {
|
|
654
|
+
const device = devicesById.get(configuredSource.deviceId);
|
|
655
|
+
const entries = bindingsById.get(configuredSource.deviceId);
|
|
656
|
+
if (device === void 0 || entries === void 0) throw new Error(`source device ${configuredSource.deviceId} could not be resolved and read`);
|
|
657
|
+
const ref = {
|
|
658
|
+
addonId: device.addonId,
|
|
659
|
+
stableId: device.stableId
|
|
660
|
+
};
|
|
661
|
+
const supportedCaps = Object.keys(require_dist.SOURCE_CAP_ACTIVE_FIELD).filter((cap) => Object.hasOwn(require_dist.SOURCE_CAP_CHANGED_AT_FIELD, cap) && bindingHasCap(entries, cap)).toSorted();
|
|
662
|
+
if (supportedCaps.length === 0) throw new Error(`source device ${configuredSource.deviceId} has no supported source capability`);
|
|
663
|
+
for (const cap of supportedCaps) {
|
|
664
|
+
const activeField = require_dist.SOURCE_CAP_ACTIVE_FIELD[cap];
|
|
665
|
+
const changedAtField = require_dist.SOURCE_CAP_CHANGED_AT_FIELD[cap];
|
|
666
|
+
if (activeField === void 0 || changedAtField === void 0) throw new Error(`source capability ${cap} has no transition field mapping`);
|
|
667
|
+
sources.push({
|
|
668
|
+
source: ref,
|
|
669
|
+
cap,
|
|
670
|
+
activeField,
|
|
671
|
+
changedAtField
|
|
672
|
+
});
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
if (sources.length === 0) throw new Error(`camera ${camera.id} has no resolvable doorbell sources`);
|
|
676
|
+
return sources;
|
|
677
|
+
}
|
|
678
|
+
function wrapperIsActive(entries) {
|
|
679
|
+
return entries.some((entry) => entry.capName === "doorbell" && entry.kind === "wrapped" && entry.providerAddonId === "virtual-doorbell");
|
|
680
|
+
}
|
|
681
|
+
function composerIsBound(entries) {
|
|
682
|
+
return entries.some((entry) => entry.capName === "doorbell" && (entry.providerAddonId === "composer" || entry.nativeAddonId === "composer"));
|
|
683
|
+
}
|
|
684
|
+
var DoorbellCompositionMigration = class {
|
|
685
|
+
deps;
|
|
686
|
+
rerunRequested = false;
|
|
687
|
+
inFlight = null;
|
|
688
|
+
stopped = false;
|
|
689
|
+
constructor(deps) {
|
|
690
|
+
this.deps = deps;
|
|
691
|
+
}
|
|
692
|
+
request(reason) {
|
|
693
|
+
if (this.stopped) return;
|
|
694
|
+
this.rerunRequested = true;
|
|
695
|
+
if (this.inFlight !== null) return;
|
|
696
|
+
this.inFlight = this.runUntilQuiet(reason).finally(() => {
|
|
697
|
+
this.inFlight = null;
|
|
698
|
+
if (this.rerunRequested && !this.stopped) this.request("coalesced lifecycle event");
|
|
699
|
+
});
|
|
700
|
+
}
|
|
701
|
+
async waitForIdle() {
|
|
702
|
+
while (this.inFlight !== null || this.rerunRequested) {
|
|
703
|
+
const current = this.inFlight;
|
|
704
|
+
if (current !== null) await current;
|
|
705
|
+
else await Promise.resolve();
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
shutdown() {
|
|
709
|
+
this.stopped = true;
|
|
710
|
+
this.rerunRequested = false;
|
|
711
|
+
}
|
|
712
|
+
async runUntilQuiet(reason) {
|
|
713
|
+
while (this.rerunRequested && !this.stopped) {
|
|
714
|
+
this.rerunRequested = false;
|
|
715
|
+
try {
|
|
716
|
+
await this.runAttempt(reason);
|
|
717
|
+
} catch (err) {
|
|
718
|
+
this.deps.logger.warn("doorbell-source migration: attempt failed", { meta: {
|
|
719
|
+
reason,
|
|
720
|
+
error: err instanceof Error ? err.message : String(err)
|
|
721
|
+
} });
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
async runAttempt(reason) {
|
|
726
|
+
if (await this.deps.isComplete()) return;
|
|
727
|
+
const cameras = await this.deps.listCameras();
|
|
728
|
+
const configuredByCamera = /* @__PURE__ */ new Map();
|
|
729
|
+
const failedReads = /* @__PURE__ */ new Set();
|
|
730
|
+
for (const camera of cameras) try {
|
|
731
|
+
const sources = await this.deps.readSources(camera.id);
|
|
732
|
+
if (sources.length > 0) configuredByCamera.set(camera.id, sources);
|
|
733
|
+
} catch (err) {
|
|
734
|
+
failedReads.add(camera.id);
|
|
735
|
+
this.warnCamera(camera.id, `could not read source settings (${errorText(err)})`);
|
|
736
|
+
}
|
|
737
|
+
if (failedReads.size > 0) return;
|
|
738
|
+
if (configuredByCamera.size === 0) {
|
|
739
|
+
await this.deps.markComplete();
|
|
740
|
+
return;
|
|
741
|
+
}
|
|
742
|
+
const camerasById = new Map(cameras.map((camera) => [camera.id, camera]));
|
|
743
|
+
const sourceIds = [...new Set([...configuredByCamera.values()].flatMap((sources) => sources.map((s) => s.deviceId)))].toSorted((a, b) => a - b);
|
|
744
|
+
const sourceDevices = await this.deps.listSourceDevices(sourceIds);
|
|
745
|
+
const devicesById = new Map(sourceDevices.map((device) => [device.id, device]));
|
|
746
|
+
const bindingIds = [...new Set([...configuredByCamera.keys(), ...sourceIds])].toSorted((a, b) => a - b);
|
|
747
|
+
const bindingRows = await this.deps.getBindingsBatch(bindingIds);
|
|
748
|
+
const bindingsById = new Map(bindingRows.map((row) => [row.deviceId, row.entries]));
|
|
749
|
+
const coreBlocks = this.deps.coreBlocks();
|
|
750
|
+
if (coreBlocks === null) {
|
|
751
|
+
for (const cameraId of configuredByCamera.keys()) this.warnCamera(cameraId, "core-blocks provider is not ready");
|
|
752
|
+
return;
|
|
753
|
+
}
|
|
754
|
+
let allSettled = true;
|
|
755
|
+
for (const [cameraId, configured] of configuredByCamera) {
|
|
756
|
+
const camera = camerasById.get(cameraId);
|
|
757
|
+
const entries = bindingsById.get(cameraId);
|
|
758
|
+
if (camera === void 0 || entries === void 0) {
|
|
759
|
+
allSettled = false;
|
|
760
|
+
this.warnCamera(cameraId, "camera or binding row could not be read");
|
|
761
|
+
continue;
|
|
762
|
+
}
|
|
763
|
+
try {
|
|
764
|
+
const sources = migrationSources(camera, configured, devicesById, bindingsById);
|
|
765
|
+
const cameraRef = {
|
|
766
|
+
addonId: camera.addonId,
|
|
767
|
+
stableId: camera.stableId
|
|
768
|
+
};
|
|
769
|
+
const read = await coreBlocks.getCustomization({ ...cameraRef });
|
|
770
|
+
if (read.state !== "loaded") throw new Error(`customizations are not loaded (${read.reason})`);
|
|
771
|
+
const view = read.view;
|
|
772
|
+
if (view !== null && !view.block.enabled) throw new Error(`customization block ${view.block.id} is disabled`);
|
|
773
|
+
const existingComposition = view?.block.source?.kind === "composition" ? view.block.source.composition : null;
|
|
774
|
+
if (view !== null && existingComposition === null) throw new Error(`customization block ${view.block.id} has no readable composition`);
|
|
775
|
+
const existingDoorbell = existingComposition?.features.find((feature) => feature.cap === "doorbell");
|
|
776
|
+
const wrapperActive = wrapperIsActive(entries);
|
|
777
|
+
const composerBound = composerIsBound(entries);
|
|
778
|
+
const crashRecovery = !wrapperActive && composerBound && existingDoorbell?.kind === "fields" && existingDoorbell.enabled === true;
|
|
779
|
+
const enabled = wrapperActive || crashRecovery;
|
|
780
|
+
const canonical = buildDoorbellCompositionFeature({
|
|
781
|
+
camera: cameraRef,
|
|
782
|
+
sources,
|
|
783
|
+
enabled
|
|
784
|
+
});
|
|
785
|
+
let needsSave = view === null;
|
|
786
|
+
let composition;
|
|
787
|
+
if (existingComposition === null) composition = {
|
|
788
|
+
target: {
|
|
789
|
+
kind: "existing",
|
|
790
|
+
device: cameraRef
|
|
791
|
+
},
|
|
792
|
+
features: [canonical]
|
|
793
|
+
};
|
|
794
|
+
else if (existingDoorbell === void 0) {
|
|
795
|
+
composition = {
|
|
796
|
+
...existingComposition,
|
|
797
|
+
features: [...existingComposition.features, canonical]
|
|
798
|
+
};
|
|
799
|
+
needsSave = true;
|
|
800
|
+
} else if (existingDoorbell.kind === "fields" && sameJson(withoutEnabled(existingDoorbell), withoutEnabled(canonical))) {
|
|
801
|
+
composition = {
|
|
802
|
+
...existingComposition,
|
|
803
|
+
features: existingComposition.features.map((feature) => feature.cap === "doorbell" ? canonical : feature)
|
|
804
|
+
};
|
|
805
|
+
needsSave ||= existingDoorbell.enabled !== canonical.enabled;
|
|
806
|
+
} else throw new Error("an existing doorbell feature is not owned by this migration");
|
|
807
|
+
if (needsSave) if (view === null) await coreBlocks.createComposition({
|
|
808
|
+
block: {
|
|
809
|
+
enabled: true,
|
|
810
|
+
composition
|
|
811
|
+
},
|
|
812
|
+
caller: DOORBELL_COMPOSITION_MIGRATION_CALLER
|
|
813
|
+
});
|
|
814
|
+
else await coreBlocks.updateComposition({
|
|
815
|
+
blockId: view.block.id,
|
|
816
|
+
expectedUpdatedAt: view.block.updatedAt,
|
|
817
|
+
block: { composition },
|
|
818
|
+
caller: DOORBELL_COMPOSITION_MIGRATION_CALLER
|
|
819
|
+
});
|
|
820
|
+
if (!enabled) continue;
|
|
821
|
+
if (!composerBound) {
|
|
822
|
+
allSettled = false;
|
|
823
|
+
continue;
|
|
824
|
+
}
|
|
825
|
+
if (wrapperActive) {
|
|
826
|
+
await this.deps.setWrapperActive({
|
|
827
|
+
deviceId: camera.id,
|
|
828
|
+
capName: "doorbell",
|
|
829
|
+
wrapperAddonId: "virtual-doorbell",
|
|
830
|
+
active: false
|
|
831
|
+
});
|
|
832
|
+
this.deps.logger.info("doorbell-source migration: composer owns doorbell; wrapper deactivated", {
|
|
833
|
+
tags: { deviceId: camera.id },
|
|
834
|
+
meta: { reason }
|
|
835
|
+
});
|
|
836
|
+
}
|
|
837
|
+
} catch (err) {
|
|
838
|
+
allSettled = false;
|
|
839
|
+
this.warnCamera(cameraId, errorText(err));
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
if (allSettled) await this.deps.markComplete();
|
|
843
|
+
}
|
|
844
|
+
warnCamera(cameraId, reason) {
|
|
845
|
+
this.deps.logger.warn("doorbell-source migration: camera not migrated", {
|
|
846
|
+
tags: { deviceId: cameraId },
|
|
847
|
+
meta: { reason }
|
|
848
|
+
});
|
|
849
|
+
}
|
|
850
|
+
};
|
|
851
|
+
function errorText(err) {
|
|
852
|
+
return err instanceof Error ? err.message : String(err);
|
|
853
|
+
}
|
|
854
|
+
//#endregion
|
|
584
855
|
//#region src/builtins/doorbell/virtual-doorbell.addon.ts
|
|
585
856
|
/**
|
|
586
857
|
* The addon whose store IS the durable authority on wrapper activations.
|
|
@@ -630,6 +901,8 @@ function parseDoorbellSlice(slice) {
|
|
|
630
901
|
*/
|
|
631
902
|
var VirtualDoorbellAddon = class extends require_dist.BaseAddon {
|
|
632
903
|
engine;
|
|
904
|
+
compositionMigration = null;
|
|
905
|
+
providerRegisteredUnsubscribe = null;
|
|
633
906
|
/** Monotonic guard: assignments are reloaded lazily on trigger traffic. */
|
|
634
907
|
assignmentsLoadedAt = 0;
|
|
635
908
|
assignmentsLoading = null;
|
|
@@ -654,6 +927,32 @@ var VirtualDoorbellAddon = class extends require_dist.BaseAddon {
|
|
|
654
927
|
if (!data) return;
|
|
655
928
|
this.handleStateChanged(data);
|
|
656
929
|
});
|
|
930
|
+
const migrationState = this.state(DOORBELL_COMPOSITION_MIGRATION_STATE_KEY, zod.z.object({ complete: zod.z.boolean() }), { complete: false });
|
|
931
|
+
this.compositionMigration = new DoorbellCompositionMigration({
|
|
932
|
+
logger: this.ctx.logger.child("doorbell-source-migration"),
|
|
933
|
+
listCameras: async () => this.ctx.api.deviceManager.listAll.query({
|
|
934
|
+
isCamera: true,
|
|
935
|
+
projection: "slim"
|
|
936
|
+
}),
|
|
937
|
+
listSourceDevices: async (deviceIds) => this.ctx.api.deviceManager.listAll.query({
|
|
938
|
+
deviceIds: [...deviceIds],
|
|
939
|
+
projection: "slim"
|
|
940
|
+
}),
|
|
941
|
+
readSources: (cameraId) => this.readDeviceSources(cameraId),
|
|
942
|
+
getBindingsBatch: async (deviceIds) => this.ctx.api.deviceManager.getBindingsBatch.query({ deviceIds: [...deviceIds] }),
|
|
943
|
+
coreBlocks: () => this.ctx.capabilities?.get("core-blocks") ?? null,
|
|
944
|
+
setWrapperActive: (input) => this.ctx.api.deviceManager.setWrapperActive.mutate(input),
|
|
945
|
+
isComplete: async () => (await migrationState.get()).complete,
|
|
946
|
+
markComplete: async () => migrationState.set({ complete: true })
|
|
947
|
+
});
|
|
948
|
+
this.providerRegisteredUnsubscribe = this.ctx.eventBus.subscribe({ category: "capability:provider-registered" }, (event) => {
|
|
949
|
+
if (event.data["capability"] !== "core-blocks") return;
|
|
950
|
+
this.compositionMigration?.request("core-blocks registered");
|
|
951
|
+
});
|
|
952
|
+
this.subscribe({ category: require_dist.EventCategory.DeviceBindingsChanged }, () => {
|
|
953
|
+
this.compositionMigration?.request("device bindings changed");
|
|
954
|
+
});
|
|
955
|
+
this.compositionMigration.request("addon initialized");
|
|
657
956
|
return [{
|
|
658
957
|
capability: require_dist.doorbellCapability,
|
|
659
958
|
provider: { getStatus: (input) => this.getStatus(input.deviceId) }
|
|
@@ -662,6 +961,12 @@ var VirtualDoorbellAddon = class extends require_dist.BaseAddon {
|
|
|
662
961
|
provider: this.buildExtensionProvider()
|
|
663
962
|
}];
|
|
664
963
|
}
|
|
964
|
+
async onShutdown() {
|
|
965
|
+
this.providerRegisteredUnsubscribe?.();
|
|
966
|
+
this.providerRegisteredUnsubscribe = null;
|
|
967
|
+
this.compositionMigration?.shutdown();
|
|
968
|
+
this.compositionMigration = null;
|
|
969
|
+
}
|
|
665
970
|
/**
|
|
666
971
|
* The `device-extension` provider: the only editor of a camera's trigger
|
|
667
972
|
* devices since D655 (see `doorbell-extension-slot.ts`).
|