@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.
- package/dist/api/static/precompressed-asset.js +11 -1
- package/dist/api/trpc/device-collection-dispatch.js +22 -3
- package/dist/api/trpc/generated-cap-mounts.js +3 -2
- package/dist/api/trpc/generated-cap-routers.js +986 -963
- package/dist/api/trpc/scope-access.js +1 -1
- package/dist/core/addon/addon-registry.service.js +10 -0
- package/package.json +9 -9
|
@@ -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 =
|
|
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
|
|
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),
|