@camstack/server 1.2.375 → 1.2.377

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.
@@ -56,6 +56,7 @@ exports.isCompressibleAsset = isCompressibleAsset;
56
56
  exports.resolvePrecompressedAsset = resolvePrecompressedAsset;
57
57
  exports.warmPrecompressedAssets = warmPrecompressedAssets;
58
58
  exports.resetPrecompressedWarning = resetPrecompressedWarning;
59
+ exports.precompressedTempPath = precompressedTempPath;
59
60
  exports.precompressedSiblingExists = precompressedSiblingExists;
60
61
  const fs = __importStar(require("node:fs"));
61
62
  const fsp = __importStar(require("node:fs/promises"));
@@ -113,7 +114,7 @@ async function compressToDisk(filePath, brPath, log) {
113
114
  });
114
115
  // Write via a temp file + rename so a concurrent reader never sees a
115
116
  // half-written body.
116
- const tmp = `${brPath}.${String(process.pid)}.tmp`;
117
+ const tmp = precompressedTempPath(brPath);
117
118
  await fsp.writeFile(tmp, out);
118
119
  await fsp.rename(tmp, brPath);
119
120
  log?.('precompressed a static asset', {
@@ -215,6 +216,15 @@ async function listCompressible(dir, out = []) {
215
216
  function resetPrecompressedWarning() {
216
217
  warnedUnwritable = false;
217
218
  }
219
+ /**
220
+ * Where a compression is staged before the rename. Exported so the test can
221
+ * make THIS write fail for any uid: a `chmod 555` on the dist does nothing to
222
+ * root, which is what CI runs as, and the test that relied on it produced a
223
+ * `.br` in a "read-only" directory (2026-09-23).
224
+ */
225
+ function precompressedTempPath(brPath) {
226
+ return `${brPath}.${String(process.pid)}.tmp`;
227
+ }
218
228
  /** Synchronous existence probe used by the callers' own guards. */
219
229
  function precompressedSiblingExists(filePath) {
220
230
  return fs.existsSync(`${filePath}.br`);
@@ -323,7 +323,7 @@ async function fanOut(sources, method, input, capName, deviceId, logger) {
323
323
  });
324
324
  continue;
325
325
  }
326
- rows.push(...finishRows(source, result.value, isSourceListing));
326
+ rows.push(...finishRows(source, result.value, isSourceListing, capName, deviceId, logger));
327
327
  }
328
328
  return mergeRows(rows);
329
329
  }
@@ -345,7 +345,7 @@ async function fanOut(sources, method, input, capName, deviceId, logger) {
345
345
  * filters any more; the filtering happened when {@link sourcesForDevice} read
346
346
  * the bindings.
347
347
  */
348
- function finishRows(source, rows, isSourceListing) {
348
+ function finishRows(source, rows, isSourceListing, capName, deviceId, logger) {
349
349
  if (!isSourceListing)
350
350
  return rows;
351
351
  const out = [];
@@ -354,6 +354,25 @@ function finishRows(source, rows, isSourceListing) {
354
354
  out.push(row);
355
355
  continue;
356
356
  }
357
+ const id = Reflect.get(row, SOURCE_ID_FIELD);
358
+ if (typeof id === 'string') {
359
+ const owner = (0, types_1.reservedSourceOwner)(capName, id);
360
+ if (owner !== null && owner !== source.addonId) {
361
+ // A RESERVED id (D625) is the DEFAULT of a picker, expressed as a
362
+ // value rather than as a sort order. A second row spelling it would
363
+ // put that default back behind the fan-out order — the
364
+ // `?? sources[0]` accident the reservation exists to remove. The row
365
+ // goes and the addon's own rows stay: it may well have real sources,
366
+ // and emptying a camera's picker over one bad row is worse than the
367
+ // bad row. The guard in `scripts/` refuses the same spelling at build
368
+ // time; this is what happens when an old build reaches a new hub.
369
+ logger?.warn(`${capName}.${exports.SOURCE_LISTING_METHOD}: source id "${id}" is RESERVED for "${owner}" — this addon's row is dropped`, {
370
+ tags: { deviceId, addonId: source.addonId },
371
+ meta: { capability: capName, sourceId: id, reservedFor: owner },
372
+ });
373
+ continue;
374
+ }
375
+ }
357
376
  out.push({ ...row, [SOURCE_ADDON_FIELD]: source.addonId });
358
377
  }
359
378
  return out;
@@ -508,7 +527,7 @@ function requireDeviceCollection(registry, capName, logger) {
508
527
  const answered = await callSource(first, prop, input, capName, deviceId);
509
528
  if (!Array.isArray(answered))
510
529
  return answered;
511
- return finishRows(first, answered, prop === exports.SOURCE_LISTING_METHOD);
530
+ return finishRows(first, answered, prop === exports.SOURCE_LISTING_METHOD, capName, deviceId, logger);
512
531
  }
513
532
  return await fanOut(sources, prop, input, capName, deviceId, logger);
514
533
  }
@@ -218,7 +218,6 @@ function mountAllCaps(services) {
218
218
  }, remoteCapProxy),
219
219
  enumSensor: (0, generated_cap_routers_js_1.createCapRouter_enumSensor)((_ctx) => (0, cap_mount_helpers_js_1.requireDeviceScoped)(reg, 'enum-sensor'), remoteCapProxy),
220
220
  eventEmitter: (0, generated_cap_routers_js_1.createCapRouter_eventEmitter)((_ctx) => (0, cap_mount_helpers_js_1.requireDeviceScoped)(reg, 'event-emitter'), remoteCapProxy),
221
- events: (0, generated_cap_routers_js_1.createCapRouter_events)((_ctx) => reg?.getSingleton('events') ?? null, remoteCapProxy),
222
221
  faceGallery: (0, generated_cap_routers_js_1.createCapRouter_faceGallery)((_ctx) => reg?.getSingleton('face-gallery') ?? null, remoteCapProxy),
223
222
  failureContribution: (0, generated_cap_routers_js_1.createCapRouter_failureContribution)((_ctx, addonId) => {
224
223
  if (!reg)
@@ -428,7 +427,9 @@ function mountAllCaps(services) {
428
427
  ptz: (0, generated_cap_routers_js_1.createCapRouter_ptz)((_ctx) => (0, cap_mount_helpers_js_1.requireDeviceScoped)(reg, 'ptz'), remoteCapProxy),
429
428
  ptzAutotrack: (0, generated_cap_routers_js_1.createCapRouter_ptzAutotrack)((_ctx) => (0, cap_mount_helpers_js_1.requireDeviceScoped)(reg, 'ptz-autotrack'), remoteCapProxy),
430
429
  reboot: (0, generated_cap_routers_js_1.createCapRouter_reboot)((_ctx) => (0, cap_mount_helpers_js_1.requireDeviceScoped)(reg, 'reboot'), remoteCapProxy),
431
- recording: (0, generated_cap_routers_js_1.createCapRouter_recording)((_ctx) => reg?.getSingleton('recording') ?? null, remoteCapProxy),
430
+ recording: (0, generated_cap_routers_js_1.createCapRouter_recording)((_ctx) => (0, device_collection_dispatch_js_1.requireDeviceCollection)(reg, 'recording'), remoteCapProxy),
431
+ recordingArchive: (0, generated_cap_routers_js_1.createCapRouter_recordingArchive)((_ctx) => reg?.getSingleton('recording-archive') ??
432
+ null, remoteCapProxy),
432
433
  recordingExport: (0, generated_cap_routers_js_1.createCapRouter_recordingExport)((_ctx) => reg?.getSingleton('recording-export') ??
433
434
  null, remoteCapProxy),
434
435
  recordingOnboard: (0, generated_cap_routers_js_1.createCapRouter_recordingOnboard)((_ctx) => (0, cap_mount_helpers_js_1.requireDeviceScoped)(reg, 'recording-onboard'), remoteCapProxy),