camstack 1.2.106 → 1.2.108

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-3C6CQU54.js");
41
+ await import("./launcher-4UETVHGT.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-3C6CQU54.js");
86
+ await import("./launcher-4UETVHGT.js");
87
87
  }
88
88
 
89
89
  // src/commands/setup.ts
@@ -65727,21 +65727,46 @@ var require_device_manager_addon = __commonJS({
65727
65727
  device
65728
65728
  };
65729
65729
  }
65730
+ function persistedOnlineFallback(input) {
65731
+ if (!input.registryPresent) return false;
65732
+ if (input.parentDeviceId !== null) return true;
65733
+ return Object.keys(input.config ?? {}).length > 0;
65734
+ }
65730
65735
  function extractCallerAddonId(input) {
65731
65736
  const raw = Reflect.get(input, "callerAddonId");
65732
65737
  return typeof raw === "string" && raw.length > 0 ? raw : void 0;
65733
65738
  }
65734
65739
  async function listPersistedByAddon(pctx, input) {
65735
65740
  const { addonId } = input;
65736
- return (await pctx.metaStore.rows.listByAddon(addonId)).map(({ meta: m }) => ({
65737
- id: m.id,
65738
- stableId: m.stableId,
65739
- type: m.type,
65740
- name: m.name,
65741
- location: m.location ?? null,
65742
- disabled: m.disabled ?? false,
65743
- parentDeviceId: m.parentDeviceId
65744
- }));
65741
+ const rows = await pctx.metaStore.rows.listByAddon(addonId);
65742
+ const saved = [];
65743
+ for (const row of rows) {
65744
+ const m = row.meta;
65745
+ if (!row.registered) {
65746
+ const reserved = await pctx.settings.readDeviceStore(m.id);
65747
+ if (Object.keys(reserved ?? {}).length === 0) {
65748
+ pctx.host.ctx.logger.warn("Skipping an unregistered device reservation with no configuration \u2014 not a device, not restored", {
65749
+ tags: { deviceId: m.id },
65750
+ meta: {
65751
+ addonId,
65752
+ stableId: m.stableId,
65753
+ type: m.type
65754
+ }
65755
+ });
65756
+ continue;
65757
+ }
65758
+ }
65759
+ saved.push({
65760
+ id: m.id,
65761
+ stableId: m.stableId,
65762
+ type: m.type,
65763
+ name: m.name,
65764
+ location: m.location ?? null,
65765
+ disabled: m.disabled ?? false,
65766
+ parentDeviceId: m.parentDeviceId
65767
+ });
65768
+ }
65769
+ return saved;
65745
65770
  }
65746
65771
  async function readDeviceConfigs(pctx, deviceIds) {
65747
65772
  if (deviceIds.length === 0) return /* @__PURE__ */ new Map();
@@ -65811,7 +65836,11 @@ var require_device_manager_addon = __commonJS({
65811
65836
  disabled: m.disabled ?? false,
65812
65837
  parentDeviceId: m.parentDeviceId ?? null,
65813
65838
  role: toDeviceRole(m.role),
65814
- online: pctx.host.resolveDeviceOnline(m.id, pctx.registry !== null),
65839
+ online: pctx.host.resolveDeviceOnline(m.id, persistedOnlineFallback({
65840
+ registryPresent: pctx.registry !== null,
65841
+ parentDeviceId: m.parentDeviceId ?? null,
65842
+ config: persistedConfig
65843
+ })),
65815
65844
  probed: pctx.host.resolveDeviceProbed(m.id),
65816
65845
  features: persistedFeatures(m.features),
65817
65846
  isCamera: persistedIsCamera(persistedType),
@@ -65856,7 +65885,11 @@ var require_device_manager_addon = __commonJS({
65856
65885
  disabled: m.disabled ?? false,
65857
65886
  parentDeviceId: m.parentDeviceId,
65858
65887
  role: toDeviceRole(m.role),
65859
- online: pctx.host.resolveDeviceOnline(deviceId, true),
65888
+ online: pctx.host.resolveDeviceOnline(deviceId, persistedOnlineFallback({
65889
+ registryPresent: true,
65890
+ parentDeviceId: m.parentDeviceId ?? null,
65891
+ config: persistedConfig
65892
+ })),
65860
65893
  probed: pctx.host.resolveDeviceProbed(deviceId),
65861
65894
  features: persistedFeatures(m.features),
65862
65895
  isCamera: persistedIsCamera(m.type),
@@ -66172,7 +66205,7 @@ var require_device_manager_addon = __commonJS({
66172
66205
  async function adoptDevice(pctx, input) {
66173
66206
  const dp = await pctx.host.requireDeviceProvider(input.addonId);
66174
66207
  if (!await dp.supportsDiscovery({})) throw new Error(`Addon "${input.addonId}" does not support device adoption`);
66175
- const summary = await dp.adoptDiscoveredDevice({ candidate: input.candidate });
66208
+ const summary = await pctx.removalFence.runUnfenced(() => dp.adoptDiscoveredDevice({ candidate: input.candidate }));
66176
66209
  if (input.integrationId !== void 0) try {
66177
66210
  await pctx.stampIntegrationId(summary.id, input.integrationId);
66178
66211
  } catch (err) {
@@ -66194,10 +66227,10 @@ var require_device_manager_addon = __commonJS({
66194
66227
  async function createDevice(pctx, input) {
66195
66228
  const dp = await pctx.host.requireDeviceProvider(input.addonId);
66196
66229
  if (!await dp.supportsManualCreation({})) throw new Error(`Addon "${input.addonId}" does not support manual device creation`);
66197
- const summary = await dp.createDevice({
66230
+ const summary = await pctx.removalFence.runUnfenced(() => dp.createDevice({
66198
66231
  type: input.type,
66199
66232
  config: input.config
66200
- });
66233
+ }));
66201
66234
  if (input.integrationId !== void 0) try {
66202
66235
  await pctx.stampIntegrationId(summary.id, input.integrationId);
66203
66236
  } catch (err) {
@@ -67487,6 +67520,17 @@ var require_device_manager_addon = __commonJS({
67487
67520
  return await pctx.metaStore.withMetaWriteLock(async () => {
67488
67521
  const existing = await pctx.metaStore.rows.findByStableId(addonId, stableId);
67489
67522
  if (existing) return { id: existing.meta.id };
67523
+ const fenced = pctx.removalFence.isPairFenced(addonId, stableId);
67524
+ if (fenced) {
67525
+ pctx.host.ctx.logger.warn("allocateDeviceId refused \u2014 this device was just removed, NOT re-created", {
67526
+ tags: { deviceId: fenced.deviceId },
67527
+ meta: {
67528
+ addonId,
67529
+ stableId
67530
+ }
67531
+ });
67532
+ throw new Error(`[device-manager] allocateDeviceId: device ${fenced.deviceId} (${addonId}/${stableId}) was removed \u2014 refusing to re-create it`);
67533
+ }
67490
67534
  const id = await pctx.metaStore.allocateNextDeviceId();
67491
67535
  await pctx.metaStore.rows.upsert({
67492
67536
  addonId,
@@ -67615,6 +67659,16 @@ var require_device_manager_addon = __commonJS({
67615
67659
  async function registerDevice(pctx, input) {
67616
67660
  const { addonId, stableId, id, type, name, parentDeviceId, features, config } = input;
67617
67661
  const featuresArr = Array.isArray(features) ? [...features] : [];
67662
+ if (pctx.removalFence.isIdFenced(id) ?? pctx.removalFence.isPairFenced(addonId, stableId)) {
67663
+ pctx.host.ctx.logger.warn("registerDevice refused \u2014 this device was removed, NOT resurrected", {
67664
+ tags: { deviceId: id },
67665
+ meta: {
67666
+ addonId,
67667
+ stableId
67668
+ }
67669
+ });
67670
+ throw new Error(`[device-manager] registerDevice: device ${id} (${addonId}/${stableId}) was removed \u2014 refusing to resurrect it`);
67671
+ }
67618
67672
  const { isFirstRegistration, exportFingerprint, fingerprintChanged, inheritedLocation, inheritedDescendantIds, inheritFailed } = await pctx.metaStore.withMetaWriteLock(async () => {
67619
67673
  const existingRow = await pctx.metaStore.rows.get(id);
67620
67674
  const existingMeta = existingRow?.meta;
@@ -67755,6 +67809,11 @@ var require_device_manager_addon = __commonJS({
67755
67809
  const deviceName = persistedMeta.name;
67756
67810
  await pctx.metaStore.withMetaWriteLock(async () => {
67757
67811
  await pctx.metaStore.rows.remove(deviceId);
67812
+ pctx.removalFence.record({
67813
+ deviceId,
67814
+ addonId,
67815
+ stableId
67816
+ });
67758
67817
  });
67759
67818
  await pctx.settings.clearDeviceStore(deviceId);
67760
67819
  await pctx.settings.clearDeviceRuntimeState(deviceId);
@@ -69681,7 +69740,7 @@ var require_device_manager_addon = __commonJS({
69681
69740
  function createMigrationGuard(now = Date.now) {
69682
69741
  const inFlight = /* @__PURE__ */ new Set();
69683
69742
  const completed = /* @__PURE__ */ new Map();
69684
- const pairKey = (sourceId, targetId) => `${sourceId}->${targetId}`;
69743
+ const pairKey2 = (sourceId, targetId) => `${sourceId}->${targetId}`;
69685
69744
  return {
69686
69745
  begin(sourceId, targetId) {
69687
69746
  for (const id of [sourceId, targetId]) if (inFlight.has(id)) throw new Error(`[device-manager] migrateDevice: a migration involving device ${id} is already in flight \u2014 this call was refused, not queued. The running migration's report stands; re-read the device rows before retrying.`);
@@ -69693,13 +69752,13 @@ var require_device_manager_addon = __commonJS({
69693
69752
  inFlight.delete(targetId);
69694
69753
  },
69695
69754
  recordCompleted(record) {
69696
- completed.set(pairKey(record.sourceId, record.targetId), {
69755
+ completed.set(pairKey2(record.sourceId, record.targetId), {
69697
69756
  ...record,
69698
69757
  completedAt: now()
69699
69758
  });
69700
69759
  },
69701
69760
  matchReplay(sourceId, targetId, atSource, atTarget) {
69702
- const key = pairKey(sourceId, targetId);
69761
+ const key = pairKey2(sourceId, targetId);
69703
69762
  const record = completed.get(key);
69704
69763
  if (record === void 0) return null;
69705
69764
  if (now() - record.completedAt > 9e5) {
@@ -69711,6 +69770,56 @@ var require_device_manager_addon = __commonJS({
69711
69770
  }
69712
69771
  };
69713
69772
  }
69773
+ function pairKey(addonId, stableId) {
69774
+ return `${addonId}\0${stableId}`;
69775
+ }
69776
+ function createRemovalFence(options = {}) {
69777
+ const ttlMs = options.ttlMs ?? 3e5;
69778
+ const now = options.now ?? (() => Date.now());
69779
+ const byId = /* @__PURE__ */ new Map();
69780
+ const byPair = /* @__PURE__ */ new Map();
69781
+ let suspensions = 0;
69782
+ const fresh = (entry) => {
69783
+ if (entry === void 0) return null;
69784
+ if (suspensions > 0) return null;
69785
+ if (now() - entry.removedAt >= ttlMs) {
69786
+ byId.delete(entry.deviceId);
69787
+ byPair.delete(pairKey(entry.addonId, entry.stableId));
69788
+ return null;
69789
+ }
69790
+ return entry;
69791
+ };
69792
+ return {
69793
+ record(entry) {
69794
+ const full = {
69795
+ ...entry,
69796
+ removedAt: now()
69797
+ };
69798
+ byId.set(full.deviceId, full);
69799
+ byPair.set(pairKey(full.addonId, full.stableId), full);
69800
+ },
69801
+ isIdFenced(deviceId) {
69802
+ return fresh(byId.get(deviceId));
69803
+ },
69804
+ isPairFenced(addonId, stableId) {
69805
+ return fresh(byPair.get(pairKey(addonId, stableId)));
69806
+ },
69807
+ lift(addonId, stableId) {
69808
+ const entry = byPair.get(pairKey(addonId, stableId));
69809
+ if (entry === void 0) return;
69810
+ byPair.delete(pairKey(addonId, stableId));
69811
+ byId.delete(entry.deviceId);
69812
+ },
69813
+ async runUnfenced(fn) {
69814
+ suspensions += 1;
69815
+ try {
69816
+ return await fn();
69817
+ } finally {
69818
+ suspensions -= 1;
69819
+ }
69820
+ }
69821
+ };
69822
+ }
69714
69823
  var DeviceManagerAddon = class extends require_dist10.BaseAddon {
69715
69824
  constructor() {
69716
69825
  super({});
@@ -69951,6 +70060,7 @@ var require_device_manager_addon = __commonJS({
69951
70060
  registry,
69952
70061
  bindingsDeps: this.bindingsDeps,
69953
70062
  migrationGuard: createMigrationGuard(),
70063
+ removalFence: createRemovalFence(),
69954
70064
  requireDeviceOps,
69955
70065
  stampIntegrationId: stampIntegrationId$1,
69956
70066
  get provider() {
@@ -241146,6 +241256,53 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
241146
241256
  const ids = [...this._permanentRestoreFailures.keys()].join(", ");
241147
241257
  return `${this._permanentRestoreFailures.size} device(s) permanently failed restore (deviceIds: ${ids}) \u2014 restart the ${this.providerName} provider to retry`;
241148
241258
  }
241259
+ /**
241260
+ * A top-level persisted row with NO configuration is a leftover, not a
241261
+ * device — quarantine it instead of dialling it.
241262
+ *
241263
+ * Device 614 was deleted, resurrected as a config-less shell by a write
241264
+ * already in flight (D376), and then dialled at every single boot: eight
241265
+ * `Failed to restore device — bounded retry scheduled` lines in one day,
241266
+ * each one the same Zod refusal on `host` and `password`. The bound was
241267
+ * real, but it is a bound PER PROCESS, and the runner restarts — so nothing
241268
+ * about the row ever stopped costing attempts and log lines.
241269
+ *
241270
+ * No retry can add a configuration that is not there. The row is reported
241271
+ * once, with its `deviceId`, and set aside.
241272
+ *
241273
+ * Top-level only: an accessory child legitimately carries an empty blob (it
241274
+ * lives off its parent), and a hub-adopted child with an emptied blob is
241275
+ * repaired by `healSavedConfig` from its parent instead. Providers that
241276
+ * genuinely create configuration-free top-level devices turn this off.
241277
+ */
241278
+ quarantineConfiglessTopLevelRestores = true;
241279
+ isUnrestorableShell(saved) {
241280
+ if (!this.quarantineConfiglessTopLevelRestores) return false;
241281
+ if (saved.parentDeviceId !== null) return false;
241282
+ const config = saved.config;
241283
+ if (config === void 0) return false;
241284
+ return Object.keys(config).length === 0;
241285
+ }
241286
+ quarantineShell(saved) {
241287
+ this._permanentRestoreFailures.set(saved.id, {
241288
+ deviceId: saved.id,
241289
+ stableId: saved.stableId,
241290
+ type: saved.type,
241291
+ attempts: 0,
241292
+ lastError: "persisted row has no configuration",
241293
+ failedAt: Date.now()
241294
+ });
241295
+ this.ctx.logger.error("Persisted device row has NO configuration \u2014 quarantined: not restored, not retried. Remove it (deviceManager.removeDevice) or re-add the device.", {
241296
+ tags: {
241297
+ deviceId: saved.id,
241298
+ stableId: saved.stableId
241299
+ },
241300
+ meta: {
241301
+ type: saved.type,
241302
+ provider: this.providerName
241303
+ }
241304
+ });
241305
+ }
241149
241306
  cancelRestoreRetries() {
241150
241307
  this._restoreRetryScheduler?.cancel();
241151
241308
  this._restoreRetryScheduler = null;
@@ -241331,6 +241488,10 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
241331
241488
  });
241332
241489
  return;
241333
241490
  }
241491
+ if (this.isUnrestorableShell(saved)) {
241492
+ this.quarantineShell(saved);
241493
+ return;
241494
+ }
241334
241495
  try {
241335
241496
  await attemptRestore(saved);
241336
241497
  } catch (err) {
@@ -414044,8 +414205,8 @@ var require_spa_mount = __commonJS({
414044
414205
  } catch {
414045
414206
  return reply.status(503).send({ error: "Admin UI shell unavailable" });
414046
414207
  }
414047
- const prefix = parseForwardedPrefix(headers[exports.FORWARDED_PREFIX_HEADER]);
414048
- return reply.type("text/html").send(prefix === null ? html : injectSpaMount(html, prefix));
414208
+ const prefix = parseForwardedPrefix(headers[exports.FORWARDED_PREFIX_HEADER]) ?? "";
414209
+ return reply.type("text/html").send(injectSpaMount(html, prefix));
414049
414210
  }
414050
414211
  }
414051
414212
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "camstack",
3
- "version": "1.2.106",
3
+ "version": "1.2.108",
4
4
  "description": "CLI tool for managing and running CamStack server",
5
5
  "keywords": [
6
6
  "camstack",