@camstack/server 1.2.355 → 1.2.357
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.
|
@@ -20,10 +20,16 @@
|
|
|
20
20
|
* source the DEVICE has (`CapabilityRegistry.resolveCollectionForDevice` —
|
|
21
21
|
* the enabled providers, minus the ones the operator switched off for this
|
|
22
22
|
* camera through `deviceManager.setWrapperActive`, D556), and the results
|
|
23
|
-
* are concatenated. No dedup, no best-source
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
23
|
+
* are concatenated. No dedup ACROSS SOURCES, no best-source pick: a clip
|
|
24
|
+
* held in two storages appears twice, once per source, and each row says
|
|
25
|
+
* which (D549 decision 11 — stated there as a consequence precisely so it
|
|
26
|
+
* is not "fixed" later). But a source is not a REGISTRATION: one addon
|
|
27
|
+
* reaches this dispatcher as the device's native provider, as the
|
|
28
|
+
* hub-local registration and once per agent node, and those are one
|
|
29
|
+
* source answering three times. Each row is held once, keyed by the
|
|
30
|
+
* identity its provider minted ({@link holdEachRowOnce}); on 592 that was
|
|
31
|
+
* three `analytics` tick-boxes for one thing, D62's defect with the
|
|
32
|
+
* polarity reversed (D554 amended 2026-09-20).
|
|
27
33
|
* 2. **One failing source never empties the others.** Every source is called
|
|
28
34
|
* independently and a rejection removes only its own rows — and says so,
|
|
29
35
|
* per source, with `tags: { deviceId }`. A fan-out where one throwing
|
|
@@ -35,7 +41,9 @@
|
|
|
35
41
|
* ids are self-contained and source-prefixed (`analytics:…`,
|
|
36
42
|
* `native:reolink:onboard:…`, `hksv:…`), so the dispatcher asks each
|
|
37
43
|
* source which ids it owns ({@link SOURCE_LISTING_METHOD}) and hands the
|
|
38
|
-
* call to the one that claims the longest matching prefix.
|
|
44
|
+
* call to the one that claims the longest matching prefix. Several
|
|
45
|
+
* registrations of one source all claim it: the FIRST serving one wins,
|
|
46
|
+
* and it is the registration the collapsed listing row names. An id nobody
|
|
39
47
|
* claims is REFUSED by name — never handed to the first provider, which is
|
|
40
48
|
* how a fabricated `addonId` once came back with another addon's answer
|
|
41
49
|
* (D552 consequences, `rejectUnresolvedAddonPin`).
|
|
@@ -49,7 +57,6 @@
|
|
|
49
57
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
50
58
|
exports.SOURCE_LISTING_METHOD = void 0;
|
|
51
59
|
exports.requireDeviceCollection = requireDeviceCollection;
|
|
52
|
-
const system_1 = require("@camstack/system");
|
|
53
60
|
const types_1 = require("@camstack/types");
|
|
54
61
|
const server_1 = require("@trpc/server");
|
|
55
62
|
/**
|
|
@@ -64,50 +71,127 @@ exports.SOURCE_LISTING_METHOD = 'listSources';
|
|
|
64
71
|
const SOURCE_ID_FIELD = 'source';
|
|
65
72
|
/** The field on a source row that names the addon serving it (D555/D557). */
|
|
66
73
|
const SOURCE_ADDON_FIELD = 'addonId';
|
|
67
|
-
/** The field a source row carries the operator's off switch on (D556 amended). */
|
|
68
|
-
const SOURCE_SWITCHED_OFF_FIELD = 'switchedOff';
|
|
69
|
-
/** The availability field a switched-off row must not carry a value in. */
|
|
70
|
-
const CATALOG_AGE_FIELD = 'catalogAsOf';
|
|
71
74
|
/**
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
+
* The input field naming WHICH of a device's providers to ask. One at a time,
|
|
76
|
+
* by `addonId`, never a list of source ids: the operator settled the surface
|
|
77
|
+
* on 2026-09-20 (*"l'utilizzatore è uno solo"*). Absent means the device's
|
|
78
|
+
* bound provider.
|
|
79
|
+
*/
|
|
80
|
+
const PROVIDER_PIN_FIELD = 'provider';
|
|
81
|
+
/** The capability whose singleton answers what a device is bound to. */
|
|
82
|
+
const DEVICE_MANAGER_CAP = 'device-manager';
|
|
83
|
+
/**
|
|
84
|
+
* EVERY source a DEVICE has — read from the device's BINDINGS, which is the
|
|
85
|
+
* framework's answer to "which providers does this device have".
|
|
86
|
+
*
|
|
87
|
+
* It used to enumerate REGISTRATIONS, and that is what put `analytics` in the
|
|
88
|
+
* picker three times on the first live run (device 592, 2026-09-20): one addon
|
|
89
|
+
* reached here as the device's native provider, as the hub-local registration
|
|
90
|
+
* and again as `pipeline-analytics@little-unraid`, with a fourth on the Mac
|
|
91
|
+
* node that `resolveCollectionForDevice` had already dropped. Three tick-boxes
|
|
92
|
+
* for one thing, and `listClips` triplicating every row underneath them.
|
|
93
|
+
*
|
|
94
|
+
* A binding is one per `(device, cap, provider)` and `setWrapperActive` (D62)
|
|
95
|
+
* is the authority over it, so **three rows for one provider stop being
|
|
96
|
+
* representable** — there is nothing to de-duplicate after the fact. It also
|
|
97
|
+
* makes the list mean what the operator says it means: a source exists for a
|
|
98
|
+
* camera because a configuration elsewhere put it there, not because some
|
|
99
|
+
* addon somewhere registered the capability.
|
|
75
100
|
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
* {@link requireDeviceCollection} calls it for the source listing and for
|
|
83
|
-
* nothing else. The deny set is the registry's PROJECTION of the persisted
|
|
84
|
-
* wrapper activation — the operator's verb is `deviceManager.setWrapperActive`
|
|
85
|
-
* and the authority is its store, never a second one here (D556).
|
|
101
|
+
* The binding names an ADDON, never a shape: `DeviceBindingEntry.kind` is UI
|
|
102
|
+
* metadata and is not branched on here (D13). A named addon is looked up in the
|
|
103
|
+
* cap's collection map, and failing that against the device's native provider,
|
|
104
|
+
* which the registry cannot always NAME (a cross-process native arrives through
|
|
105
|
+
* `setNativeFallback` with no addon id of its own) — in that case the binding is
|
|
106
|
+
* the only thing that names it.
|
|
86
107
|
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
* the reference IS the key.
|
|
108
|
+
* **A bindings read that FAILS keeps the registration set** (D49). Answering an
|
|
109
|
+
* empty list would read as "this camera has no recordings", which is the defect
|
|
110
|
+
* this whole line of work exists to end, so a failure is logged and the old
|
|
111
|
+
* behaviour stands for that call.
|
|
92
112
|
*/
|
|
93
|
-
function sourcesForDevice(registry, capName, deviceId) {
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
|
|
113
|
+
async function sourcesForDevice(registry, capName, deviceId, logger) {
|
|
114
|
+
const registered = new Map(registry.getCollectionEntries(capName));
|
|
115
|
+
const bound = await boundAddonIds(registry, capName, deviceId, logger);
|
|
116
|
+
if (bound === null)
|
|
117
|
+
return registrationsForDevice(registry, capName, deviceId);
|
|
118
|
+
const sources = [];
|
|
119
|
+
for (const addonId of bound) {
|
|
120
|
+
const provider = registered.get(addonId) ?? nativeNamedBy(registry, capName, deviceId, addonId);
|
|
121
|
+
if (provider === undefined) {
|
|
122
|
+
// A binding whose provider never booted is a DROPPED source, and a
|
|
123
|
+
// dropped source that says nothing is indistinguishable from a camera
|
|
124
|
+
// that has none.
|
|
125
|
+
logger?.warn(`${capName}: device is bound to "${addonId}", which has registered no provider here`, { tags: { deviceId, addonId }, meta: { capability: capName } });
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
sources.push({ addonId, provider });
|
|
129
|
+
}
|
|
130
|
+
return sources;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* The addon ids the device is bound to for `capName`, or `null` when the
|
|
134
|
+
* bindings could not be read at all (never an empty list for that case — the
|
|
135
|
+
* two must not look alike).
|
|
136
|
+
*/
|
|
137
|
+
async function boundAddonIds(registry, capName, deviceId, logger) {
|
|
138
|
+
const manager = registry.getSingleton(DEVICE_MANAGER_CAP);
|
|
139
|
+
if (manager === null || typeof manager.getBindings !== 'function') {
|
|
140
|
+
logger?.warn(`${capName}: no device manager to read this device's bindings from`, {
|
|
141
|
+
tags: { deviceId },
|
|
142
|
+
meta: { capability: capName },
|
|
143
|
+
});
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
try {
|
|
147
|
+
const answer = await manager.getBindings({ deviceId });
|
|
148
|
+
const ids = [];
|
|
149
|
+
for (const entry of answer.entries) {
|
|
150
|
+
if (entry.capName !== capName)
|
|
151
|
+
continue;
|
|
152
|
+
const addonId = entry.providerAddonId;
|
|
153
|
+
if (addonId.length > 0 && !ids.includes(addonId))
|
|
154
|
+
ids.push(addonId);
|
|
155
|
+
}
|
|
156
|
+
return ids;
|
|
157
|
+
}
|
|
158
|
+
catch (err) {
|
|
159
|
+
logger?.warn(`${capName}: could not read this device's bindings — keeping the registrations`, {
|
|
160
|
+
tags: { deviceId },
|
|
161
|
+
meta: { capability: capName, reason: errMessage(err) },
|
|
162
|
+
});
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* The device's native provider when the binding's addon id is the one that
|
|
168
|
+
* registered it — or when the registry cannot name it at all, which is how a
|
|
169
|
+
* cross-process native arrives (`setNativeFallback`). The native is not in
|
|
170
|
+
* `state.providers`, so it is asked about separately (D556 decision 5).
|
|
171
|
+
*/
|
|
172
|
+
function nativeNamedBy(registry, capName, deviceId, addonId) {
|
|
173
|
+
const native = registry.getNativeProvider(capName, deviceId);
|
|
174
|
+
if (native === null)
|
|
175
|
+
return undefined;
|
|
176
|
+
const nativeAddonId = registry.getNativeAddonId(capName, deviceId);
|
|
177
|
+
if (nativeAddonId !== null && nativeAddonId !== addonId)
|
|
178
|
+
return undefined;
|
|
179
|
+
return native;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* What this dispatcher did before the bindings became its authority, kept for
|
|
183
|
+
* exactly one caller: the failure path above. Every registered provider this
|
|
184
|
+
* device may use, native first.
|
|
185
|
+
*/
|
|
186
|
+
function registrationsForDevice(registry, capName, deviceId) {
|
|
187
|
+
const admitted = new Set(registry.resolveCollectionForDevice(capName, String(deviceId)));
|
|
97
188
|
const sources = [];
|
|
98
189
|
const seen = new Set();
|
|
99
190
|
const native = registry.getNativeProvider(capName, deviceId);
|
|
100
191
|
if (native !== null) {
|
|
101
|
-
// The native is NOT in `state.providers`, so `resolveCollectionForDevice`
|
|
102
|
-
// never sees it — it is asked about separately, against the same authority
|
|
103
|
-
// (D556 decision 5). Without this the picker would carry one row the
|
|
104
|
-
// operator cannot switch off, and it is the camera's OWN recordings:
|
|
105
|
-
// exactly the source the external-videoclips line is about.
|
|
106
|
-
const nativeAddonId = registry.getNativeAddonId(capName, deviceId) ?? 'native';
|
|
107
192
|
sources.push({
|
|
108
|
-
addonId:
|
|
193
|
+
addonId: registry.getNativeAddonId(capName, deviceId) ?? 'native',
|
|
109
194
|
provider: native,
|
|
110
|
-
switchedOff: registry.isDeviceSourceDisabled(String(deviceId), capName, nativeAddonId),
|
|
111
195
|
});
|
|
112
196
|
seen.add(native);
|
|
113
197
|
}
|
|
@@ -115,11 +199,7 @@ function sourcesForDevice(registry, capName, deviceId) {
|
|
|
115
199
|
if (!admitted.has(provider) || seen.has(provider))
|
|
116
200
|
continue;
|
|
117
201
|
seen.add(provider);
|
|
118
|
-
sources.push({
|
|
119
|
-
addonId,
|
|
120
|
-
provider,
|
|
121
|
-
switchedOff: registry.isDeviceSourceDisabled(String(deviceId), capName, addonId),
|
|
122
|
-
});
|
|
202
|
+
sources.push({ addonId, provider });
|
|
123
203
|
}
|
|
124
204
|
return sources;
|
|
125
205
|
}
|
|
@@ -206,7 +286,7 @@ function unreachableSourceRow(addonId, reason) {
|
|
|
206
286
|
availability: { state: 'unreachable', reason },
|
|
207
287
|
};
|
|
208
288
|
}
|
|
209
|
-
async function fanOut(
|
|
289
|
+
async function fanOut(sources, method, input, capName, deviceId, logger) {
|
|
210
290
|
const isSourceListing = method === exports.SOURCE_LISTING_METHOD;
|
|
211
291
|
const settled = await Promise.allSettled(sources.map(async (source) => await callSource(source, method, input, capName, deviceId)));
|
|
212
292
|
const rows = [];
|
|
@@ -233,88 +313,38 @@ async function fanOut(registry, sources, method, input, capName, deviceId, logge
|
|
|
233
313
|
});
|
|
234
314
|
continue;
|
|
235
315
|
}
|
|
236
|
-
rows.push(...finishRows(
|
|
316
|
+
rows.push(...finishRows(source, result.value, isSourceListing));
|
|
237
317
|
}
|
|
238
318
|
return mergeRows(rows);
|
|
239
319
|
}
|
|
240
320
|
/**
|
|
241
|
-
*
|
|
321
|
+
* Stamp each source-listing row with the addon that really served it.
|
|
242
322
|
*
|
|
243
|
-
*
|
|
244
|
-
*
|
|
245
|
-
* confirmation to report and will never get a fresher one — and an age that can
|
|
246
|
-
* only grow draws a STALLING source rather than an off switch (D62: disabled
|
|
247
|
-
* must never look like broken). The rest of the block is left exactly as the
|
|
248
|
-
* provider said it: what the source CAN do is a different question from whether
|
|
249
|
-
* it may.
|
|
250
|
-
*/
|
|
251
|
-
function withoutCatalogAge(availability) {
|
|
252
|
-
if (availability === null || typeof availability !== 'object')
|
|
253
|
-
return availability;
|
|
254
|
-
return Object.fromEntries(Object.entries(availability).filter(([key]) => key !== CATALOG_AGE_FIELD));
|
|
255
|
-
}
|
|
256
|
-
/**
|
|
257
|
-
* Stamp each row with the addon that really served it, mark a source listing
|
|
258
|
-
* row the operator switched off, and drop the CLIP rows of such a source.
|
|
259
|
-
*
|
|
260
|
-
* The two halves are deliberately asymmetric, and that asymmetry is the whole
|
|
261
|
-
* of D556's amendment: a switched-off source contributes no clips (the point of
|
|
262
|
-
* the switch) and keeps its row (the control the switch is undone from). A
|
|
263
|
-
* `listSources` that dropped the row left an unticked source with nothing to
|
|
264
|
-
* un-tick — on a Reolink camera, which has exactly one source since D555's
|
|
265
|
-
* collapse, that is the entire picker.
|
|
266
|
-
*
|
|
267
|
-
* **The stamp** is authoritative: a surface cannot otherwise resolve a source
|
|
268
|
-
* row to who answers it, and the alternative — a `native:reolink:* →
|
|
323
|
+
* The stamp is authoritative: a surface cannot otherwise resolve a source row
|
|
324
|
+
* to who answers it, and the alternative — a `native:reolink:* →
|
|
269
325
|
* provider-reolink` prefix table inside the widget — is a second authority on
|
|
270
326
|
* provider identity living in the one package with no business knowing it
|
|
271
327
|
* (D557). The registry's id is a fact; a provider's own value is a claim, so
|
|
272
328
|
* the fact wins.
|
|
273
329
|
*
|
|
274
|
-
*
|
|
275
|
-
*
|
|
276
|
-
*
|
|
277
|
-
*
|
|
278
|
-
*
|
|
279
|
-
*
|
|
280
|
-
*
|
|
281
|
-
* A camera with NO deny entry is untouched: the rows come back verbatim, in
|
|
282
|
-
* the provider's own order, which is D554 decision 6's guarantee and today's
|
|
283
|
-
* behaviour for every camera on the fleet.
|
|
330
|
+
* It used to also DROP the rows of a source the operator had switched off, and
|
|
331
|
+
* mark the listing row that let him switch it back on. That whole half went on
|
|
332
|
+
* 2026-09-20: every provider always works, and a source exists for a camera
|
|
333
|
+
* because a binding put it there — so "off" is an unbound provider, not a
|
|
334
|
+
* second store beside the binding (D556/D557 amended). Nothing here filters
|
|
335
|
+
* any more; the filtering happened when {@link sourcesForDevice} read the
|
|
336
|
+
* bindings.
|
|
284
337
|
*/
|
|
285
|
-
function finishRows(
|
|
286
|
-
|
|
338
|
+
function finishRows(source, rows, isSourceListing) {
|
|
339
|
+
if (!isSourceListing)
|
|
340
|
+
return rows;
|
|
287
341
|
const out = [];
|
|
288
342
|
for (const row of rows) {
|
|
289
343
|
if (row === null || typeof row !== 'object') {
|
|
290
344
|
out.push(row);
|
|
291
345
|
continue;
|
|
292
346
|
}
|
|
293
|
-
|
|
294
|
-
// ONE predicate for the whole deny set, migration rule included: an entry
|
|
295
|
-
// is a source id, or (pre-re-key, D555) the id of the addon that serves it.
|
|
296
|
-
const off = denied.length > 0 &&
|
|
297
|
-
(0, system_1.isSourceDenied)(denied, {
|
|
298
|
-
addonId: source.addonId,
|
|
299
|
-
...(typeof rowSourceId === 'string' ? { sourceId: rowSourceId } : {}),
|
|
300
|
-
});
|
|
301
|
-
if (isSourceListing) {
|
|
302
|
-
const stamped = {
|
|
303
|
-
...row,
|
|
304
|
-
[SOURCE_ADDON_FIELD]: source.addonId,
|
|
305
|
-
};
|
|
306
|
-
out.push(off
|
|
307
|
-
? {
|
|
308
|
-
...stamped,
|
|
309
|
-
availability: withoutCatalogAge(Reflect.get(row, 'availability')),
|
|
310
|
-
[SOURCE_SWITCHED_OFF_FIELD]: true,
|
|
311
|
-
}
|
|
312
|
-
: stamped);
|
|
313
|
-
continue;
|
|
314
|
-
}
|
|
315
|
-
if (off)
|
|
316
|
-
continue;
|
|
317
|
-
out.push(row);
|
|
347
|
+
out.push({ ...row, [SOURCE_ADDON_FIELD]: source.addonId });
|
|
318
348
|
}
|
|
319
349
|
return out;
|
|
320
350
|
}
|
|
@@ -402,6 +432,31 @@ async function dispatchScalar(sources, method, input, capName, deviceId, logger)
|
|
|
402
432
|
`An unclaimed id is refused rather than answered by another source.`,
|
|
403
433
|
});
|
|
404
434
|
}
|
|
435
|
+
/**
|
|
436
|
+
* The device's sources narrowed to the one the caller PINNED, if it pinned one.
|
|
437
|
+
*
|
|
438
|
+
* A pin the device is not bound to is REFUSED by name. Answering it from
|
|
439
|
+
* another provider is how a fabricated addon id once came back with somebody
|
|
440
|
+
* else's answer (D552, `rejectUnresolvedAddonPin`), and here it would hand a
|
|
441
|
+
* caller who asked for the camera's own SD card the analytics visit log with
|
|
442
|
+
* nothing saying so.
|
|
443
|
+
*/
|
|
444
|
+
function pinnedProvider(sources, input, capName, deviceId) {
|
|
445
|
+
if (input === null || typeof input !== 'object')
|
|
446
|
+
return sources;
|
|
447
|
+
const pin = Reflect.get(input, PROVIDER_PIN_FIELD);
|
|
448
|
+
if (typeof pin !== 'string' || pin.length === 0)
|
|
449
|
+
return sources;
|
|
450
|
+
const picked = sources.filter((source) => source.addonId === pin);
|
|
451
|
+
if (picked.length > 0)
|
|
452
|
+
return picked;
|
|
453
|
+
throw new server_1.TRPCError({
|
|
454
|
+
code: 'BAD_REQUEST',
|
|
455
|
+
message: `Capability "${capName}": device ${deviceId} is not bound to provider "${pin}". ` +
|
|
456
|
+
`Bound providers: ${sources.map((source) => source.addonId).join(', ') || '(none)'}. ` +
|
|
457
|
+
`A pin nobody answers is refused rather than served by another provider.`,
|
|
458
|
+
});
|
|
459
|
+
}
|
|
405
460
|
/**
|
|
406
461
|
* Build the per-device COLLECTION dispatcher for `capName` — the collection
|
|
407
462
|
* twin of `requireDeviceScoped`, and the provider object the cap router's
|
|
@@ -425,53 +480,27 @@ function requireDeviceCollection(registry, capName, logger) {
|
|
|
425
480
|
message: `${capName}.${prop}: input must carry numeric "deviceId"`,
|
|
426
481
|
});
|
|
427
482
|
}
|
|
428
|
-
const
|
|
429
|
-
|
|
483
|
+
const bound = await sourcesForDevice(registry, capName, deviceId, logger);
|
|
484
|
+
const sources = pinnedProvider(bound, input, capName, deviceId);
|
|
485
|
+
if (sources.length === 0) {
|
|
430
486
|
throw new server_1.TRPCError({
|
|
431
487
|
code: 'PRECONDITION_FAILED',
|
|
432
488
|
message: `Capability "${capName}" not registered for device ${deviceId}`,
|
|
433
489
|
});
|
|
434
490
|
}
|
|
435
|
-
// The LISTING sees every source the device has, switched off or not
|
|
436
|
-
// — that row is the only control the operator has to switch one back
|
|
437
|
-
// on. Everything else sees the SERVING set.
|
|
438
|
-
const listing = prop === exports.SOURCE_LISTING_METHOD;
|
|
439
|
-
const sources = listing ? all : all.filter((source) => !source.switchedOff);
|
|
440
|
-
if (sources.length === 0) {
|
|
441
|
-
// Every source is switched off. That is an operator's decision and
|
|
442
|
-
// not a missing provider, so it must not wear PRECONDITION_FAILED's
|
|
443
|
-
// "not registered" — the rows say which it is, and the answer to
|
|
444
|
-
// "what clips does it have" is honestly none (D556 left this open).
|
|
445
|
-
logger?.info(`${capName}.${prop}: every source of this device is switched off`, {
|
|
446
|
-
tags: { deviceId },
|
|
447
|
-
meta: { capability: capName, method: prop, sources: all.map((s) => s.addonId) },
|
|
448
|
-
});
|
|
449
|
-
if (isFanOutMethod(registry, capName, prop))
|
|
450
|
-
return [];
|
|
451
|
-
throw new server_1.TRPCError({
|
|
452
|
-
code: 'BAD_REQUEST',
|
|
453
|
-
message: `Capability "${capName}.${prop}": every source of device ${deviceId} is ` +
|
|
454
|
-
`switched off for this camera. Switch one back on to serve it.`,
|
|
455
|
-
});
|
|
456
|
-
}
|
|
457
491
|
const first = sources[0];
|
|
458
492
|
if (isFanOutMethod(registry, capName, prop)) {
|
|
459
493
|
// One source is not a merge: its rows travel verbatim, in its own
|
|
460
494
|
// order, so a camera with one provider answers exactly as it did
|
|
461
|
-
// before this dispatcher existed.
|
|
495
|
+
// before this dispatcher existed (D554 decision 6). Only the
|
|
496
|
+
// listing rows are finished, and only to carry the addon stamp.
|
|
462
497
|
if (sources.length === 1 && first !== undefined) {
|
|
463
|
-
// One PROVIDER is not a merge — but it may still serve several
|
|
464
|
-
// SOURCES, and the operator may have switched one of them off.
|
|
465
|
-
// So the rows are finished (stamped, and filtered by source)
|
|
466
|
-
// even here; with no deny entry that is the identity function,
|
|
467
|
-
// so an untouched camera answers exactly as it did before
|
|
468
|
-
// (D554 decision 6).
|
|
469
498
|
const answered = await callSource(first, prop, input, capName, deviceId);
|
|
470
499
|
if (!Array.isArray(answered))
|
|
471
500
|
return answered;
|
|
472
|
-
return finishRows(
|
|
501
|
+
return finishRows(first, answered, prop === exports.SOURCE_LISTING_METHOD);
|
|
473
502
|
}
|
|
474
|
-
return await fanOut(
|
|
503
|
+
return await fanOut(sources, prop, input, capName, deviceId, logger);
|
|
475
504
|
}
|
|
476
505
|
if (sources.length === 1 && first !== undefined) {
|
|
477
506
|
return await callSource(first, prop, input, capName, deviceId);
|
|
@@ -6729,6 +6729,15 @@ function createCapRouter_pipelineAnalytics(getProvider, createRemoteProxy) {
|
|
|
6729
6729
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
|
|
6730
6730
|
return p.listEventMedia(methodInput);
|
|
6731
6731
|
}),
|
|
6732
|
+
ownersWithMedia: trpc_middleware_js_1.protectedProcedure
|
|
6733
|
+
.input(types_87.pipelineAnalyticsCapability.methods.ownersWithMedia.input.loose())
|
|
6734
|
+
.output(types_87.pipelineAnalyticsCapability.methods.ownersWithMedia.output)
|
|
6735
|
+
.query(async ({ input, ctx }) => {
|
|
6736
|
+
const { nodeId, ...methodInput } = input;
|
|
6737
|
+
const p = resolveProvider('pipeline-analytics', nodeId, () => getProvider(ctx), createRemoteProxy);
|
|
6738
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
|
|
6739
|
+
return p.ownersWithMedia(methodInput);
|
|
6740
|
+
}),
|
|
6732
6741
|
searchObjectEvents: trpc_middleware_js_1.protectedProcedure
|
|
6733
6742
|
.input(types_87.pipelineAnalyticsCapability.methods.searchObjectEvents.input.loose())
|
|
6734
6743
|
.output(types_87.pipelineAnalyticsCapability.methods.searchObjectEvents.output)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@camstack/server",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.357",
|
|
4
4
|
"private": false,
|
|
5
5
|
"files": [
|
|
6
6
|
"dist",
|
|
@@ -35,18 +35,18 @@
|
|
|
35
35
|
]
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@camstack/addon-admin-ui": "1.2.
|
|
39
|
-
"@camstack/addon-agent-ui": "1.2.
|
|
38
|
+
"@camstack/addon-admin-ui": "1.2.246",
|
|
39
|
+
"@camstack/addon-agent-ui": "1.2.134",
|
|
40
40
|
"@camstack/addon-auth": "1.2.123",
|
|
41
41
|
"@camstack/addon-decoder-nodeav": "1.2.115",
|
|
42
42
|
"@camstack/addon-notifiers": "1.2.119",
|
|
43
|
-
"@camstack/addon-pipeline": "1.2.
|
|
44
|
-
"@camstack/addon-pipeline-orchestrator": "1.2.
|
|
45
|
-
"@camstack/addon-post-analysis": "1.2.
|
|
43
|
+
"@camstack/addon-pipeline": "1.2.274",
|
|
44
|
+
"@camstack/addon-pipeline-orchestrator": "1.2.232",
|
|
45
|
+
"@camstack/addon-post-analysis": "1.2.273",
|
|
46
46
|
"@camstack/sdk": "1.2.118",
|
|
47
|
-
"@camstack/system": "1.2.
|
|
48
|
-
"@camstack/types": "1.2.
|
|
49
|
-
"@camstack/ui-library": "1.2.
|
|
47
|
+
"@camstack/system": "1.2.278",
|
|
48
|
+
"@camstack/types": "1.2.236",
|
|
49
|
+
"@camstack/ui-library": "1.2.192",
|
|
50
50
|
"@fastify/compress": "^9.0.0",
|
|
51
51
|
"@fastify/cookie": "^11.0.2",
|
|
52
52
|
"@fastify/cors": "^11.2.0",
|