@camstack/system 1.2.274 → 1.2.276
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/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/grid-last-frame-store.d.ts +39 -0
- package/dist/builtins/camera-grid/index.js +67 -12
- package/dist/builtins/camera-grid/index.mjs +67 -12
- 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-aggregation.d.ts +0 -6
- package/dist/builtins/device-manager/device-bindings-store.d.ts +12 -2
- package/dist/builtins/device-manager/device-manager.addon.d.ts +4 -0
- package/dist/builtins/device-manager/device-manager.addon.js +177 -58
- package/dist/builtins/device-manager/device-manager.addon.mjs +174 -59
- package/dist/builtins/device-manager/device-meta-types.d.ts +36 -3
- package/dist/builtins/device-manager/device-source-activation.d.ts +70 -0
- package/dist/builtins/doorbell/virtual-doorbell.addon.js +1 -1
- package/dist/builtins/doorbell/virtual-doorbell.addon.mjs +1 -1
- 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 +2 -2
- package/dist/builtins/local-network/local-network.addon.mjs +2 -2
- 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 +26 -6
- package/dist/builtins/snapshot/index.mjs +26 -6
- package/dist/builtins/snapshot/snapshot-durable-store.d.ts +4 -0
- 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/{dist-DchKwh_m.mjs → dist-AHdiIdJH.mjs} +412 -19
- package/dist/{dist-C_4Xt0Ya.js → dist-D_OlSf31.js} +417 -18
- package/dist/index.d.ts +7 -0
- package/dist/index.js +104 -38
- package/dist/index.mjs +102 -40
- package/dist/kernel/capability-registry.d.ts +59 -9
- package/dist/kernel/index.d.ts +1 -0
- package/dist/{retired-settings-keys-DPSHUHMu.mjs → retired-settings-keys-Cp3oG_aH.mjs} +1 -1
- package/dist/{retired-settings-keys-6wZNp7lr.js → retired-settings-keys-LwFyTKun.js} +1 -1
- package/dist/tls/cert-identity.d.ts +22 -0
- package/dist/tls/cert-issue.d.ts +31 -0
- package/dist/tls/cert-manager.d.ts +8 -2
- package/dist/tls/index.d.ts +2 -2
- package/dist/tls-D_wUeOTD.js +12122 -0
- package/dist/tls-IM-v7JV4.mjs +11984 -0
- package/package.json +2 -1
- package/dist/tls/cert-openssl.d.ts +0 -23
- package/dist/tls-2xbgg78V.mjs +0 -752
- package/dist/tls-BFs4PzoW.js +0 -890
|
@@ -3,8 +3,8 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
require("../../chunk-Cek0wNdY.js");
|
|
6
|
-
const require_dist = require("../../dist-
|
|
7
|
-
const require_retired_settings_keys = require("../../retired-settings-keys-
|
|
6
|
+
const require_dist = require("../../dist-D_OlSf31.js");
|
|
7
|
+
const require_retired_settings_keys = require("../../retired-settings-keys-LwFyTKun.js");
|
|
8
8
|
let node_crypto = require("node:crypto");
|
|
9
9
|
let _camstack_types_node = require("@camstack/types/node");
|
|
10
10
|
//#region src/builtins/device-manager/adoption-job-engine.ts
|
|
@@ -762,6 +762,7 @@ async function resolveBindingsForDevice(deps, deviceId, row, pass) {
|
|
|
762
762
|
const seenCaps = /* @__PURE__ */ new Set();
|
|
763
763
|
const resolveRemote = (capName) => deps.remoteNativeCaps.get(deviceId)?.get(capName) ?? resolveRemoteNativeCapFromRegistry(deps, capName, deviceId);
|
|
764
764
|
for (const [capName, { wrapperAddonId }] of Object.entries(perDevice)) {
|
|
765
|
+
if (wrapperAddonId === void 0) continue;
|
|
765
766
|
const hubLocalNative = deps.capabilityRegistry?.getNativeAddonId(capName, deviceId) ?? null;
|
|
766
767
|
const remoteNative = resolveRemote(capName);
|
|
767
768
|
const nativeAddonId = hubLocalNative ?? remoteNative?.addonId ?? "";
|
|
@@ -2526,28 +2527,6 @@ function splitDeviceStoreKeys(patch) {
|
|
|
2526
2527
|
//#endregion
|
|
2527
2528
|
//#region src/builtins/device-manager/device-aggregation.ts
|
|
2528
2529
|
/**
|
|
2529
|
-
* Device-details aggregation + field-update routing for the device-manager
|
|
2530
|
-
* addon. Extracted from `device-manager.addon.ts` as free functions that
|
|
2531
|
-
* receive an explicit `AggregationDeps` context (DI) rather than closing over
|
|
2532
|
-
* the addon instance.
|
|
2533
|
-
*
|
|
2534
|
-
* This module owns the binding-driven settings/live/status aggregators
|
|
2535
|
-
* (`getDeviceAggregate`, `collectSystemDeviceContributions`,
|
|
2536
|
-
* `deriveDeviceConfigContribution`, `buildBaseDeviceSection`,
|
|
2537
|
-
* `getDeviceStatusAggregate`, `resolveDriverConfigSchema`) and the
|
|
2538
|
-
* field-update routing (`updateDeviceField`, `updateDeviceFieldsBatch`,
|
|
2539
|
-
* `applyGroupPatch`, `resolveContributionProvider`, `dispatchDeviceAction`)
|
|
2540
|
-
* plus the wrapper-activation mutators (`setWrapperActive`,
|
|
2541
|
-
* `listWrappersForCap`, `listBindableCapsForDeviceType`).
|
|
2542
|
-
*
|
|
2543
|
-
* Sibling aggregator functions call each other directly (passing `deps`);
|
|
2544
|
-
* cross-domain leaf calls (`getBindings`, `lookupPersistedStableId`,
|
|
2545
|
-
* `resolveRemoteNativeCapFromRegistry`, read/writeBindingsStore) route through
|
|
2546
|
-
* the bindings-store module via `deps.bindingsDeps`. EXACT semantics, ordering,
|
|
2547
|
-
* and side effects of the original class methods are preserved; the addon keeps
|
|
2548
|
-
* thin delegator methods over these functions.
|
|
2549
|
-
*/
|
|
2550
|
-
/**
|
|
2551
2530
|
* One process-wide cache behind every device-details fan-out.
|
|
2552
2531
|
*
|
|
2553
2532
|
* Module-level on purpose: the page re-polls every 2.5 s and a per-call cache
|
|
@@ -3250,39 +3229,6 @@ async function listBindableCapsForDeviceType(deps, input) {
|
|
|
3250
3229
|
wrappers: [...registry.getWrappersForCap(capName)]
|
|
3251
3230
|
}));
|
|
3252
3231
|
}
|
|
3253
|
-
async function setWrapperActive(deps, input) {
|
|
3254
|
-
const storeKey = String(input.deviceId);
|
|
3255
|
-
await deps.bindingsDeps.withAddonStoreWriteLock(async () => {
|
|
3256
|
-
const store = await readBindingsStore(deps.bindingsDeps);
|
|
3257
|
-
const perDevice = { ...store.deviceBindings[storeKey] };
|
|
3258
|
-
if (input.active) perDevice[input.capName] = { wrapperAddonId: input.wrapperAddonId };
|
|
3259
|
-
else perDevice[input.capName] = { wrapperAddonId: null };
|
|
3260
|
-
const nextDeviceBindings = Object.keys(perDevice).length > 0 ? {
|
|
3261
|
-
...store.deviceBindings,
|
|
3262
|
-
[storeKey]: perDevice
|
|
3263
|
-
} : (() => {
|
|
3264
|
-
const { [storeKey]: _drop, ...rest } = store.deviceBindings;
|
|
3265
|
-
return rest;
|
|
3266
|
-
})();
|
|
3267
|
-
await writeBindingsStore(deps.bindingsDeps, { deviceBindings: nextDeviceBindings });
|
|
3268
|
-
});
|
|
3269
|
-
deps.ctx.eventBus.emit({
|
|
3270
|
-
id: (0, node_crypto.randomUUID)(),
|
|
3271
|
-
timestamp: /* @__PURE__ */ new Date(),
|
|
3272
|
-
source: {
|
|
3273
|
-
type: "addon",
|
|
3274
|
-
id: deps.ctx.id
|
|
3275
|
-
},
|
|
3276
|
-
category: require_dist.EventCategory.DeviceBindingsChanged,
|
|
3277
|
-
data: {
|
|
3278
|
-
deviceId: input.deviceId,
|
|
3279
|
-
capName: input.capName,
|
|
3280
|
-
reason: input.active ? "wrapper-activated" : "wrapper-deactivated",
|
|
3281
|
-
addonId: input.wrapperAddonId,
|
|
3282
|
-
nodeId: resolveWrapperNodeId(input.wrapperAddonId)
|
|
3283
|
-
}
|
|
3284
|
-
});
|
|
3285
|
-
}
|
|
3286
3232
|
/**
|
|
3287
3233
|
* Build the wireable-cap entry for one capability definition: the status
|
|
3288
3234
|
* schema's scalar leaf fields plus — for an item-array cap
|
|
@@ -3356,6 +3302,162 @@ async function getWireableFields(deps, input) {
|
|
|
3356
3302
|
return { caps };
|
|
3357
3303
|
}
|
|
3358
3304
|
//#endregion
|
|
3305
|
+
//#region src/builtins/device-manager/device-source-activation.ts
|
|
3306
|
+
/**
|
|
3307
|
+
* Is `source` denied for a device, given the persisted deny set?
|
|
3308
|
+
*
|
|
3309
|
+
* The ONE place the migration rule lives: an entry matches either the source's
|
|
3310
|
+
* own id or the id of the addon that serves it. New writes are always source
|
|
3311
|
+
* ids; an addon id can only be a pre-re-key entry, and it still means "every
|
|
3312
|
+
* source this addon serves here".
|
|
3313
|
+
*/
|
|
3314
|
+
function isSourceDenied(denied, source) {
|
|
3315
|
+
if (denied.includes(source.addonId)) return true;
|
|
3316
|
+
return source.sourceId !== void 0 && denied.includes(source.sourceId);
|
|
3317
|
+
}
|
|
3318
|
+
/**
|
|
3319
|
+
* The deny list after one toggle. Pure, order-preserving, idempotent — a
|
|
3320
|
+
* second "off" on an addon already off changes nothing.
|
|
3321
|
+
*/
|
|
3322
|
+
function nextDisabledSources(current, key, active) {
|
|
3323
|
+
if (active) return current.filter((id) => id !== key);
|
|
3324
|
+
return current.includes(key) ? current : [...current, key];
|
|
3325
|
+
}
|
|
3326
|
+
/** Is `capName` a COLLECTION cap — a cap whose device has several SOURCES? */
|
|
3327
|
+
function isCollectionCap(registry, capName) {
|
|
3328
|
+
return registry?.getDefinition(capName)?.mode === "collection";
|
|
3329
|
+
}
|
|
3330
|
+
/**
|
|
3331
|
+
* The persisted deny list for one `(device, cap)`, empty when never written.
|
|
3332
|
+
*
|
|
3333
|
+
* Reads the current field AND the pre-re-key one, unioned: a legacy addon id
|
|
3334
|
+
* is still an entry and still denies every source that addon serves here. The
|
|
3335
|
+
* union is what makes the migration a read rule rather than a data rewrite —
|
|
3336
|
+
* nothing has to walk a thousand cameras to keep an operator's decision.
|
|
3337
|
+
*/
|
|
3338
|
+
function disabledSourcesOf(record) {
|
|
3339
|
+
const current = record?.disabledSourceIds ?? [];
|
|
3340
|
+
const legacy = record?.disabledSourceAddonIds ?? [];
|
|
3341
|
+
const out = [...current];
|
|
3342
|
+
for (const id of legacy) if (!out.includes(id)) out.push(id);
|
|
3343
|
+
return out;
|
|
3344
|
+
}
|
|
3345
|
+
/**
|
|
3346
|
+
* Push ONE device's persisted source deactivations onto the resolution path.
|
|
3347
|
+
* The only writer of the registry's per-device source set.
|
|
3348
|
+
*/
|
|
3349
|
+
function mirrorDeviceSources(registry, deviceId, capName, disabledAddonIds) {
|
|
3350
|
+
registry?.setDeviceDisabledSources(String(deviceId), capName, disabledAddonIds);
|
|
3351
|
+
}
|
|
3352
|
+
/**
|
|
3353
|
+
* Replay the whole persisted store onto a fresh registry.
|
|
3354
|
+
*
|
|
3355
|
+
* The projection is in-memory, so a hub restart starts it empty — and an empty
|
|
3356
|
+
* deny list means "every source", which would silently switch every source the
|
|
3357
|
+
* operator turned off back ON. That is precisely the D62 failure (an off switch
|
|
3358
|
+
* that reports on), so the replay is not an optimisation: it is what makes the
|
|
3359
|
+
* authority durable. Called from the device-manager's `onInitialize`, before
|
|
3360
|
+
* any camera can be asked for its clips, and safe to call again.
|
|
3361
|
+
*/
|
|
3362
|
+
function restoreDeviceSourceActivations(registry, store, logger) {
|
|
3363
|
+
const deviceBindings = store.deviceBindings;
|
|
3364
|
+
if (deviceBindings === void 0) return 0;
|
|
3365
|
+
let restored = 0;
|
|
3366
|
+
for (const [deviceKey, perDevice] of Object.entries(deviceBindings)) {
|
|
3367
|
+
const deviceId = Number(deviceKey);
|
|
3368
|
+
for (const [capName, record] of Object.entries(perDevice)) {
|
|
3369
|
+
const disabled = disabledSourcesOf(record);
|
|
3370
|
+
if (disabled.length === 0) continue;
|
|
3371
|
+
registry.setDeviceDisabledSources(deviceKey, capName, disabled);
|
|
3372
|
+
restored += 1;
|
|
3373
|
+
logger.info("restored per-device source deactivations", {
|
|
3374
|
+
tags: { deviceId },
|
|
3375
|
+
meta: {
|
|
3376
|
+
capability: capName,
|
|
3377
|
+
disabledSourceIds: disabled
|
|
3378
|
+
}
|
|
3379
|
+
});
|
|
3380
|
+
}
|
|
3381
|
+
}
|
|
3382
|
+
return restored;
|
|
3383
|
+
}
|
|
3384
|
+
/**
|
|
3385
|
+
* Activate or deactivate a wrapper for one device.
|
|
3386
|
+
*
|
|
3387
|
+
* Singleton caps keep the shape D62 fixed: one `wrapperAddonId`, or the
|
|
3388
|
+
* explicit `null` marker that stops `getBindings` step 2 re-binding a
|
|
3389
|
+
* `defaultActive` wrapper. Collection caps write `disabledSourceAddonIds`
|
|
3390
|
+
* instead — their `wrapperAddonId` is left exactly as it was, because a cap
|
|
3391
|
+
* with several sources has no "which one" for `getBindings` to answer (D554
|
|
3392
|
+
* context 3) and inventing one would turn a source toggle into a cap-wide off
|
|
3393
|
+
* switch.
|
|
3394
|
+
*/
|
|
3395
|
+
async function setWrapperActive(deps, input) {
|
|
3396
|
+
const storeKey = String(input.deviceId);
|
|
3397
|
+
const collection = isCollectionCap(deps.capabilityRegistry, input.capName);
|
|
3398
|
+
let mirrored = [];
|
|
3399
|
+
await deps.withAddonStoreWriteLock(async () => {
|
|
3400
|
+
const store = await readBindingsStore(deps);
|
|
3401
|
+
const perDevice = { ...store.deviceBindings[storeKey] };
|
|
3402
|
+
const existing = perDevice[input.capName];
|
|
3403
|
+
if (collection) {
|
|
3404
|
+
const key = input.sourceId ?? input.wrapperAddonId;
|
|
3405
|
+
const disabled = nextDisabledSources(disabledSourcesOf(existing), key, input.active);
|
|
3406
|
+
mirrored = disabled;
|
|
3407
|
+
const wrapperAddonId = existing?.wrapperAddonId;
|
|
3408
|
+
perDevice[input.capName] = {
|
|
3409
|
+
...wrapperAddonId === void 0 ? {} : { wrapperAddonId },
|
|
3410
|
+
...disabled.length === 0 ? {} : { disabledSourceIds: disabled }
|
|
3411
|
+
};
|
|
3412
|
+
if (Object.keys(perDevice[input.capName] ?? {}).length === 0) delete perDevice[input.capName];
|
|
3413
|
+
} else if (input.active) perDevice[input.capName] = {
|
|
3414
|
+
...existing,
|
|
3415
|
+
wrapperAddonId: input.wrapperAddonId
|
|
3416
|
+
};
|
|
3417
|
+
else perDevice[input.capName] = {
|
|
3418
|
+
...existing,
|
|
3419
|
+
wrapperAddonId: null
|
|
3420
|
+
};
|
|
3421
|
+
await writeBindingsStore(deps, { deviceBindings: Object.keys(perDevice).length > 0 ? {
|
|
3422
|
+
...store.deviceBindings,
|
|
3423
|
+
[storeKey]: perDevice
|
|
3424
|
+
} : (() => {
|
|
3425
|
+
const { [storeKey]: _drop, ...rest } = store.deviceBindings;
|
|
3426
|
+
return rest;
|
|
3427
|
+
})() });
|
|
3428
|
+
});
|
|
3429
|
+
if (collection) {
|
|
3430
|
+
mirrorDeviceSources(deps.capabilityRegistry, input.deviceId, input.capName, mirrored);
|
|
3431
|
+
deps.ctx.logger.info(input.active ? "source activated for device" : "source deactivated for device", {
|
|
3432
|
+
tags: {
|
|
3433
|
+
deviceId: input.deviceId,
|
|
3434
|
+
addonId: input.wrapperAddonId
|
|
3435
|
+
},
|
|
3436
|
+
meta: {
|
|
3437
|
+
capability: input.capName,
|
|
3438
|
+
sourceId: input.sourceId ?? null,
|
|
3439
|
+
disabledSourceIds: [...mirrored]
|
|
3440
|
+
}
|
|
3441
|
+
});
|
|
3442
|
+
}
|
|
3443
|
+
deps.ctx.eventBus.emit({
|
|
3444
|
+
id: (0, node_crypto.randomUUID)(),
|
|
3445
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
3446
|
+
source: {
|
|
3447
|
+
type: "addon",
|
|
3448
|
+
id: deps.ctx.id
|
|
3449
|
+
},
|
|
3450
|
+
category: require_dist.EventCategory.DeviceBindingsChanged,
|
|
3451
|
+
data: {
|
|
3452
|
+
deviceId: input.deviceId,
|
|
3453
|
+
capName: input.capName,
|
|
3454
|
+
reason: input.active ? "wrapper-activated" : "wrapper-deactivated",
|
|
3455
|
+
addonId: input.wrapperAddonId,
|
|
3456
|
+
nodeId: resolveWrapperNodeId(input.wrapperAddonId)
|
|
3457
|
+
}
|
|
3458
|
+
});
|
|
3459
|
+
}
|
|
3460
|
+
//#endregion
|
|
3359
3461
|
//#region src/builtins/device-manager/device-event-propagator.ts
|
|
3360
3462
|
/**
|
|
3361
3463
|
* Walks the parent chain for every device-sourced event and re-emits a
|
|
@@ -7327,7 +7429,15 @@ var DeviceManagerAddon = class extends require_dist.BaseAddon {
|
|
|
7327
7429
|
return listBindableCapsForDeviceType(this.aggregationDeps, input);
|
|
7328
7430
|
}
|
|
7329
7431
|
async setWrapperActive(input) {
|
|
7330
|
-
return setWrapperActive(this.
|
|
7432
|
+
return setWrapperActive(this.wrapperActivationDeps, input);
|
|
7433
|
+
}
|
|
7434
|
+
/** The narrow dependency bag the activation verb needs (D556). */
|
|
7435
|
+
get wrapperActivationDeps() {
|
|
7436
|
+
return {
|
|
7437
|
+
ctx: this.ctx,
|
|
7438
|
+
capabilityRegistry: this.capabilityRegistry,
|
|
7439
|
+
withAddonStoreWriteLock: this.addonStoreWriteLock
|
|
7440
|
+
};
|
|
7331
7441
|
}
|
|
7332
7442
|
async onInitialize() {
|
|
7333
7443
|
const settings = this.ctx.settings;
|
|
@@ -7348,6 +7458,11 @@ var DeviceManagerAddon = class extends require_dist.BaseAddon {
|
|
|
7348
7458
|
const deviceRows = new DeviceRowStore(deviceRowBackendOf(settingsStoreApi), this.ctx.logger.child("rows"));
|
|
7349
7459
|
await deviceRows.declare();
|
|
7350
7460
|
this.deviceRows = deviceRows;
|
|
7461
|
+
const capRegistryForRestore = this.capabilityRegistry;
|
|
7462
|
+
if (capRegistryForRestore) {
|
|
7463
|
+
const restored = restoreDeviceSourceActivations(capRegistryForRestore, await readBindingsStore({ ctx: this.ctx }), this.ctx.logger);
|
|
7464
|
+
if (restored > 0) this.ctx.logger.info("per-device source deactivations restored", { meta: { rows: restored } });
|
|
7465
|
+
}
|
|
7351
7466
|
try {
|
|
7352
7467
|
await require_retired_settings_keys.purgeRetiredSettingsRows(deviceRows.retiredRowStore(), this.ctx.logger.child("RetiredRows"));
|
|
7353
7468
|
} catch (err) {
|
|
@@ -7602,4 +7717,8 @@ var DeviceManagerAddon = class extends require_dist.BaseAddon {
|
|
|
7602
7717
|
//#endregion
|
|
7603
7718
|
exports.DeviceManagerAddon = DeviceManagerAddon;
|
|
7604
7719
|
exports.default = DeviceManagerAddon;
|
|
7720
|
+
exports.isSourceDenied = isSourceDenied;
|
|
7605
7721
|
exports.mergeAggregates = mergeAggregates;
|
|
7722
|
+
exports.nextDisabledSources = nextDisabledSources;
|
|
7723
|
+
exports.restoreDeviceSourceActivations = restoreDeviceSourceActivations;
|
|
7724
|
+
exports.setWrapperActive = setWrapperActive;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { $ as deviceStateCapability, D as STREAM_PROFILE_META, H as buildStreamParamsConfigSchema,
|
|
2
|
-
import { n as purgeRetiredSettingsRows } from "../../retired-settings-keys-
|
|
1
|
+
import { $ as deviceStateCapability, Bt as resolveContainerPrimaryChild, D as STREAM_PROFILE_META, En as isDeviceConfigCap, Fn as EventCategory, H as buildStreamParamsConfigSchema, Jt as siteZoneKey, Pn as sleep$1, Pt as parseStreamParamsFormPatch, Q as deviceManagerCapability, St as locationIconKey, Ut as runtimeStatePolicyFor, _n as WELL_KNOWN_TAB_MAP, cn as BaseAddon, et as deviceStatusCapability, fn as DeviceFeature, it as enumerateSchemaFields, jt as normalizeUnit, m as DeviceStatusSchema, mn as DeviceType, o as CAMERA_SWITCH_ORDER, on as errMsg, pn as DeviceRole, rt as enumerateItemArrayFields, s as CAP_NAMES_WITH_STATUS, t as ALL_CAPABILITY_DEFINITIONS, v as LocationIconIdSchema } from "../../dist-AHdiIdJH.mjs";
|
|
2
|
+
import { n as purgeRetiredSettingsRows } from "../../retired-settings-keys-Cp3oG_aH.mjs";
|
|
3
3
|
import { randomUUID } from "node:crypto";
|
|
4
4
|
import { canonicalDeviceFingerprint } from "@camstack/types/node";
|
|
5
5
|
//#region src/builtins/device-manager/adoption-job-engine.ts
|
|
@@ -757,6 +757,7 @@ async function resolveBindingsForDevice(deps, deviceId, row, pass) {
|
|
|
757
757
|
const seenCaps = /* @__PURE__ */ new Set();
|
|
758
758
|
const resolveRemote = (capName) => deps.remoteNativeCaps.get(deviceId)?.get(capName) ?? resolveRemoteNativeCapFromRegistry(deps, capName, deviceId);
|
|
759
759
|
for (const [capName, { wrapperAddonId }] of Object.entries(perDevice)) {
|
|
760
|
+
if (wrapperAddonId === void 0) continue;
|
|
760
761
|
const hubLocalNative = deps.capabilityRegistry?.getNativeAddonId(capName, deviceId) ?? null;
|
|
761
762
|
const remoteNative = resolveRemote(capName);
|
|
762
763
|
const nativeAddonId = hubLocalNative ?? remoteNative?.addonId ?? "";
|
|
@@ -2521,28 +2522,6 @@ function splitDeviceStoreKeys(patch) {
|
|
|
2521
2522
|
//#endregion
|
|
2522
2523
|
//#region src/builtins/device-manager/device-aggregation.ts
|
|
2523
2524
|
/**
|
|
2524
|
-
* Device-details aggregation + field-update routing for the device-manager
|
|
2525
|
-
* addon. Extracted from `device-manager.addon.ts` as free functions that
|
|
2526
|
-
* receive an explicit `AggregationDeps` context (DI) rather than closing over
|
|
2527
|
-
* the addon instance.
|
|
2528
|
-
*
|
|
2529
|
-
* This module owns the binding-driven settings/live/status aggregators
|
|
2530
|
-
* (`getDeviceAggregate`, `collectSystemDeviceContributions`,
|
|
2531
|
-
* `deriveDeviceConfigContribution`, `buildBaseDeviceSection`,
|
|
2532
|
-
* `getDeviceStatusAggregate`, `resolveDriverConfigSchema`) and the
|
|
2533
|
-
* field-update routing (`updateDeviceField`, `updateDeviceFieldsBatch`,
|
|
2534
|
-
* `applyGroupPatch`, `resolveContributionProvider`, `dispatchDeviceAction`)
|
|
2535
|
-
* plus the wrapper-activation mutators (`setWrapperActive`,
|
|
2536
|
-
* `listWrappersForCap`, `listBindableCapsForDeviceType`).
|
|
2537
|
-
*
|
|
2538
|
-
* Sibling aggregator functions call each other directly (passing `deps`);
|
|
2539
|
-
* cross-domain leaf calls (`getBindings`, `lookupPersistedStableId`,
|
|
2540
|
-
* `resolveRemoteNativeCapFromRegistry`, read/writeBindingsStore) route through
|
|
2541
|
-
* the bindings-store module via `deps.bindingsDeps`. EXACT semantics, ordering,
|
|
2542
|
-
* and side effects of the original class methods are preserved; the addon keeps
|
|
2543
|
-
* thin delegator methods over these functions.
|
|
2544
|
-
*/
|
|
2545
|
-
/**
|
|
2546
2525
|
* One process-wide cache behind every device-details fan-out.
|
|
2547
2526
|
*
|
|
2548
2527
|
* Module-level on purpose: the page re-polls every 2.5 s and a per-call cache
|
|
@@ -3245,39 +3224,6 @@ async function listBindableCapsForDeviceType(deps, input) {
|
|
|
3245
3224
|
wrappers: [...registry.getWrappersForCap(capName)]
|
|
3246
3225
|
}));
|
|
3247
3226
|
}
|
|
3248
|
-
async function setWrapperActive(deps, input) {
|
|
3249
|
-
const storeKey = String(input.deviceId);
|
|
3250
|
-
await deps.bindingsDeps.withAddonStoreWriteLock(async () => {
|
|
3251
|
-
const store = await readBindingsStore(deps.bindingsDeps);
|
|
3252
|
-
const perDevice = { ...store.deviceBindings[storeKey] };
|
|
3253
|
-
if (input.active) perDevice[input.capName] = { wrapperAddonId: input.wrapperAddonId };
|
|
3254
|
-
else perDevice[input.capName] = { wrapperAddonId: null };
|
|
3255
|
-
const nextDeviceBindings = Object.keys(perDevice).length > 0 ? {
|
|
3256
|
-
...store.deviceBindings,
|
|
3257
|
-
[storeKey]: perDevice
|
|
3258
|
-
} : (() => {
|
|
3259
|
-
const { [storeKey]: _drop, ...rest } = store.deviceBindings;
|
|
3260
|
-
return rest;
|
|
3261
|
-
})();
|
|
3262
|
-
await writeBindingsStore(deps.bindingsDeps, { deviceBindings: nextDeviceBindings });
|
|
3263
|
-
});
|
|
3264
|
-
deps.ctx.eventBus.emit({
|
|
3265
|
-
id: randomUUID(),
|
|
3266
|
-
timestamp: /* @__PURE__ */ new Date(),
|
|
3267
|
-
source: {
|
|
3268
|
-
type: "addon",
|
|
3269
|
-
id: deps.ctx.id
|
|
3270
|
-
},
|
|
3271
|
-
category: EventCategory.DeviceBindingsChanged,
|
|
3272
|
-
data: {
|
|
3273
|
-
deviceId: input.deviceId,
|
|
3274
|
-
capName: input.capName,
|
|
3275
|
-
reason: input.active ? "wrapper-activated" : "wrapper-deactivated",
|
|
3276
|
-
addonId: input.wrapperAddonId,
|
|
3277
|
-
nodeId: resolveWrapperNodeId(input.wrapperAddonId)
|
|
3278
|
-
}
|
|
3279
|
-
});
|
|
3280
|
-
}
|
|
3281
3227
|
/**
|
|
3282
3228
|
* Build the wireable-cap entry for one capability definition: the status
|
|
3283
3229
|
* schema's scalar leaf fields plus — for an item-array cap
|
|
@@ -3351,6 +3297,162 @@ async function getWireableFields(deps, input) {
|
|
|
3351
3297
|
return { caps };
|
|
3352
3298
|
}
|
|
3353
3299
|
//#endregion
|
|
3300
|
+
//#region src/builtins/device-manager/device-source-activation.ts
|
|
3301
|
+
/**
|
|
3302
|
+
* Is `source` denied for a device, given the persisted deny set?
|
|
3303
|
+
*
|
|
3304
|
+
* The ONE place the migration rule lives: an entry matches either the source's
|
|
3305
|
+
* own id or the id of the addon that serves it. New writes are always source
|
|
3306
|
+
* ids; an addon id can only be a pre-re-key entry, and it still means "every
|
|
3307
|
+
* source this addon serves here".
|
|
3308
|
+
*/
|
|
3309
|
+
function isSourceDenied(denied, source) {
|
|
3310
|
+
if (denied.includes(source.addonId)) return true;
|
|
3311
|
+
return source.sourceId !== void 0 && denied.includes(source.sourceId);
|
|
3312
|
+
}
|
|
3313
|
+
/**
|
|
3314
|
+
* The deny list after one toggle. Pure, order-preserving, idempotent — a
|
|
3315
|
+
* second "off" on an addon already off changes nothing.
|
|
3316
|
+
*/
|
|
3317
|
+
function nextDisabledSources(current, key, active) {
|
|
3318
|
+
if (active) return current.filter((id) => id !== key);
|
|
3319
|
+
return current.includes(key) ? current : [...current, key];
|
|
3320
|
+
}
|
|
3321
|
+
/** Is `capName` a COLLECTION cap — a cap whose device has several SOURCES? */
|
|
3322
|
+
function isCollectionCap(registry, capName) {
|
|
3323
|
+
return registry?.getDefinition(capName)?.mode === "collection";
|
|
3324
|
+
}
|
|
3325
|
+
/**
|
|
3326
|
+
* The persisted deny list for one `(device, cap)`, empty when never written.
|
|
3327
|
+
*
|
|
3328
|
+
* Reads the current field AND the pre-re-key one, unioned: a legacy addon id
|
|
3329
|
+
* is still an entry and still denies every source that addon serves here. The
|
|
3330
|
+
* union is what makes the migration a read rule rather than a data rewrite —
|
|
3331
|
+
* nothing has to walk a thousand cameras to keep an operator's decision.
|
|
3332
|
+
*/
|
|
3333
|
+
function disabledSourcesOf(record) {
|
|
3334
|
+
const current = record?.disabledSourceIds ?? [];
|
|
3335
|
+
const legacy = record?.disabledSourceAddonIds ?? [];
|
|
3336
|
+
const out = [...current];
|
|
3337
|
+
for (const id of legacy) if (!out.includes(id)) out.push(id);
|
|
3338
|
+
return out;
|
|
3339
|
+
}
|
|
3340
|
+
/**
|
|
3341
|
+
* Push ONE device's persisted source deactivations onto the resolution path.
|
|
3342
|
+
* The only writer of the registry's per-device source set.
|
|
3343
|
+
*/
|
|
3344
|
+
function mirrorDeviceSources(registry, deviceId, capName, disabledAddonIds) {
|
|
3345
|
+
registry?.setDeviceDisabledSources(String(deviceId), capName, disabledAddonIds);
|
|
3346
|
+
}
|
|
3347
|
+
/**
|
|
3348
|
+
* Replay the whole persisted store onto a fresh registry.
|
|
3349
|
+
*
|
|
3350
|
+
* The projection is in-memory, so a hub restart starts it empty — and an empty
|
|
3351
|
+
* deny list means "every source", which would silently switch every source the
|
|
3352
|
+
* operator turned off back ON. That is precisely the D62 failure (an off switch
|
|
3353
|
+
* that reports on), so the replay is not an optimisation: it is what makes the
|
|
3354
|
+
* authority durable. Called from the device-manager's `onInitialize`, before
|
|
3355
|
+
* any camera can be asked for its clips, and safe to call again.
|
|
3356
|
+
*/
|
|
3357
|
+
function restoreDeviceSourceActivations(registry, store, logger) {
|
|
3358
|
+
const deviceBindings = store.deviceBindings;
|
|
3359
|
+
if (deviceBindings === void 0) return 0;
|
|
3360
|
+
let restored = 0;
|
|
3361
|
+
for (const [deviceKey, perDevice] of Object.entries(deviceBindings)) {
|
|
3362
|
+
const deviceId = Number(deviceKey);
|
|
3363
|
+
for (const [capName, record] of Object.entries(perDevice)) {
|
|
3364
|
+
const disabled = disabledSourcesOf(record);
|
|
3365
|
+
if (disabled.length === 0) continue;
|
|
3366
|
+
registry.setDeviceDisabledSources(deviceKey, capName, disabled);
|
|
3367
|
+
restored += 1;
|
|
3368
|
+
logger.info("restored per-device source deactivations", {
|
|
3369
|
+
tags: { deviceId },
|
|
3370
|
+
meta: {
|
|
3371
|
+
capability: capName,
|
|
3372
|
+
disabledSourceIds: disabled
|
|
3373
|
+
}
|
|
3374
|
+
});
|
|
3375
|
+
}
|
|
3376
|
+
}
|
|
3377
|
+
return restored;
|
|
3378
|
+
}
|
|
3379
|
+
/**
|
|
3380
|
+
* Activate or deactivate a wrapper for one device.
|
|
3381
|
+
*
|
|
3382
|
+
* Singleton caps keep the shape D62 fixed: one `wrapperAddonId`, or the
|
|
3383
|
+
* explicit `null` marker that stops `getBindings` step 2 re-binding a
|
|
3384
|
+
* `defaultActive` wrapper. Collection caps write `disabledSourceAddonIds`
|
|
3385
|
+
* instead — their `wrapperAddonId` is left exactly as it was, because a cap
|
|
3386
|
+
* with several sources has no "which one" for `getBindings` to answer (D554
|
|
3387
|
+
* context 3) and inventing one would turn a source toggle into a cap-wide off
|
|
3388
|
+
* switch.
|
|
3389
|
+
*/
|
|
3390
|
+
async function setWrapperActive(deps, input) {
|
|
3391
|
+
const storeKey = String(input.deviceId);
|
|
3392
|
+
const collection = isCollectionCap(deps.capabilityRegistry, input.capName);
|
|
3393
|
+
let mirrored = [];
|
|
3394
|
+
await deps.withAddonStoreWriteLock(async () => {
|
|
3395
|
+
const store = await readBindingsStore(deps);
|
|
3396
|
+
const perDevice = { ...store.deviceBindings[storeKey] };
|
|
3397
|
+
const existing = perDevice[input.capName];
|
|
3398
|
+
if (collection) {
|
|
3399
|
+
const key = input.sourceId ?? input.wrapperAddonId;
|
|
3400
|
+
const disabled = nextDisabledSources(disabledSourcesOf(existing), key, input.active);
|
|
3401
|
+
mirrored = disabled;
|
|
3402
|
+
const wrapperAddonId = existing?.wrapperAddonId;
|
|
3403
|
+
perDevice[input.capName] = {
|
|
3404
|
+
...wrapperAddonId === void 0 ? {} : { wrapperAddonId },
|
|
3405
|
+
...disabled.length === 0 ? {} : { disabledSourceIds: disabled }
|
|
3406
|
+
};
|
|
3407
|
+
if (Object.keys(perDevice[input.capName] ?? {}).length === 0) delete perDevice[input.capName];
|
|
3408
|
+
} else if (input.active) perDevice[input.capName] = {
|
|
3409
|
+
...existing,
|
|
3410
|
+
wrapperAddonId: input.wrapperAddonId
|
|
3411
|
+
};
|
|
3412
|
+
else perDevice[input.capName] = {
|
|
3413
|
+
...existing,
|
|
3414
|
+
wrapperAddonId: null
|
|
3415
|
+
};
|
|
3416
|
+
await writeBindingsStore(deps, { deviceBindings: Object.keys(perDevice).length > 0 ? {
|
|
3417
|
+
...store.deviceBindings,
|
|
3418
|
+
[storeKey]: perDevice
|
|
3419
|
+
} : (() => {
|
|
3420
|
+
const { [storeKey]: _drop, ...rest } = store.deviceBindings;
|
|
3421
|
+
return rest;
|
|
3422
|
+
})() });
|
|
3423
|
+
});
|
|
3424
|
+
if (collection) {
|
|
3425
|
+
mirrorDeviceSources(deps.capabilityRegistry, input.deviceId, input.capName, mirrored);
|
|
3426
|
+
deps.ctx.logger.info(input.active ? "source activated for device" : "source deactivated for device", {
|
|
3427
|
+
tags: {
|
|
3428
|
+
deviceId: input.deviceId,
|
|
3429
|
+
addonId: input.wrapperAddonId
|
|
3430
|
+
},
|
|
3431
|
+
meta: {
|
|
3432
|
+
capability: input.capName,
|
|
3433
|
+
sourceId: input.sourceId ?? null,
|
|
3434
|
+
disabledSourceIds: [...mirrored]
|
|
3435
|
+
}
|
|
3436
|
+
});
|
|
3437
|
+
}
|
|
3438
|
+
deps.ctx.eventBus.emit({
|
|
3439
|
+
id: randomUUID(),
|
|
3440
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
3441
|
+
source: {
|
|
3442
|
+
type: "addon",
|
|
3443
|
+
id: deps.ctx.id
|
|
3444
|
+
},
|
|
3445
|
+
category: EventCategory.DeviceBindingsChanged,
|
|
3446
|
+
data: {
|
|
3447
|
+
deviceId: input.deviceId,
|
|
3448
|
+
capName: input.capName,
|
|
3449
|
+
reason: input.active ? "wrapper-activated" : "wrapper-deactivated",
|
|
3450
|
+
addonId: input.wrapperAddonId,
|
|
3451
|
+
nodeId: resolveWrapperNodeId(input.wrapperAddonId)
|
|
3452
|
+
}
|
|
3453
|
+
});
|
|
3454
|
+
}
|
|
3455
|
+
//#endregion
|
|
3354
3456
|
//#region src/builtins/device-manager/device-event-propagator.ts
|
|
3355
3457
|
/**
|
|
3356
3458
|
* Walks the parent chain for every device-sourced event and re-emits a
|
|
@@ -7322,7 +7424,15 @@ var DeviceManagerAddon = class extends BaseAddon {
|
|
|
7322
7424
|
return listBindableCapsForDeviceType(this.aggregationDeps, input);
|
|
7323
7425
|
}
|
|
7324
7426
|
async setWrapperActive(input) {
|
|
7325
|
-
return setWrapperActive(this.
|
|
7427
|
+
return setWrapperActive(this.wrapperActivationDeps, input);
|
|
7428
|
+
}
|
|
7429
|
+
/** The narrow dependency bag the activation verb needs (D556). */
|
|
7430
|
+
get wrapperActivationDeps() {
|
|
7431
|
+
return {
|
|
7432
|
+
ctx: this.ctx,
|
|
7433
|
+
capabilityRegistry: this.capabilityRegistry,
|
|
7434
|
+
withAddonStoreWriteLock: this.addonStoreWriteLock
|
|
7435
|
+
};
|
|
7326
7436
|
}
|
|
7327
7437
|
async onInitialize() {
|
|
7328
7438
|
const settings = this.ctx.settings;
|
|
@@ -7343,6 +7453,11 @@ var DeviceManagerAddon = class extends BaseAddon {
|
|
|
7343
7453
|
const deviceRows = new DeviceRowStore(deviceRowBackendOf(settingsStoreApi), this.ctx.logger.child("rows"));
|
|
7344
7454
|
await deviceRows.declare();
|
|
7345
7455
|
this.deviceRows = deviceRows;
|
|
7456
|
+
const capRegistryForRestore = this.capabilityRegistry;
|
|
7457
|
+
if (capRegistryForRestore) {
|
|
7458
|
+
const restored = restoreDeviceSourceActivations(capRegistryForRestore, await readBindingsStore({ ctx: this.ctx }), this.ctx.logger);
|
|
7459
|
+
if (restored > 0) this.ctx.logger.info("per-device source deactivations restored", { meta: { rows: restored } });
|
|
7460
|
+
}
|
|
7346
7461
|
try {
|
|
7347
7462
|
await purgeRetiredSettingsRows(deviceRows.retiredRowStore(), this.ctx.logger.child("RetiredRows"));
|
|
7348
7463
|
} catch (err) {
|
|
@@ -7595,4 +7710,4 @@ var DeviceManagerAddon = class extends BaseAddon {
|
|
|
7595
7710
|
}
|
|
7596
7711
|
};
|
|
7597
7712
|
//#endregion
|
|
7598
|
-
export { DeviceManagerAddon, DeviceManagerAddon as default, mergeAggregates };
|
|
7713
|
+
export { DeviceManagerAddon, DeviceManagerAddon as default, setWrapperActive as i, mergeAggregates, nextDisabledSources as n, restoreDeviceSourceActivations as r, isSourceDenied as t };
|
|
@@ -43,10 +43,43 @@ export type DriverConfigSchemaResult = {
|
|
|
43
43
|
} | {
|
|
44
44
|
status: 'unavailable';
|
|
45
45
|
};
|
|
46
|
+
/**
|
|
47
|
+
* One `(device, capability)` row of the persisted wrapper-activation store —
|
|
48
|
+
* the authority for every per-camera function switch (D62) and, since D556,
|
|
49
|
+
* for which SOURCES a camera's collection cap has.
|
|
50
|
+
*
|
|
51
|
+
* The two fields answer two different cardinalities and are never both the
|
|
52
|
+
* answer for one cap:
|
|
53
|
+
*
|
|
54
|
+
* - `wrapperAddonId` — a SINGLETON cap's bound wrapper, or the explicit
|
|
55
|
+
* `null` "operator disabled" marker that stops `getBindings` step 2
|
|
56
|
+
* re-binding a `defaultActive` wrapper. Absent means the operator never
|
|
57
|
+
* touched this cap on this device.
|
|
58
|
+
* - `disabledSourceIds` — a COLLECTION cap's switched-OFF sources. A DENY
|
|
59
|
+
* list on purpose: absence means every source the camera has, which is both
|
|
60
|
+
* today's behaviour and the only shape under which a provider registered
|
|
61
|
+
* later is seen without a second write (D556).
|
|
62
|
+
*
|
|
63
|
+
* `disabledSourceIds` holds SOURCE ids (`native:reolink:hub`), not addon ids.
|
|
64
|
+
* One addon can serve several of a device's sources, so an addon-keyed entry
|
|
65
|
+
* could only ever mean "all of them" — which is exactly how a LEGACY entry is
|
|
66
|
+
* still read: {@link DeviceCapBinding.disabledSourceAddonIds} is never written
|
|
67
|
+
* any more and every id in it denies every source that addon serves for this
|
|
68
|
+
* device. One predicate, stated once, so an operator's existing decision does
|
|
69
|
+
* not silently stop applying (D555).
|
|
70
|
+
*/
|
|
71
|
+
export interface DeviceCapBinding {
|
|
72
|
+
readonly wrapperAddonId?: string | null;
|
|
73
|
+
/** SOURCE ids the operator switched off for this device. */
|
|
74
|
+
readonly disabledSourceIds?: readonly string[];
|
|
75
|
+
/**
|
|
76
|
+
* @deprecated Pre-re-key entries, keyed by ADDON id. Read, never written:
|
|
77
|
+
* an id here denies every source that addon serves for this device.
|
|
78
|
+
*/
|
|
79
|
+
readonly disabledSourceAddonIds?: readonly string[];
|
|
80
|
+
}
|
|
46
81
|
export interface DeviceBindingsStore {
|
|
47
|
-
readonly deviceBindings: Record<string, Record<string,
|
|
48
|
-
wrapperAddonId: string | null;
|
|
49
|
-
}>>;
|
|
82
|
+
readonly deviceBindings: Record<string, Record<string, DeviceCapBinding>>;
|
|
50
83
|
}
|
|
51
84
|
export interface PersistedDeviceMeta {
|
|
52
85
|
type: string;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { AddonContext, ICapabilityRegistry, IScopedLogger } from '@camstack/types';
|
|
2
|
+
import { DeviceBindingsStore } from './device-meta-types.js';
|
|
3
|
+
import { WriteLock } from './write-lock.js';
|
|
4
|
+
/**
|
|
5
|
+
* What a wrapper-activation write needs, and nothing more.
|
|
6
|
+
*
|
|
7
|
+
* Deliberately narrower than `AggregationDeps`: the verb touches the settings
|
|
8
|
+
* door, the event bus, the registry and the addon-store write lock. Handing it
|
|
9
|
+
* the device ledger and the remote-native cache it never reads would make this
|
|
10
|
+
* loop untestable without standing up the whole addon.
|
|
11
|
+
*/
|
|
12
|
+
export interface WrapperActivationDeps {
|
|
13
|
+
readonly ctx: AddonContext;
|
|
14
|
+
readonly capabilityRegistry: ICapabilityRegistry | undefined;
|
|
15
|
+
/** Ordering for every read-modify-write of the fleet-wide `deviceBindings` blob (D62). */
|
|
16
|
+
readonly withAddonStoreWriteLock: WriteLock;
|
|
17
|
+
}
|
|
18
|
+
/** The operator-facing activation, as it arrives from the cap router. */
|
|
19
|
+
export interface WrapperActivationInput {
|
|
20
|
+
readonly deviceId: number;
|
|
21
|
+
readonly capName: string;
|
|
22
|
+
readonly wrapperAddonId: string;
|
|
23
|
+
/**
|
|
24
|
+
* The SOURCE to toggle, for a collection cap. Absent keeps the cap-wide
|
|
25
|
+
* meaning D556 shipped — every source this addon serves for this device —
|
|
26
|
+
* which is also how a pre-re-key entry is read.
|
|
27
|
+
*/
|
|
28
|
+
readonly sourceId?: string;
|
|
29
|
+
readonly active: boolean;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Is `source` denied for a device, given the persisted deny set?
|
|
33
|
+
*
|
|
34
|
+
* The ONE place the migration rule lives: an entry matches either the source's
|
|
35
|
+
* own id or the id of the addon that serves it. New writes are always source
|
|
36
|
+
* ids; an addon id can only be a pre-re-key entry, and it still means "every
|
|
37
|
+
* source this addon serves here".
|
|
38
|
+
*/
|
|
39
|
+
export declare function isSourceDenied(denied: readonly string[], source: {
|
|
40
|
+
readonly sourceId?: string;
|
|
41
|
+
readonly addonId: string;
|
|
42
|
+
}): boolean;
|
|
43
|
+
/**
|
|
44
|
+
* The deny list after one toggle. Pure, order-preserving, idempotent — a
|
|
45
|
+
* second "off" on an addon already off changes nothing.
|
|
46
|
+
*/
|
|
47
|
+
export declare function nextDisabledSources(current: readonly string[], key: string, active: boolean): readonly string[];
|
|
48
|
+
/**
|
|
49
|
+
* Replay the whole persisted store onto a fresh registry.
|
|
50
|
+
*
|
|
51
|
+
* The projection is in-memory, so a hub restart starts it empty — and an empty
|
|
52
|
+
* deny list means "every source", which would silently switch every source the
|
|
53
|
+
* operator turned off back ON. That is precisely the D62 failure (an off switch
|
|
54
|
+
* that reports on), so the replay is not an optimisation: it is what makes the
|
|
55
|
+
* authority durable. Called from the device-manager's `onInitialize`, before
|
|
56
|
+
* any camera can be asked for its clips, and safe to call again.
|
|
57
|
+
*/
|
|
58
|
+
export declare function restoreDeviceSourceActivations(registry: ICapabilityRegistry, store: Partial<DeviceBindingsStore>, logger: IScopedLogger): number;
|
|
59
|
+
/**
|
|
60
|
+
* Activate or deactivate a wrapper for one device.
|
|
61
|
+
*
|
|
62
|
+
* Singleton caps keep the shape D62 fixed: one `wrapperAddonId`, or the
|
|
63
|
+
* explicit `null` marker that stops `getBindings` step 2 re-binding a
|
|
64
|
+
* `defaultActive` wrapper. Collection caps write `disabledSourceAddonIds`
|
|
65
|
+
* instead — their `wrapperAddonId` is left exactly as it was, because a cap
|
|
66
|
+
* with several sources has no "which one" for `getBindings` to answer (D554
|
|
67
|
+
* context 3) and inventing one would turn a source toggle into a cap-wide off
|
|
68
|
+
* switch.
|
|
69
|
+
*/
|
|
70
|
+
export declare function setWrapperActive(deps: WrapperActivationDeps, input: WrapperActivationInput): Promise<void>;
|