camstack 1.2.89 → 1.2.90
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/cli.js
CHANGED
|
@@ -38,7 +38,7 @@ async function runServe(args) {
|
|
|
38
38
|
...typeof values.data === "string" ? { data: values.data } : {}
|
|
39
39
|
};
|
|
40
40
|
Object.assign(process.env, buildServeEnv(opts));
|
|
41
|
-
await import("./launcher-
|
|
41
|
+
await import("./launcher-7KH64TCV.js");
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
// src/commands/agent.ts
|
|
@@ -83,7 +83,7 @@ async function runAgent(args) {
|
|
|
83
83
|
...typeof values.port === "string" ? { port: values.port } : {}
|
|
84
84
|
};
|
|
85
85
|
Object.assign(process.env, buildAgentEnv(opts));
|
|
86
|
-
await import("./launcher-
|
|
86
|
+
await import("./launcher-7KH64TCV.js");
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
// src/commands/setup.ts
|
|
@@ -65799,6 +65799,68 @@ var require_device_manager_addon = __commonJS({
|
|
|
65799
65799
|
return linkedIds.filter((id) => picked.has(id));
|
|
65800
65800
|
}
|
|
65801
65801
|
var LINK_CANDIDATE_PROJECTION = { projection: "slim" };
|
|
65802
|
+
function findLocationInvisibleContainers(params) {
|
|
65803
|
+
const { selfId, selfLocation, resolvedIds, candidates } = params;
|
|
65804
|
+
if (selfLocation === null || selfLocation.length === 0) return [];
|
|
65805
|
+
const byParent = /* @__PURE__ */ new Map();
|
|
65806
|
+
for (const c of candidates) {
|
|
65807
|
+
if (c.parentDeviceId === null) continue;
|
|
65808
|
+
const bucket = byParent.get(c.parentDeviceId);
|
|
65809
|
+
if (bucket) bucket.push(c);
|
|
65810
|
+
else byParent.set(c.parentDeviceId, [c]);
|
|
65811
|
+
}
|
|
65812
|
+
const out = [];
|
|
65813
|
+
for (const c of candidates) {
|
|
65814
|
+
if (c.type !== require_dist10.DeviceType.Container) continue;
|
|
65815
|
+
if (c.id === selfId) continue;
|
|
65816
|
+
if (c.location !== selfLocation) continue;
|
|
65817
|
+
let anyResolved = false;
|
|
65818
|
+
let anyNullLocation = false;
|
|
65819
|
+
let descendantCount = 0;
|
|
65820
|
+
const visited = /* @__PURE__ */ new Set([c.id]);
|
|
65821
|
+
let frontier = [c.id];
|
|
65822
|
+
while (frontier.length > 0) {
|
|
65823
|
+
const next = [];
|
|
65824
|
+
for (const parentId of frontier) for (const child of byParent.get(parentId) ?? []) {
|
|
65825
|
+
if (visited.has(child.id)) continue;
|
|
65826
|
+
visited.add(child.id);
|
|
65827
|
+
descendantCount += 1;
|
|
65828
|
+
if (resolvedIds.has(child.id)) anyResolved = true;
|
|
65829
|
+
if (child.location === null) anyNullLocation = true;
|
|
65830
|
+
next.push(child.id);
|
|
65831
|
+
}
|
|
65832
|
+
frontier = next;
|
|
65833
|
+
}
|
|
65834
|
+
if (descendantCount > 0 && !anyResolved && anyNullLocation) out.push(c.id);
|
|
65835
|
+
}
|
|
65836
|
+
return out;
|
|
65837
|
+
}
|
|
65838
|
+
var INVISIBLE_CONTAINER_WARN_CAP = 4096;
|
|
65839
|
+
var warnedInvisibleContainers = /* @__PURE__ */ new Set();
|
|
65840
|
+
function warnLocationInvisibleContainers(pctx, cameraId, byId, all, resolved) {
|
|
65841
|
+
if (resolved.mode !== "auto") return;
|
|
65842
|
+
const selfLocation = byId.get(cameraId)?.location ?? null;
|
|
65843
|
+
if (selfLocation === null || selfLocation.length === 0) return;
|
|
65844
|
+
const invisible = findLocationInvisibleContainers({
|
|
65845
|
+
selfId: cameraId,
|
|
65846
|
+
selfLocation,
|
|
65847
|
+
resolvedIds: new Set(resolved.devices.map((d) => d.deviceId)),
|
|
65848
|
+
candidates: all
|
|
65849
|
+
});
|
|
65850
|
+
for (const containerId of invisible) {
|
|
65851
|
+
const key = `${cameraId}:${containerId}`;
|
|
65852
|
+
if (warnedInvisibleContainers.has(key)) continue;
|
|
65853
|
+
if (warnedInvisibleContainers.size >= INVISIBLE_CONTAINER_WARN_CAP) return;
|
|
65854
|
+
warnedInvisibleContainers.add(key);
|
|
65855
|
+
pctx.host.ctx.logger.warn("getLinkedDevices: container matches the camera location but is invisible to auto-link \u2014 its children carry no location; re-save the container location to realign", {
|
|
65856
|
+
tags: { deviceId: containerId },
|
|
65857
|
+
meta: {
|
|
65858
|
+
cameraId,
|
|
65859
|
+
location: selfLocation
|
|
65860
|
+
}
|
|
65861
|
+
});
|
|
65862
|
+
}
|
|
65863
|
+
}
|
|
65802
65864
|
function resolveFor(deviceId, all, byId, blob) {
|
|
65803
65865
|
const config = parseLinkedDevicesConfig(blob);
|
|
65804
65866
|
const ids = resolveLinkedDeviceIds({
|
|
@@ -65828,7 +65890,10 @@ var require_device_manager_addon = __commonJS({
|
|
|
65828
65890
|
async function getLinkedDevices(pctx, input) {
|
|
65829
65891
|
const { deviceId } = input;
|
|
65830
65892
|
const [all, blob] = await Promise.all([listAll(pctx, LINK_CANDIDATE_PROJECTION), pctx.settings.readDeviceStore(deviceId)]);
|
|
65831
|
-
|
|
65893
|
+
const byId = new Map(all.map((d) => [d.id, d]));
|
|
65894
|
+
const resolved = resolveFor(deviceId, all, byId, blob);
|
|
65895
|
+
warnLocationInvisibleContainers(pctx, deviceId, byId, all, resolved);
|
|
65896
|
+
return resolved;
|
|
65832
65897
|
}
|
|
65833
65898
|
async function getLinkedDevicesBatch(pctx, input) {
|
|
65834
65899
|
const deviceIds = [...new Set(input.deviceIds)];
|
|
@@ -65841,10 +65906,14 @@ var require_device_manager_addon = __commonJS({
|
|
|
65841
65906
|
return {};
|
|
65842
65907
|
})))]);
|
|
65843
65908
|
const byId = new Map(all.map((d) => [d.id, d]));
|
|
65844
|
-
return deviceIds.map((deviceId, index) =>
|
|
65845
|
-
deviceId,
|
|
65846
|
-
|
|
65847
|
-
|
|
65909
|
+
return deviceIds.map((deviceId, index) => {
|
|
65910
|
+
const resolved = resolveFor(deviceId, all, byId, blobs[index] ?? {});
|
|
65911
|
+
warnLocationInvisibleContainers(pctx, deviceId, byId, all, resolved);
|
|
65912
|
+
return {
|
|
65913
|
+
deviceId,
|
|
65914
|
+
...resolved
|
|
65915
|
+
};
|
|
65916
|
+
});
|
|
65848
65917
|
}
|
|
65849
65918
|
async function buildLinkedDevicesContribution(pctx, deviceId) {
|
|
65850
65919
|
const persisted = await pctx.metaStore.resolvePersistedById(deviceId);
|
|
@@ -66773,6 +66842,121 @@ var require_device_manager_addon = __commonJS({
|
|
|
66773
66842
|
roleKeysKeptAtTarget: Object.keys(target.role).toSorted()
|
|
66774
66843
|
};
|
|
66775
66844
|
}
|
|
66845
|
+
var LOCATION_BACKFILL_MARKER_KEY = "locationBackfillVersion";
|
|
66846
|
+
function nearestLocatedAncestor(byId, startParentId) {
|
|
66847
|
+
const visited = /* @__PURE__ */ new Set();
|
|
66848
|
+
let cursor = startParentId;
|
|
66849
|
+
while (cursor !== null && !visited.has(cursor)) {
|
|
66850
|
+
visited.add(cursor);
|
|
66851
|
+
const ancestor = byId.get(cursor);
|
|
66852
|
+
if (!ancestor) return null;
|
|
66853
|
+
if (ancestor.location !== null) return ancestor;
|
|
66854
|
+
cursor = ancestor.parentDeviceId;
|
|
66855
|
+
}
|
|
66856
|
+
return null;
|
|
66857
|
+
}
|
|
66858
|
+
function planNullLocationFills(rows) {
|
|
66859
|
+
const byId = new Map(rows.map((r) => [r.id, r]));
|
|
66860
|
+
const plan = [];
|
|
66861
|
+
for (const row of rows) {
|
|
66862
|
+
if (row.location !== null) continue;
|
|
66863
|
+
if (row.parentDeviceId === null) continue;
|
|
66864
|
+
const hit = nearestLocatedAncestor(byId, row.parentDeviceId);
|
|
66865
|
+
if (hit === null) continue;
|
|
66866
|
+
if (hit.type !== require_dist10.DeviceType.Container) continue;
|
|
66867
|
+
if (hit.location === null) continue;
|
|
66868
|
+
plan.push({
|
|
66869
|
+
id: row.id,
|
|
66870
|
+
location: hit.location,
|
|
66871
|
+
ancestorId: hit.id
|
|
66872
|
+
});
|
|
66873
|
+
}
|
|
66874
|
+
return plan;
|
|
66875
|
+
}
|
|
66876
|
+
async function resolveInheritedLocation(rows, parentDeviceId) {
|
|
66877
|
+
const visited = /* @__PURE__ */ new Set();
|
|
66878
|
+
let cursor = parentDeviceId;
|
|
66879
|
+
while (cursor !== null && !visited.has(cursor)) {
|
|
66880
|
+
visited.add(cursor);
|
|
66881
|
+
const ancestor = await rows.get(cursor);
|
|
66882
|
+
if (ancestor === null) return null;
|
|
66883
|
+
if (ancestor.meta.location !== null) return ancestor.meta.type === require_dist10.DeviceType.Container ? ancestor.meta.location : null;
|
|
66884
|
+
cursor = ancestor.meta.parentDeviceId;
|
|
66885
|
+
}
|
|
66886
|
+
return null;
|
|
66887
|
+
}
|
|
66888
|
+
function groupByAncestor(fills) {
|
|
66889
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
66890
|
+
for (const fill of fills) {
|
|
66891
|
+
const bucket = grouped.get(fill.ancestorId);
|
|
66892
|
+
if (bucket) bucket.push(fill);
|
|
66893
|
+
else grouped.set(fill.ancestorId, [fill]);
|
|
66894
|
+
}
|
|
66895
|
+
return grouped;
|
|
66896
|
+
}
|
|
66897
|
+
async function runLocationBackfillAtBoot(pctx, bootRows) {
|
|
66898
|
+
const logger = pctx.host.ctx.logger;
|
|
66899
|
+
const markerRaw = (await pctx.settings.readAddonStore())[LOCATION_BACKFILL_MARKER_KEY];
|
|
66900
|
+
const completedVersion = typeof markerRaw === "number" ? markerRaw : 0;
|
|
66901
|
+
let repaired = false;
|
|
66902
|
+
if (completedVersion < 1) {
|
|
66903
|
+
const patched = [];
|
|
66904
|
+
const failed = [];
|
|
66905
|
+
await pctx.metaStore.withMetaWriteLock(async () => {
|
|
66906
|
+
const plan = planNullLocationFills((await pctx.metaStore.rows.listAll()).map((r) => r.meta));
|
|
66907
|
+
for (const fill of plan) try {
|
|
66908
|
+
const current = await pctx.metaStore.rows.get(fill.id);
|
|
66909
|
+
if (current === null || current.meta.location !== null) continue;
|
|
66910
|
+
await pctx.metaStore.rows.patch(fill.id, { location: fill.location });
|
|
66911
|
+
patched.push(fill);
|
|
66912
|
+
} catch (err) {
|
|
66913
|
+
failed.push({
|
|
66914
|
+
id: fill.id,
|
|
66915
|
+
error: err
|
|
66916
|
+
});
|
|
66917
|
+
}
|
|
66918
|
+
});
|
|
66919
|
+
repaired = true;
|
|
66920
|
+
for (const [ancestorId, fills] of groupByAncestor(patched)) {
|
|
66921
|
+
pctx.host.ctx.eventBus.emit({
|
|
66922
|
+
id: (0, node_crypto.randomUUID)(),
|
|
66923
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
66924
|
+
source: {
|
|
66925
|
+
type: "device",
|
|
66926
|
+
id: ancestorId
|
|
66927
|
+
},
|
|
66928
|
+
category: require_dist10.EventCategory.DeviceMetaChanged,
|
|
66929
|
+
data: {
|
|
66930
|
+
deviceId: ancestorId,
|
|
66931
|
+
field: "location",
|
|
66932
|
+
value: fills[0]?.location ?? null
|
|
66933
|
+
}
|
|
66934
|
+
});
|
|
66935
|
+
logger.info("location backfill: aligned location-less descendants to their ancestor", {
|
|
66936
|
+
tags: { deviceId: ancestorId },
|
|
66937
|
+
meta: {
|
|
66938
|
+
location: fills[0]?.location,
|
|
66939
|
+
updatedIds: fills.map((f) => f.id)
|
|
66940
|
+
}
|
|
66941
|
+
});
|
|
66942
|
+
}
|
|
66943
|
+
if (failed.length > 0) logger.error("location backfill partially failed \u2014 retrying on the next boot", { meta: {
|
|
66944
|
+
failedIds: failed.map((f) => f.id),
|
|
66945
|
+
errors: failed.map((f) => f.error instanceof Error ? f.error.message : String(f.error))
|
|
66946
|
+
} });
|
|
66947
|
+
else await pctx.metaStore.withAddonStoreWriteLock(async () => {
|
|
66948
|
+
await pctx.settings.writeAddonStore({ [LOCATION_BACKFILL_MARKER_KEY]: 1 });
|
|
66949
|
+
});
|
|
66950
|
+
}
|
|
66951
|
+
const residual = planNullLocationFills((!repaired && bootRows !== void 0 ? bootRows : await pctx.metaStore.rows.listAll()).map((r) => r.meta));
|
|
66952
|
+
for (const [ancestorId, fills] of groupByAncestor(residual)) logger.warn("location cascade misaligned \u2014 location-less descendants under a located ancestor never match location auto-link; re-save the ancestor location to realign", {
|
|
66953
|
+
tags: { deviceId: ancestorId },
|
|
66954
|
+
meta: {
|
|
66955
|
+
location: fills[0]?.location,
|
|
66956
|
+
misalignedDescendantIds: fills.map((f) => f.id)
|
|
66957
|
+
}
|
|
66958
|
+
});
|
|
66959
|
+
}
|
|
66776
66960
|
async function stampIntegrationId(metaStore, ctx, deviceId, integrationId) {
|
|
66777
66961
|
await metaStore.withMetaWriteLock(async () => {
|
|
66778
66962
|
if (!await metaStore.resolvePersistedById(deviceId)) throw new Error(`[device-manager] stampIntegrationId: unknown device id=${deviceId}`);
|
|
@@ -66926,7 +67110,7 @@ var require_device_manager_addon = __commonJS({
|
|
|
66926
67110
|
async function registerDevice(pctx, input) {
|
|
66927
67111
|
const { addonId, stableId, id, type, name, parentDeviceId, features, config } = input;
|
|
66928
67112
|
const featuresArr = Array.isArray(features) ? [...features] : [];
|
|
66929
|
-
const { isFirstRegistration, exportFingerprint, fingerprintChanged } = await pctx.metaStore.withMetaWriteLock(async () => {
|
|
67113
|
+
const { isFirstRegistration, exportFingerprint, fingerprintChanged, inheritedLocation, inheritedDescendantIds, inheritFailed } = await pctx.metaStore.withMetaWriteLock(async () => {
|
|
66930
67114
|
const existingRow = await pctx.metaStore.rows.get(id);
|
|
66931
67115
|
const existingMeta = existingRow?.meta;
|
|
66932
67116
|
const wasRegistered = existingRow?.registered ?? false;
|
|
@@ -66938,6 +67122,10 @@ var require_device_manager_addon = __commonJS({
|
|
|
66938
67122
|
deviceType: type,
|
|
66939
67123
|
features: featuresArr
|
|
66940
67124
|
});
|
|
67125
|
+
const priorLocation = existingMeta?.location ?? null;
|
|
67126
|
+
const resolvedParentId = parentDeviceId ?? null;
|
|
67127
|
+
const parentChanged = existingMeta !== void 0 && (existingMeta.parentDeviceId ?? null) !== resolvedParentId;
|
|
67128
|
+
const inherited = priorLocation === null && resolvedParentId !== null && (isFirst || parentChanged) ? await resolveInheritedLocation(pctx.metaStore.rows, resolvedParentId) : null;
|
|
66941
67129
|
await pctx.metaStore.rows.upsertRegistration({
|
|
66942
67130
|
deviceId: id,
|
|
66943
67131
|
addonId,
|
|
@@ -66945,21 +67133,69 @@ var require_device_manager_addon = __commonJS({
|
|
|
66945
67133
|
type,
|
|
66946
67134
|
name: resolvedName,
|
|
66947
67135
|
userNamed: wasUserNamed,
|
|
66948
|
-
location:
|
|
67136
|
+
location: priorLocation ?? inherited,
|
|
66949
67137
|
disabled: existingMeta?.disabled ?? false,
|
|
66950
|
-
parentDeviceId:
|
|
67138
|
+
parentDeviceId: resolvedParentId,
|
|
66951
67139
|
registered: true,
|
|
66952
67140
|
features: featuresArr,
|
|
66953
67141
|
exportFingerprint: fingerprint
|
|
66954
67142
|
});
|
|
67143
|
+
const descendantsFilled = [];
|
|
67144
|
+
const descendantsFailed = [];
|
|
67145
|
+
if (inherited !== null) for (const descendant of await collectDescendants(pctx.metaStore, id)) {
|
|
67146
|
+
if (descendant.location !== null) continue;
|
|
67147
|
+
try {
|
|
67148
|
+
await pctx.metaStore.rows.patch(descendant.id, { location: inherited });
|
|
67149
|
+
descendantsFilled.push(descendant.id);
|
|
67150
|
+
} catch (err) {
|
|
67151
|
+
descendantsFailed.push({
|
|
67152
|
+
id: descendant.id,
|
|
67153
|
+
error: err
|
|
67154
|
+
});
|
|
67155
|
+
}
|
|
67156
|
+
}
|
|
66955
67157
|
return {
|
|
66956
67158
|
isFirstRegistration: isFirst,
|
|
66957
67159
|
exportFingerprint: fingerprint,
|
|
66958
|
-
fingerprintChanged: existingMeta?.exportFingerprint !== fingerprint
|
|
67160
|
+
fingerprintChanged: existingMeta?.exportFingerprint !== fingerprint,
|
|
67161
|
+
inheritedLocation: inherited,
|
|
67162
|
+
inheritedDescendantIds: descendantsFilled,
|
|
67163
|
+
inheritFailed: descendantsFailed
|
|
66959
67164
|
};
|
|
66960
67165
|
});
|
|
66961
67166
|
if (Object.keys(config).length > 0) await pctx.settings.writeDeviceStore(id, config);
|
|
66962
67167
|
pctx.metaStore.idToAddonId.set(id, addonId);
|
|
67168
|
+
if (inheritedLocation !== null) {
|
|
67169
|
+
for (const changedId of [id, ...inheritedDescendantIds]) pctx.host.ctx.eventBus.emit({
|
|
67170
|
+
id: (0, node_crypto.randomUUID)(),
|
|
67171
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
67172
|
+
source: {
|
|
67173
|
+
type: "device",
|
|
67174
|
+
id: changedId
|
|
67175
|
+
},
|
|
67176
|
+
category: require_dist10.EventCategory.DeviceMetaChanged,
|
|
67177
|
+
data: {
|
|
67178
|
+
deviceId: changedId,
|
|
67179
|
+
field: "location",
|
|
67180
|
+
value: inheritedLocation
|
|
67181
|
+
}
|
|
67182
|
+
});
|
|
67183
|
+
pctx.host.ctx.logger.info("registerDevice: inherited located-ancestor location", {
|
|
67184
|
+
tags: { deviceId: id },
|
|
67185
|
+
meta: {
|
|
67186
|
+
location: inheritedLocation,
|
|
67187
|
+
descendantsFilled: inheritedDescendantIds.length
|
|
67188
|
+
}
|
|
67189
|
+
});
|
|
67190
|
+
if (inheritFailed.length > 0) pctx.host.ctx.logger.error("registerDevice: inherited-location fill partially failed \u2014 some descendants NOT aligned", {
|
|
67191
|
+
tags: { deviceId: id },
|
|
67192
|
+
meta: {
|
|
67193
|
+
location: inheritedLocation,
|
|
67194
|
+
failedIds: inheritFailed.map((f) => f.id),
|
|
67195
|
+
errors: inheritFailed.map((f) => f.error instanceof Error ? f.error.message : String(f.error))
|
|
67196
|
+
}
|
|
67197
|
+
});
|
|
67198
|
+
}
|
|
66963
67199
|
if (isFirstRegistration) pctx.host.ctx.eventBus.emit({
|
|
66964
67200
|
id: (0, node_crypto.randomUUID)(),
|
|
66965
67201
|
timestamp: /* @__PURE__ */ new Date(),
|
|
@@ -69142,7 +69378,8 @@ var require_device_manager_addon = __commonJS({
|
|
|
69142
69378
|
const stateMirror = this.stateMirrorImpl;
|
|
69143
69379
|
const resolvePersistedById = metaStore.resolvePersistedById;
|
|
69144
69380
|
const idToAddonId = metaStore.idToAddonId;
|
|
69145
|
-
|
|
69381
|
+
const bootRows = await deviceRows.listAll();
|
|
69382
|
+
for (const row of bootRows) idToAddonId.set(row.meta.id, row.meta.addonId);
|
|
69146
69383
|
const stampIntegrationId$1 = (deviceId, integrationId) => stampIntegrationId(metaStore, this.ctx, deviceId, integrationId);
|
|
69147
69384
|
const pctx = {
|
|
69148
69385
|
host: this.providerHost,
|
|
@@ -69157,6 +69394,11 @@ var require_device_manager_addon = __commonJS({
|
|
|
69157
69394
|
return provider;
|
|
69158
69395
|
}
|
|
69159
69396
|
};
|
|
69397
|
+
try {
|
|
69398
|
+
await runLocationBackfillAtBoot(pctx, bootRows);
|
|
69399
|
+
} catch (err) {
|
|
69400
|
+
this.ctx.logger.warn("location backfill failed \u2014 fleet boots without it", { meta: { error: require_dist10.errMsg(err) } });
|
|
69401
|
+
}
|
|
69160
69402
|
const provider = {
|
|
69161
69403
|
resolveDeviceOwnerSync: (deviceId) => resolveDeviceOwnerSync(pctx, deviceId),
|
|
69162
69404
|
resolveNativeCapOwnerSync: (capName, deviceId) => resolveNativeCapOwnerSync(pctx, capName, deviceId),
|