@camstack/server 1.2.368 → 1.2.370
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.
|
@@ -22,6 +22,8 @@
|
|
|
22
22
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
23
23
|
exports.COLLECTION_ARRAY_METHODS = void 0;
|
|
24
24
|
exports.aggregateCollectionCall = aggregateCollectionCall;
|
|
25
|
+
exports.devicePinOf = devicePinOf;
|
|
26
|
+
exports.pinnedDeviceCollectionCall = pinnedDeviceCollectionCall;
|
|
25
27
|
exports.addonPinOf = addonPinOf;
|
|
26
28
|
exports.pinnedCollectionCall = pinnedCollectionCall;
|
|
27
29
|
exports.createCapRouterPrimitives = createCapRouterPrimitives;
|
|
@@ -142,6 +144,84 @@ async function aggregateCollectionCall(reg, capName, method, args) {
|
|
|
142
144
|
const out = await fn(args);
|
|
143
145
|
return Array.isArray(out) ? out : null;
|
|
144
146
|
}
|
|
147
|
+
/**
|
|
148
|
+
* A DEVICE collection cap — several providers PER DEVICE, mounted per device.
|
|
149
|
+
*
|
|
150
|
+
* `videoclips` is the one: `resolveCapMount` checks `scope: 'device' + kind:
|
|
151
|
+
* 'wrapper'` BEFORE `mode: 'collection'` and answers `device-scoped`, so
|
|
152
|
+
* {@link isGlobalCollection} is false for it and the `{ addonId }` pin above
|
|
153
|
+
* does not apply. Its pin is `provider`, read by
|
|
154
|
+
* `device-collection-dispatch.ts` on the tRPC path.
|
|
155
|
+
*/
|
|
156
|
+
function isDeviceCollection(reg, capName) {
|
|
157
|
+
const def = reg.getDefinition(capName);
|
|
158
|
+
if (def === undefined)
|
|
159
|
+
return false;
|
|
160
|
+
return def.mode === 'collection' && (0, types_1.resolveCapMount)(def).kind === 'device-scoped';
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* The `{ provider }` a DEVICE-collection call pins to, or `null`.
|
|
164
|
+
*
|
|
165
|
+
* The device twin of {@link addonPinOf}, and it exists because the same defect
|
|
166
|
+
* was found a THIRD time. `videoclips` has three providers on the operator's
|
|
167
|
+
* hub; the clip byte read named `provider-reolink` every time and was answered
|
|
168
|
+
* by the recorder, then — after a redeploy in a different order — by
|
|
169
|
+
* export-hap ("was not minted by the HomeKit source"), which is what a
|
|
170
|
+
* first-wins resolution looks like from outside.
|
|
171
|
+
*
|
|
172
|
+
* Two earlier fixes were each correct and each missed this path:
|
|
173
|
+
* - D592 fixed the tRPC MOUNT, which a forked addon never reaches.
|
|
174
|
+
* - D593 fixed the Moleculer transport link — but a hub-local runner is a
|
|
175
|
+
* broker-less UDS child whose link chain is `[localProviderLink,
|
|
176
|
+
* ipcParentLink]` with no `brokerTransportLink` at all
|
|
177
|
+
* (`addon-context-factory.ts`), so it never ran there either.
|
|
178
|
+
*
|
|
179
|
+
* The call lands on the parent's `onUnownedCall`, where routing discards the
|
|
180
|
+
* selector by construction — `resolveCapRoute` takes only `nodeId`/`deviceId`
|
|
181
|
+
* — and `cap-child-index.ts` answers "first child wins".
|
|
182
|
+
*
|
|
183
|
+
* Unlike the global pin this DOES apply to array methods: naming the provider
|
|
184
|
+
* is required on `listClips` too (D554), and the narrowing happens inside
|
|
185
|
+
* `requireDeviceCollection`, which then decides fan-out vs scalar itself.
|
|
186
|
+
*/
|
|
187
|
+
function devicePinOf(reg, capName, args) {
|
|
188
|
+
if (!reg)
|
|
189
|
+
return null;
|
|
190
|
+
if (!isDeviceCollection(reg, capName))
|
|
191
|
+
return null;
|
|
192
|
+
if (args === null || typeof args !== 'object' || Array.isArray(args))
|
|
193
|
+
return null;
|
|
194
|
+
const rec = args;
|
|
195
|
+
if (typeof rec['deviceId'] !== 'number')
|
|
196
|
+
return null;
|
|
197
|
+
const pin = rec[types_1.PROVIDER_PIN_FIELD];
|
|
198
|
+
return typeof pin === 'string' && pin.length > 0 ? pin : null;
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Run a pinned DEVICE-collection call through the SAME dispatcher the tRPC
|
|
202
|
+
* mount uses, so the two paths cannot answer differently.
|
|
203
|
+
*
|
|
204
|
+
* Nothing is re-implemented here on purpose: `requireDeviceCollection` already
|
|
205
|
+
* narrows to the pinned provider, refuses a pin the device is not bound to BY
|
|
206
|
+
* NAME (D552's rule), and picks fan-out or scalar afterwards. Re-deriving any
|
|
207
|
+
* of that here would be the fourth authority on one question.
|
|
208
|
+
*
|
|
209
|
+
* Boxed like {@link pinnedCollectionCall}: a device-collection method may
|
|
210
|
+
* legitimately answer `null`, which a bare return could not tell from "not
|
|
211
|
+
* handled".
|
|
212
|
+
*/
|
|
213
|
+
async function pinnedDeviceCollectionCall(reg, capName, method, args) {
|
|
214
|
+
if (devicePinOf(reg, capName, args) === null)
|
|
215
|
+
return null;
|
|
216
|
+
const dispatcher = (0, device_collection_dispatch_js_1.requireDeviceCollection)(reg, capName);
|
|
217
|
+
if (dispatcher === null)
|
|
218
|
+
return null;
|
|
219
|
+
const fn = dispatcher[method];
|
|
220
|
+
if (typeof fn !== 'function')
|
|
221
|
+
return null;
|
|
222
|
+
const call = fn;
|
|
223
|
+
return { value: await call(args) };
|
|
224
|
+
}
|
|
145
225
|
/**
|
|
146
226
|
* The `{ addonId }` a call PINS to, or `null` when the call is not an
|
|
147
227
|
* addon-pinned collection call and must route normally.
|
|
@@ -354,7 +354,14 @@ class MoleculerService {
|
|
|
354
354
|
// registered provider answers for a resource it does not own: HA's
|
|
355
355
|
// export read Homematic's `null` for `ha_001` and never opened a link
|
|
356
356
|
// (2026-08-09).
|
|
357
|
-
|
|
357
|
+
//
|
|
358
|
+
// The DEVICE twin runs first: `videoclips` mounts `device-scoped`
|
|
359
|
+
// (scope+wrapper is checked before `mode`), so the global pin above
|
|
360
|
+
// does not see it, and its pin is `provider`. Without this a forked
|
|
361
|
+
// addon's clip byte read named `provider-reolink` and was answered by
|
|
362
|
+
// whichever child registered first — the recorder, then export-hap
|
|
363
|
+
// after a redeploy in a different order (2026-09-22, D594).
|
|
364
|
+
callPinnedCollection: async (capName, method, args) => (await (0, cap_router_runtime_js_1.pinnedDeviceCollectionCall)(this.capabilityService.getRegistry(), capName, method, args)) ?? (0, cap_router_runtime_js_1.pinnedCollectionCall)(this.capabilityService.getRegistry(), capName, method, args),
|
|
358
365
|
// Hub-core `$`-service discriminant for the general
|
|
359
366
|
// provider-not-yet-registered recovery. A resolver miss on a NON-core
|
|
360
367
|
// cap (any addon-provided system/singleton cap, e.g.
|
|
@@ -409,7 +416,15 @@ class MoleculerService {
|
|
|
409
416
|
// with it — the bypass and the routing must never disagree about what
|
|
410
417
|
// counts as a pin, or the call is bounced out of the sibling path and
|
|
411
418
|
// then routed by first-provider anyway.
|
|
412
|
-
|
|
419
|
+
//
|
|
420
|
+
// BOTH pins, because this predicate and `callPinnedCollection` must
|
|
421
|
+
// agree about what counts as one. A pin the bypass does not recognise
|
|
422
|
+
// is short-circuited to the first sibling and never reaches the
|
|
423
|
+
// routing that would honour it — "honoured only there is honoured
|
|
424
|
+
// never", which is how the first attempt at the `addonId` fix failed
|
|
425
|
+
// live, and how the `provider` pin failed twice more (D592, D593).
|
|
426
|
+
isAddonPinnedCall: (capName, method, args) => (0, cap_router_runtime_js_1.addonPinOf)(this.capabilityService.getRegistry(), capName, method, args) !== null ||
|
|
427
|
+
(0, cap_router_runtime_js_1.devicePinOf)(this.capabilityService.getRegistry(), capName, args) !== null,
|
|
413
428
|
// Bound every UDS cap call by the cap's own declared timeout, falling
|
|
414
429
|
// back to the channel default. Same resolver the REMOTE plane uses
|
|
415
430
|
// below, so a method that survives 16 minutes across nodes survives it
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@camstack/server",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.370",
|
|
4
4
|
"private": false,
|
|
5
5
|
"files": [
|
|
6
6
|
"dist",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
]
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@camstack/addon-admin-ui": "1.2.
|
|
38
|
+
"@camstack/addon-admin-ui": "1.2.252",
|
|
39
39
|
"@camstack/addon-agent-ui": "1.2.137",
|
|
40
40
|
"@camstack/addon-auth": "1.2.123",
|
|
41
41
|
"@camstack/addon-decoder-nodeav": "1.2.115",
|
|
@@ -44,8 +44,8 @@
|
|
|
44
44
|
"@camstack/addon-pipeline-orchestrator": "1.2.240",
|
|
45
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.
|
|
47
|
+
"@camstack/system": "1.2.285",
|
|
48
|
+
"@camstack/types": "1.2.241",
|
|
49
49
|
"@camstack/ui-library": "1.2.195",
|
|
50
50
|
"@fastify/compress": "^9.0.0",
|
|
51
51
|
"@fastify/cookie": "^11.0.2",
|