@camstack/server 1.2.601 → 1.2.603
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/oauth2/oauth2-routes.js +30 -12
- package/dist/api/trpc/cap-router-runtime.js +54 -0
- package/dist/api/trpc/generated-cap-mounts.js +9 -2
- package/dist/api/trpc/generated-cap-routers.js +79 -16
- package/dist/api/trpc/trpc.router.js +9 -0
- package/dist/auth/alexa-lambda-page-style.js +27 -0
- package/dist/auth/app-page-shell.js +4 -0
- package/dist/core/addon/addon-registry.service.js +42 -21
- package/dist/core/addon/hub-addon-api.js +27 -0
- package/dist/manual-boot.js +1 -1
- package/package.json +14 -14
|
@@ -6,6 +6,7 @@ exports.validateAuthorizeQuery = validateAuthorizeQuery;
|
|
|
6
6
|
exports.isRedirectUriAllowed = isRedirectUriAllowed;
|
|
7
7
|
exports.summariseScopes = summariseScopes;
|
|
8
8
|
exports.registerOauth2Routes = registerOauth2Routes;
|
|
9
|
+
const types_1 = require("@camstack/types");
|
|
9
10
|
const session_cookie_js_1 = require("../../auth/session-cookie.js");
|
|
10
11
|
const app_page_strings_js_1 = require("../../auth/app-page-strings.js");
|
|
11
12
|
const consent_page_js_1 = require("./consent-page.js");
|
|
@@ -232,7 +233,7 @@ function summariseScopes(scopes) {
|
|
|
232
233
|
* weakened. The RESPONSE carries only the caller's own echoed input; it tells
|
|
233
234
|
* a client nothing about what else this hub has installed.
|
|
234
235
|
*/
|
|
235
|
-
function refuseAuthorize(reply, logger, method, call, refusal) {
|
|
236
|
+
function refuseAuthorize(request, reply, logger, method, call, refusal) {
|
|
236
237
|
logger.warn(`oauth2 authorize refused: ${refusal.error}`, {
|
|
237
238
|
meta: {
|
|
238
239
|
method,
|
|
@@ -245,7 +246,28 @@ function refuseAuthorize(reply, logger, method, call, refusal) {
|
|
|
245
246
|
if (refusal.retryAfterSec !== undefined) {
|
|
246
247
|
void reply.header('Retry-After', String(refusal.retryAfterSec));
|
|
247
248
|
}
|
|
248
|
-
return reply
|
|
249
|
+
return answerRefusal(request, reply, refusal.status, { error: refusal.error, ...refusal.detail }, refusal.status === 503 ? 'unavailable' : 'invalid_request', typeof refusal.detail?.received === 'string' ? refusal.detail.received : undefined);
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Answer a refused authorize. A BROWSER (it asked for HTML: a person who
|
|
253
|
+
* followed a link) is sent to the CamStack completion page with a reason CODE
|
|
254
|
+
* — a bare JSON body is a dead end for them. A machine caller keeps the status
|
|
255
|
+
* and JSON it always got. Relative: the browser is already on the hub's origin,
|
|
256
|
+
* and nothing the request carried (the redirect target, the state, any code)
|
|
257
|
+
* goes into the URL.
|
|
258
|
+
*/
|
|
259
|
+
function answerRefusal(request, reply, status, body, reason, integration) {
|
|
260
|
+
const accept = request.headers.accept;
|
|
261
|
+
if (typeof accept === 'string' && accept.includes('text/html')) {
|
|
262
|
+
return reply.redirect((0, types_1.oauthLandingUrl)(null, {
|
|
263
|
+
status: 'error',
|
|
264
|
+
reason,
|
|
265
|
+
...(integration !== undefined && /^[A-Za-z0-9._-]{1,64}$/.test(integration)
|
|
266
|
+
? { integration }
|
|
267
|
+
: {}),
|
|
268
|
+
}));
|
|
269
|
+
}
|
|
270
|
+
return reply.status(status).send(body);
|
|
249
271
|
}
|
|
250
272
|
/**
|
|
251
273
|
* An authorize call turned away for want of a usable session.
|
|
@@ -317,13 +339,13 @@ function registerOauth2Routes(fastify, deps) {
|
|
|
317
339
|
}
|
|
318
340
|
const registry = deps.getRegistry();
|
|
319
341
|
if (!registry) {
|
|
320
|
-
return reply
|
|
342
|
+
return answerRefusal(request, reply, 503, { error: 'service_unavailable' }, 'unavailable');
|
|
321
343
|
}
|
|
322
344
|
const { descriptors, pendingAddons } = await readIntegrationRegistry(registry);
|
|
323
345
|
const query = request.query;
|
|
324
346
|
const v = validateAuthorizeQuery(query, descriptors, pendingAddons);
|
|
325
347
|
if (!v.ok) {
|
|
326
|
-
return refuseAuthorize(reply, deps.logger, 'GET', call, v);
|
|
348
|
+
return refuseAuthorize(request, reply, deps.logger, 'GET', call, v);
|
|
327
349
|
}
|
|
328
350
|
const descriptor = descriptors.get(v.integration);
|
|
329
351
|
if (!isRedirectUriAllowed(v.redirectUri, descriptor.allowedRedirectPrefixes, descriptor.allowedPrivateHostPaths ?? [])) {
|
|
@@ -337,9 +359,7 @@ function registerOauth2Routes(fastify, deps) {
|
|
|
337
359
|
allowedPrivateHostPaths: descriptor.allowedPrivateHostPaths ?? [],
|
|
338
360
|
},
|
|
339
361
|
});
|
|
340
|
-
return reply
|
|
341
|
-
.status(400)
|
|
342
|
-
.send({ error: 'invalid_request — redirect_uri not allowed for this integration' });
|
|
362
|
+
return answerRefusal(request, reply, 400, { error: 'invalid_request — redirect_uri not allowed for this integration' }, 'invalid_request', v.integration);
|
|
343
363
|
}
|
|
344
364
|
const html = (0, consent_page_js_1.renderConsentPage)({
|
|
345
365
|
lang: (0, app_page_strings_js_1.resolveAppPageLang)({
|
|
@@ -417,7 +437,7 @@ function registerOauth2Routes(fastify, deps) {
|
|
|
417
437
|
}
|
|
418
438
|
const registry = deps.getRegistry();
|
|
419
439
|
if (!registry) {
|
|
420
|
-
return reply
|
|
440
|
+
return answerRefusal(request, reply, 503, { error: 'service_unavailable' }, 'unavailable');
|
|
421
441
|
}
|
|
422
442
|
const { descriptors, pendingAddons } = await readIntegrationRegistry(registry);
|
|
423
443
|
const body = request.body;
|
|
@@ -433,7 +453,7 @@ function registerOauth2Routes(fastify, deps) {
|
|
|
433
453
|
// and every one of them is attacker-editable.
|
|
434
454
|
const v = validateAuthorizeQuery(formQuery, descriptors, pendingAddons);
|
|
435
455
|
if (!v.ok) {
|
|
436
|
-
return refuseAuthorize(reply, deps.logger, 'POST', call, v);
|
|
456
|
+
return refuseAuthorize(request, reply, deps.logger, 'POST', call, v);
|
|
437
457
|
}
|
|
438
458
|
const descriptor = descriptors.get(v.integration);
|
|
439
459
|
if (!isRedirectUriAllowed(v.redirectUri, descriptor.allowedRedirectPrefixes, descriptor.allowedPrivateHostPaths ?? [])) {
|
|
@@ -447,9 +467,7 @@ function registerOauth2Routes(fastify, deps) {
|
|
|
447
467
|
allowedPrivateHostPaths: descriptor.allowedPrivateHostPaths ?? [],
|
|
448
468
|
},
|
|
449
469
|
});
|
|
450
|
-
return reply
|
|
451
|
-
.status(400)
|
|
452
|
-
.send({ error: 'invalid_request — redirect_uri not allowed for this integration' });
|
|
470
|
+
return answerRefusal(request, reply, 400, { error: 'invalid_request — redirect_uri not allowed for this integration' }, 'invalid_request', v.integration);
|
|
453
471
|
}
|
|
454
472
|
if (body.consent !== 'allow') {
|
|
455
473
|
return reply.redirect(`${v.redirectUri}?error=access_denied&state=${encodeURIComponent(v.state)}`);
|
|
@@ -345,6 +345,54 @@ function createCapRouterPrimitives() {
|
|
|
345
345
|
iterableSubscription: trpc_middleware_js_1.iterableSubscription,
|
|
346
346
|
};
|
|
347
347
|
}
|
|
348
|
+
/**
|
|
349
|
+
* `device-adoption` is collection-scoped: without an explicit `{ addonId }`
|
|
350
|
+
* pin the router must NOT fall through to the first registered provider.
|
|
351
|
+
* Resolve the owning addon from `integrationId` (most methods) or
|
|
352
|
+
* `camDeviceId` (`resync` only).
|
|
353
|
+
*/
|
|
354
|
+
async function inferDeviceAdoptionCollectionAddonPin(methodName, input, deps) {
|
|
355
|
+
const integrationId = typeof input.integrationId === 'string' && input.integrationId.length > 0
|
|
356
|
+
? input.integrationId
|
|
357
|
+
: undefined;
|
|
358
|
+
const camDeviceId = typeof input.camDeviceId === 'number' && Number.isInteger(input.camDeviceId)
|
|
359
|
+
? input.camDeviceId
|
|
360
|
+
: undefined;
|
|
361
|
+
if (methodName === 'resync' || (camDeviceId !== undefined && integrationId === undefined)) {
|
|
362
|
+
if (camDeviceId === undefined) {
|
|
363
|
+
throw new server_1.TRPCError({
|
|
364
|
+
code: 'BAD_REQUEST',
|
|
365
|
+
message: 'deviceAdoption.resync requires camDeviceId (owning addon is resolved from it) or an explicit addonId pin.',
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
const resolved = deps.resolveDeviceAddonId
|
|
369
|
+
? await Promise.resolve(deps.resolveDeviceAddonId(camDeviceId))
|
|
370
|
+
: null;
|
|
371
|
+
if (resolved === null || resolved === '') {
|
|
372
|
+
throw new server_1.TRPCError({
|
|
373
|
+
code: 'BAD_REQUEST',
|
|
374
|
+
message: `deviceAdoption.${methodName}: device ${camDeviceId} not found — pass addonId explicitly.`,
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
return resolved;
|
|
378
|
+
}
|
|
379
|
+
if (integrationId !== undefined) {
|
|
380
|
+
const resolved = deps.resolveIntegrationAddonId
|
|
381
|
+
? await Promise.resolve(deps.resolveIntegrationAddonId(integrationId))
|
|
382
|
+
: null;
|
|
383
|
+
if (resolved === null || resolved === '') {
|
|
384
|
+
throw new server_1.TRPCError({
|
|
385
|
+
code: 'BAD_REQUEST',
|
|
386
|
+
message: `deviceAdoption.${methodName}: unknown integrationId "${integrationId}" — pass addonId explicitly.`,
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
return resolved;
|
|
390
|
+
}
|
|
391
|
+
throw new server_1.TRPCError({
|
|
392
|
+
code: 'BAD_REQUEST',
|
|
393
|
+
message: `deviceAdoption.${methodName} requires addonId (top-level pin) or integrationId to resolve the owning provider.`,
|
|
394
|
+
});
|
|
395
|
+
}
|
|
348
396
|
/** Build the `services` half of `buildCapRouters(primitives, services)`. */
|
|
349
397
|
function createCapRouterServices(deps) {
|
|
350
398
|
const reg = deps.capabilityRegistry;
|
|
@@ -419,10 +467,16 @@ function createCapRouterServices(deps) {
|
|
|
419
467
|
`An unresolvable addonId is refused rather than answered by another provider.`,
|
|
420
468
|
});
|
|
421
469
|
};
|
|
470
|
+
const inferCollectionAddonPin = (capName, methodName, methodInput) => {
|
|
471
|
+
if (capName !== 'device-adoption')
|
|
472
|
+
return undefined;
|
|
473
|
+
return inferDeviceAdoptionCollectionAddonPin(methodName, methodInput, deps);
|
|
474
|
+
};
|
|
422
475
|
return {
|
|
423
476
|
getLocalProvider,
|
|
424
477
|
remoteProxy,
|
|
425
478
|
rejectUnresolvedAddonPin,
|
|
479
|
+
inferCollectionAddonPin,
|
|
426
480
|
noProvider: (capName, nodeId) => {
|
|
427
481
|
if (nodeId !== undefined && nodeId !== 'hub') {
|
|
428
482
|
throw new server_1.TRPCError({
|
|
@@ -186,8 +186,15 @@ function mountAllCaps(services) {
|
|
|
186
186
|
decoder: (0, generated_cap_routers_js_1.createCapRouter_decoder)((_ctx) => reg?.getSingleton('decoder') ?? null, remoteCapProxy),
|
|
187
187
|
detectionPipeline: (0, generated_cap_routers_js_1.createCapRouter_detectionPipeline)((_ctx) => (0, cap_mount_helpers_js_1.requireDeviceScoped)(reg, 'detection-pipeline'), remoteCapProxy),
|
|
188
188
|
deviceAdminLink: (0, generated_cap_routers_js_1.createCapRouter_deviceAdminLink)((_ctx) => (0, cap_mount_helpers_js_1.requireDeviceScoped)(reg, 'device-admin-link'), remoteCapProxy),
|
|
189
|
-
deviceAdoption: (0, generated_cap_routers_js_1.createCapRouter_deviceAdoption)((_ctx) =>
|
|
190
|
-
|
|
189
|
+
deviceAdoption: (0, generated_cap_routers_js_1.createCapRouter_deviceAdoption)((_ctx, addonId) => {
|
|
190
|
+
if (!reg)
|
|
191
|
+
return null;
|
|
192
|
+
if (addonId !== undefined) {
|
|
193
|
+
return reg.getProviderByAddonId('device-adoption', addonId);
|
|
194
|
+
}
|
|
195
|
+
const entries = reg.getCollectionEntries('device-adoption');
|
|
196
|
+
return entries[0]?.[1] ?? null;
|
|
197
|
+
}, remoteCapProxy),
|
|
191
198
|
deviceCredentials: (0, generated_cap_routers_js_1.createCapRouter_deviceCredentials)((_ctx) => reg?.getSingleton('device-credentials') ?? null, remoteCapProxy),
|
|
192
199
|
deviceDiscovery: (0, generated_cap_routers_js_1.createCapRouter_deviceDiscovery)((_ctx) => (0, cap_mount_helpers_js_1.requireDeviceScoped)(reg, 'device-discovery'), remoteCapProxy),
|
|
193
200
|
deviceExport: (0, generated_cap_routers_js_1.createCapRouter_deviceExport)((_ctx, addonId) => {
|
|
@@ -2612,8 +2612,8 @@ function createCapRouter_deviceAdoption(getProvider, createRemoteProxy) {
|
|
|
2612
2612
|
.input(types_2.DEVICE_STATUS_METHOD.getStatus.input.loose())
|
|
2613
2613
|
.output(types_2.DEVICE_STATUS_METHOD.getStatus.output)
|
|
2614
2614
|
.query(async ({ input, ctx }) => {
|
|
2615
|
-
const { nodeId, ...methodInput } = input;
|
|
2616
|
-
const p = resolveProvider('device-adoption', nodeId, () => getProvider(ctx), createRemoteProxy);
|
|
2615
|
+
const { nodeId, addonId, ...methodInput } = input;
|
|
2616
|
+
const p = resolveProvider('device-adoption', nodeId, () => getProvider(ctx, addonId), createRemoteProxy);
|
|
2617
2617
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
|
|
2618
2618
|
return p.getStatus(methodInput);
|
|
2619
2619
|
}),
|
|
@@ -2621,8 +2621,8 @@ function createCapRouter_deviceAdoption(getProvider, createRemoteProxy) {
|
|
|
2621
2621
|
.input(types_45.deviceAdoptionCapability.methods.listCandidateFilters.input.loose())
|
|
2622
2622
|
.output(types_45.deviceAdoptionCapability.methods.listCandidateFilters.output)
|
|
2623
2623
|
.query(async ({ input, ctx }) => {
|
|
2624
|
-
const { nodeId, ...methodInput } = input;
|
|
2625
|
-
const p = resolveProvider('device-adoption', nodeId, () => getProvider(ctx), createRemoteProxy);
|
|
2624
|
+
const { nodeId, addonId, ...methodInput } = input;
|
|
2625
|
+
const p = resolveProvider('device-adoption', nodeId, () => getProvider(ctx, addonId), createRemoteProxy);
|
|
2626
2626
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
|
|
2627
2627
|
return p.listCandidateFilters(methodInput);
|
|
2628
2628
|
}),
|
|
@@ -2630,8 +2630,8 @@ function createCapRouter_deviceAdoption(getProvider, createRemoteProxy) {
|
|
|
2630
2630
|
.input(types_45.deviceAdoptionCapability.methods.listCandidates.input.loose())
|
|
2631
2631
|
.output(types_45.deviceAdoptionCapability.methods.listCandidates.output)
|
|
2632
2632
|
.query(async ({ input, ctx }) => {
|
|
2633
|
-
const { nodeId, ...methodInput } = input;
|
|
2634
|
-
const p = resolveProvider('device-adoption', nodeId, () => getProvider(ctx), createRemoteProxy);
|
|
2633
|
+
const { nodeId, addonId, ...methodInput } = input;
|
|
2634
|
+
const p = resolveProvider('device-adoption', nodeId, () => getProvider(ctx, addonId), createRemoteProxy);
|
|
2635
2635
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
|
|
2636
2636
|
return p.listCandidates(methodInput);
|
|
2637
2637
|
}),
|
|
@@ -2639,8 +2639,8 @@ function createCapRouter_deviceAdoption(getProvider, createRemoteProxy) {
|
|
|
2639
2639
|
.input(types_45.deviceAdoptionCapability.methods.getCandidate.input.loose())
|
|
2640
2640
|
.output(types_45.deviceAdoptionCapability.methods.getCandidate.output)
|
|
2641
2641
|
.query(async ({ input, ctx }) => {
|
|
2642
|
-
const { nodeId, ...methodInput } = input;
|
|
2643
|
-
const p = resolveProvider('device-adoption', nodeId, () => getProvider(ctx), createRemoteProxy);
|
|
2642
|
+
const { nodeId, addonId, ...methodInput } = input;
|
|
2643
|
+
const p = resolveProvider('device-adoption', nodeId, () => getProvider(ctx, addonId), createRemoteProxy);
|
|
2644
2644
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
|
|
2645
2645
|
return p.getCandidate(methodInput);
|
|
2646
2646
|
}),
|
|
@@ -2648,8 +2648,8 @@ function createCapRouter_deviceAdoption(getProvider, createRemoteProxy) {
|
|
|
2648
2648
|
.input(types_45.deviceAdoptionCapability.methods.refresh.input.loose())
|
|
2649
2649
|
.output(types_45.deviceAdoptionCapability.methods.refresh.output)
|
|
2650
2650
|
.mutation(async ({ input, ctx }) => {
|
|
2651
|
-
const { nodeId, ...methodInput } = input;
|
|
2652
|
-
const p = resolveProvider('device-adoption', nodeId, () => getProvider(ctx), createRemoteProxy);
|
|
2651
|
+
const { nodeId, addonId, ...methodInput } = input;
|
|
2652
|
+
const p = resolveProvider('device-adoption', nodeId, () => getProvider(ctx, addonId), createRemoteProxy);
|
|
2653
2653
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
|
|
2654
2654
|
return p.refresh(methodInput);
|
|
2655
2655
|
}),
|
|
@@ -2657,8 +2657,8 @@ function createCapRouter_deviceAdoption(getProvider, createRemoteProxy) {
|
|
|
2657
2657
|
.input(types_45.deviceAdoptionCapability.methods.adopt.input.loose())
|
|
2658
2658
|
.output(types_45.deviceAdoptionCapability.methods.adopt.output)
|
|
2659
2659
|
.mutation(async ({ input, ctx }) => {
|
|
2660
|
-
const { nodeId, ...methodInput } = input;
|
|
2661
|
-
const p = resolveProvider('device-adoption', nodeId, () => getProvider(ctx), createRemoteProxy);
|
|
2660
|
+
const { nodeId, addonId, ...methodInput } = input;
|
|
2661
|
+
const p = resolveProvider('device-adoption', nodeId, () => getProvider(ctx, addonId), createRemoteProxy);
|
|
2662
2662
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
|
|
2663
2663
|
return p.adopt(methodInput);
|
|
2664
2664
|
}),
|
|
@@ -2666,8 +2666,8 @@ function createCapRouter_deviceAdoption(getProvider, createRemoteProxy) {
|
|
|
2666
2666
|
.input(types_45.deviceAdoptionCapability.methods.release.input.loose())
|
|
2667
2667
|
.output(types_45.deviceAdoptionCapability.methods.release.output)
|
|
2668
2668
|
.mutation(async ({ input, ctx }) => {
|
|
2669
|
-
const { nodeId, ...methodInput } = input;
|
|
2670
|
-
const p = resolveProvider('device-adoption', nodeId, () => getProvider(ctx), createRemoteProxy);
|
|
2669
|
+
const { nodeId, addonId, ...methodInput } = input;
|
|
2670
|
+
const p = resolveProvider('device-adoption', nodeId, () => getProvider(ctx, addonId), createRemoteProxy);
|
|
2671
2671
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
|
|
2672
2672
|
return p.release(methodInput);
|
|
2673
2673
|
}),
|
|
@@ -2675,8 +2675,8 @@ function createCapRouter_deviceAdoption(getProvider, createRemoteProxy) {
|
|
|
2675
2675
|
.input(types_45.deviceAdoptionCapability.methods.resync.input.loose())
|
|
2676
2676
|
.output(types_45.deviceAdoptionCapability.methods.resync.output)
|
|
2677
2677
|
.mutation(async ({ input, ctx }) => {
|
|
2678
|
-
const { nodeId, ...methodInput } = input;
|
|
2679
|
-
const p = resolveProvider('device-adoption', nodeId, () => getProvider(ctx), createRemoteProxy);
|
|
2678
|
+
const { nodeId, addonId, ...methodInput } = input;
|
|
2679
|
+
const p = resolveProvider('device-adoption', nodeId, () => getProvider(ctx, addonId), createRemoteProxy);
|
|
2680
2680
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
|
|
2681
2681
|
return p.resync(methodInput);
|
|
2682
2682
|
}),
|
|
@@ -4269,6 +4269,15 @@ function createCapRouter_dishwasher(getProvider, createRemoteProxy) {
|
|
|
4269
4269
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
|
|
4270
4270
|
return p.pause(methodInput);
|
|
4271
4271
|
}),
|
|
4272
|
+
resume: trpc_middleware_js_1.protectedProcedure
|
|
4273
|
+
.input(types_56.dishwasherCapability.methods.resume.input.loose())
|
|
4274
|
+
.output(types_56.dishwasherCapability.methods.resume.output)
|
|
4275
|
+
.mutation(async ({ input, ctx }) => {
|
|
4276
|
+
const { nodeId, ...methodInput } = input;
|
|
4277
|
+
const p = resolveProvider('dishwasher', nodeId, () => getProvider(ctx), createRemoteProxy);
|
|
4278
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
|
|
4279
|
+
return p.resume(methodInput);
|
|
4280
|
+
}),
|
|
4272
4281
|
stop: trpc_middleware_js_1.protectedProcedure
|
|
4273
4282
|
.input(types_56.dishwasherCapability.methods.stop.input.loose())
|
|
4274
4283
|
.output(types_56.dishwasherCapability.methods.stop.output)
|
|
@@ -4314,6 +4323,42 @@ function createCapRouter_dishwasher(getProvider, createRemoteProxy) {
|
|
|
4314
4323
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
|
|
4315
4324
|
return p.setHygienePlus(methodInput);
|
|
4316
4325
|
}),
|
|
4326
|
+
setExtraDry: trpc_middleware_js_1.protectedProcedure
|
|
4327
|
+
.input(types_56.dishwasherCapability.methods.setExtraDry.input.loose())
|
|
4328
|
+
.output(types_56.dishwasherCapability.methods.setExtraDry.output)
|
|
4329
|
+
.mutation(async ({ input, ctx }) => {
|
|
4330
|
+
const { nodeId, ...methodInput } = input;
|
|
4331
|
+
const p = resolveProvider('dishwasher', nodeId, () => getProvider(ctx), createRemoteProxy);
|
|
4332
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
|
|
4333
|
+
return p.setExtraDry(methodInput);
|
|
4334
|
+
}),
|
|
4335
|
+
setIntensiveZone: trpc_middleware_js_1.protectedProcedure
|
|
4336
|
+
.input(types_56.dishwasherCapability.methods.setIntensiveZone.input.loose())
|
|
4337
|
+
.output(types_56.dishwasherCapability.methods.setIntensiveZone.output)
|
|
4338
|
+
.mutation(async ({ input, ctx }) => {
|
|
4339
|
+
const { nodeId, ...methodInput } = input;
|
|
4340
|
+
const p = resolveProvider('dishwasher', nodeId, () => getProvider(ctx), createRemoteProxy);
|
|
4341
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
|
|
4342
|
+
return p.setIntensiveZone(methodInput);
|
|
4343
|
+
}),
|
|
4344
|
+
setVarioSpeedPlus: trpc_middleware_js_1.protectedProcedure
|
|
4345
|
+
.input(types_56.dishwasherCapability.methods.setVarioSpeedPlus.input.loose())
|
|
4346
|
+
.output(types_56.dishwasherCapability.methods.setVarioSpeedPlus.output)
|
|
4347
|
+
.mutation(async ({ input, ctx }) => {
|
|
4348
|
+
const { nodeId, ...methodInput } = input;
|
|
4349
|
+
const p = resolveProvider('dishwasher', nodeId, () => getProvider(ctx), createRemoteProxy);
|
|
4350
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
|
|
4351
|
+
return p.setVarioSpeedPlus(methodInput);
|
|
4352
|
+
}),
|
|
4353
|
+
setChildLock: trpc_middleware_js_1.protectedProcedure
|
|
4354
|
+
.input(types_56.dishwasherCapability.methods.setChildLock.input.loose())
|
|
4355
|
+
.output(types_56.dishwasherCapability.methods.setChildLock.output)
|
|
4356
|
+
.mutation(async ({ input, ctx }) => {
|
|
4357
|
+
const { nodeId, ...methodInput } = input;
|
|
4358
|
+
const p = resolveProvider('dishwasher', nodeId, () => getProvider(ctx), createRemoteProxy);
|
|
4359
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
|
|
4360
|
+
return p.setChildLock(methodInput);
|
|
4361
|
+
}),
|
|
4317
4362
|
setOption: trpc_middleware_js_1.protectedProcedure
|
|
4318
4363
|
.input(types_56.dishwasherCapability.methods.setOption.input.loose())
|
|
4319
4364
|
.output(types_56.dishwasherCapability.methods.setOption.output)
|
|
@@ -4323,6 +4368,15 @@ function createCapRouter_dishwasher(getProvider, createRemoteProxy) {
|
|
|
4323
4368
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
|
|
4324
4369
|
return p.setOption(methodInput);
|
|
4325
4370
|
}),
|
|
4371
|
+
getOptions: trpc_middleware_js_1.protectedProcedure
|
|
4372
|
+
.input(types_56.dishwasherCapability.methods.getOptions.input.loose())
|
|
4373
|
+
.output(types_56.dishwasherCapability.methods.getOptions.output)
|
|
4374
|
+
.query(async ({ input, ctx }) => {
|
|
4375
|
+
const { nodeId, ...methodInput } = input;
|
|
4376
|
+
const p = resolveProvider('dishwasher', nodeId, () => getProvider(ctx), createRemoteProxy);
|
|
4377
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
|
|
4378
|
+
return p.getOptions(methodInput);
|
|
4379
|
+
}),
|
|
4326
4380
|
});
|
|
4327
4381
|
}
|
|
4328
4382
|
function createCapRouter_doorbell(getProvider, createRemoteProxy) {
|
|
@@ -11092,6 +11146,15 @@ function createCapRouter_storageMigration(getProvider, createRemoteProxy) {
|
|
|
11092
11146
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
|
|
11093
11147
|
return p.movers(methodInput);
|
|
11094
11148
|
}),
|
|
11149
|
+
moverClearance: trpc_middleware_js_1.adminProcedure
|
|
11150
|
+
.input(types_127.storageMigrationCapability.methods.moverClearance.input.loose())
|
|
11151
|
+
.output(types_127.storageMigrationCapability.methods.moverClearance.output)
|
|
11152
|
+
.query(async ({ input, ctx }) => {
|
|
11153
|
+
const { nodeId, ...methodInput } = input;
|
|
11154
|
+
const p = resolveProvider('storage-migration', nodeId, () => getProvider(ctx), createRemoteProxy);
|
|
11155
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
|
|
11156
|
+
return p.moverClearance(methodInput);
|
|
11157
|
+
}),
|
|
11095
11158
|
residue: trpc_middleware_js_1.adminProcedure
|
|
11096
11159
|
.input(types_127.storageMigrationCapability.methods.residue.input.loose())
|
|
11097
11160
|
.output(types_127.storageMigrationCapability.methods.residue.output)
|
|
@@ -296,11 +296,20 @@ function buildCapabilityRouters(services) {
|
|
|
296
296
|
// (`import type`) — its value is never called; the codegen
|
|
297
297
|
// `generated-cap-mounts.ts` / `generated-cap-routers.ts` remain the TYPE
|
|
298
298
|
// source of record.
|
|
299
|
+
const integrationRegistry = services.addonRegistry.getIntegrationRegistry();
|
|
300
|
+
const deviceRegistry = services.addonRegistry.getDeviceRegistry();
|
|
299
301
|
const dynamicCaps = (0, cap_router_runtime_js_1.buildRuntimeCapRouters)({
|
|
300
302
|
capabilityRegistry: services.capabilityRegistry,
|
|
301
303
|
moleculer: services.moleculer,
|
|
302
304
|
serverProviders: buildServerProviders(services),
|
|
303
305
|
logger: services.loggingService.createLogger('cap-router'),
|
|
306
|
+
resolveIntegrationAddonId: integrationRegistry
|
|
307
|
+
? async (integrationId) => {
|
|
308
|
+
const row = await integrationRegistry.getIntegration(integrationId);
|
|
309
|
+
return row?.addonId ?? null;
|
|
310
|
+
}
|
|
311
|
+
: undefined,
|
|
312
|
+
resolveDeviceAddonId: (camDeviceId) => deviceRegistry.getAddonId(camDeviceId),
|
|
304
313
|
});
|
|
305
314
|
return {
|
|
306
315
|
// ── Runtime-built cap routers: EVERY cap whose mount is
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.alexaLambdaPageStyleSource = alexaLambdaPageStyleSource;
|
|
4
|
+
/**
|
|
5
|
+
* The source text of the skill Lambda's `page-style.js`: the hub's standalone
|
|
6
|
+
* page stylesheet plus the brand mark, as constants. Kept here, beside
|
|
7
|
+
* `app-page-shell.ts`, so there is ONE place the look lives and a spec holds the
|
|
8
|
+
* Lambda's copy to it (`scripts/gen-alexa-lambda-page-style.ts` writes it).
|
|
9
|
+
*/
|
|
10
|
+
const app_page_shell_js_1 = require("./app-page-shell.js");
|
|
11
|
+
function alexaLambdaPageStyleSource() {
|
|
12
|
+
return `// biome-ignore-all format: generated file
|
|
13
|
+
// biome-ignore-all lint: generated file
|
|
14
|
+
/**
|
|
15
|
+
* GENERATED by scripts/gen-alexa-lambda-page-style.ts from the hub's page shell
|
|
16
|
+
* (server/backend/src/auth/app-page-shell.ts). Do not edit: change the shell and
|
|
17
|
+
* regenerate. The Lambda ships plain files, so the stylesheet travels as a constant.
|
|
18
|
+
*/
|
|
19
|
+
'use strict'
|
|
20
|
+
|
|
21
|
+
const STYLE = ${JSON.stringify(app_page_shell_js_1.APP_PAGE_STYLE)}
|
|
22
|
+
const LOGO_DARK = ${JSON.stringify(app_page_shell_js_1.APP_PAGE_LOGOS.dark)}
|
|
23
|
+
const LOGO_LIGHT = ${JSON.stringify(app_page_shell_js_1.APP_PAGE_LOGOS.light)}
|
|
24
|
+
|
|
25
|
+
module.exports = { STYLE, LOGO_DARK, LOGO_LIGHT }
|
|
26
|
+
`;
|
|
27
|
+
}
|
|
@@ -64,6 +64,10 @@ const COMPONENT_CSS = [
|
|
|
64
64
|
`.error:empty{display:none}`,
|
|
65
65
|
`.btn:focus-visible,.link:focus-visible{outline:2px solid var(--color-primary);outline-offset:2px}`,
|
|
66
66
|
`code{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px;overflow-wrap:anywhere}`,
|
|
67
|
+
`.field{display:flex;flex-direction:column;gap:var(--spacing-md)}`,
|
|
68
|
+
`.field label{font-size:12px;line-height:16px;font-weight:500;color:var(--color-foreground-muted)}`,
|
|
69
|
+
`input[type=url],input[type=text]{width:100%;min-height:44px;padding:10px 12px;border:1px solid var(--color-border);border-radius:var(--radius-lg);background:var(--color-background);color:var(--color-foreground);font:inherit}`,
|
|
70
|
+
`input[type=url]:focus-visible,input[type=text]:focus-visible{outline:2px solid var(--color-primary);outline-offset:2px}`,
|
|
67
71
|
`form{display:contents}`,
|
|
68
72
|
`.actions{display:flex;flex-direction:column;gap:var(--spacing-md)}`,
|
|
69
73
|
].join('');
|
|
@@ -47,6 +47,7 @@ const path = __importStar(require("node:path"));
|
|
|
47
47
|
const system_1 = require("@camstack/system");
|
|
48
48
|
const types_1 = require("@camstack/types");
|
|
49
49
|
const client_1 = require("@trpc/client");
|
|
50
|
+
const hub_addon_api_js_1 = require("./hub-addon-api.js");
|
|
50
51
|
const addon_call_gateway_js_1 = require("./addon-call-gateway.js");
|
|
51
52
|
const addon_row_manifest_1 = require("./addon-row-manifest");
|
|
52
53
|
const addon_settings_provider_js_1 = require("./addon-settings-provider.js");
|
|
@@ -336,29 +337,49 @@ class AddonRegistryService {
|
|
|
336
337
|
*/
|
|
337
338
|
getBrokerApi() {
|
|
338
339
|
if (!this.brokerApi) {
|
|
339
|
-
|
|
340
|
-
const resolver = {
|
|
341
|
-
getByName: (capName) => reg.getSingleton(capName) ?? reg.getAllProviders(capName)[0] ?? null,
|
|
342
|
-
};
|
|
343
|
-
const client = (0, client_1.createTRPCClient)({
|
|
344
|
-
links: [
|
|
345
|
-
(0, system_1.localProviderLink)(resolver),
|
|
346
|
-
// Device-scoped caps go through the hub's CapRouteResolver, which is
|
|
347
|
-
// the only authority that can route `(cap, deviceId)` into a FORKED
|
|
348
|
-
// child — and forked children are broker-less, so the Moleculer
|
|
349
|
-
// lookup below cannot see them at all. Without this an in-process
|
|
350
|
-
// addon asking `ptz.getOptions({deviceId})` got
|
|
351
|
-
// `Service 'ptz.ptz.getOptions' is not found`, which is how the
|
|
352
|
-
// autotrack builtin came up switched on and unable to move a camera
|
|
353
|
-
// (2026-09-18).
|
|
354
|
-
(0, system_1.deviceCapResolverLink)((capName, method, input) => this.moleculer.callCapabilityOnNode(this.moleculer.broker.nodeID, capName, method, input), isDeviceScopedCapName),
|
|
355
|
-
(0, system_1.brokerTransportLink)(this.moleculer.broker),
|
|
356
|
-
],
|
|
357
|
-
});
|
|
358
|
-
this.brokerApi = client;
|
|
340
|
+
this.brokerApi = this.buildBrokerApi(undefined);
|
|
359
341
|
}
|
|
360
342
|
return this.brokerApi;
|
|
361
343
|
}
|
|
344
|
+
/**
|
|
345
|
+
* The `ctx.api` of one in-process addon. Same chain as {@link getBrokerApi},
|
|
346
|
+
* but its `localProviderLink` knows WHO is calling, so a caller-stamped cap
|
|
347
|
+
* (`device-history`, keychain, records) can identify the addon. A shared
|
|
348
|
+
* client cannot: `metrics-native` was refused `declareSeries` ("the caller
|
|
349
|
+
* addon could not be identified") on every sample.
|
|
350
|
+
*/
|
|
351
|
+
getAddonApi(addonId) {
|
|
352
|
+
const cached = this.addonApis.get(addonId);
|
|
353
|
+
if (cached)
|
|
354
|
+
return cached;
|
|
355
|
+
const api = this.buildBrokerApi(addonId);
|
|
356
|
+
this.addonApis.set(addonId, api);
|
|
357
|
+
return api;
|
|
358
|
+
}
|
|
359
|
+
addonApis = new Map();
|
|
360
|
+
buildBrokerApi(callerAddonId) {
|
|
361
|
+
const reg = this.capabilityRegistry;
|
|
362
|
+
const resolver = {
|
|
363
|
+
getByName: (capName) => reg.getSingleton(capName) ?? reg.getAllProviders(capName)[0] ?? null,
|
|
364
|
+
};
|
|
365
|
+
// Device-scoped caps go through the hub's CapRouteResolver, which is
|
|
366
|
+
// the only authority that can route `(cap, deviceId)` into a FORKED
|
|
367
|
+
// child — and forked children are broker-less, so the Moleculer
|
|
368
|
+
// lookup below cannot see them at all. Without this an in-process
|
|
369
|
+
// addon asking `ptz.getOptions({deviceId})` got
|
|
370
|
+
// `Service 'ptz.ptz.getOptions' is not found`, which is how the
|
|
371
|
+
// autotrack builtin came up switched on and unable to move a camera
|
|
372
|
+
// (2026-09-18).
|
|
373
|
+
const links = [
|
|
374
|
+
(0, system_1.deviceCapResolverLink)((capName, method, input) => this.moleculer.callCapabilityOnNode(this.moleculer.broker.nodeID, capName, method, input), isDeviceScopedCapName),
|
|
375
|
+
(0, system_1.brokerTransportLink)(this.moleculer.broker),
|
|
376
|
+
];
|
|
377
|
+
if (callerAddonId !== undefined) {
|
|
378
|
+
return (0, hub_addon_api_js_1.createHubAddonApi)({ resolver, callerAddonId, links });
|
|
379
|
+
}
|
|
380
|
+
const client = (0, client_1.createTRPCClient)({ links: [(0, system_1.localProviderLink)(resolver), ...links] });
|
|
381
|
+
return client;
|
|
382
|
+
}
|
|
362
383
|
// Common settings resolver (3-level: defaults → global → per-device).
|
|
363
384
|
// Lazily constructed on first call to `getSettingsResolver()` so tests
|
|
364
385
|
// that don't exercise the settings API can instantiate the service
|
|
@@ -3013,7 +3034,7 @@ class AddonRegistryService {
|
|
|
3013
3034
|
dataPlane: dataPlaneFacility.dataPlane,
|
|
3014
3035
|
peerBytes: peerBytesFacility.peerBytes,
|
|
3015
3036
|
get api() {
|
|
3016
|
-
return registry.
|
|
3037
|
+
return registry.getAddonApi(addonId);
|
|
3017
3038
|
},
|
|
3018
3039
|
integrationRegistry: this.integrationRegistry ?? undefined,
|
|
3019
3040
|
// Live capability-collection accessor used by addons like stream-broker
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* The `ctx.api` of an in-process hub addon.
|
|
4
|
+
*
|
|
5
|
+
* Identity is the whole point: a caller-stamped capability (`device-history`,
|
|
6
|
+
* keychain, records) learns WHO is calling only from the `callerAddonId` its
|
|
7
|
+
* `localProviderLink` stamps, so each addon needs its own client — a shared one
|
|
8
|
+
* cannot say whose call it is.
|
|
9
|
+
*/
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.createHubAddonApi = createHubAddonApi;
|
|
12
|
+
const client_1 = require("@trpc/client");
|
|
13
|
+
const system_1 = require("@camstack/system");
|
|
14
|
+
function createHubAddonApi(options) {
|
|
15
|
+
// The observer options carry the stamp only. Cap usage stays recorded on the
|
|
16
|
+
// callee side (`LocalChildRegistry`), so in-process calls are not counted
|
|
17
|
+
// here and the hub's usage graph keeps its meaning.
|
|
18
|
+
const observerOpts = {
|
|
19
|
+
callerAddonId: options.callerAddonId,
|
|
20
|
+
providerAddonIdForCap: () => null,
|
|
21
|
+
observer: () => undefined,
|
|
22
|
+
};
|
|
23
|
+
const client = (0, client_1.createTRPCClient)({
|
|
24
|
+
links: [(0, system_1.localProviderLink)(options.resolver, observerOpts), ...options.links],
|
|
25
|
+
});
|
|
26
|
+
return client;
|
|
27
|
+
}
|
package/dist/manual-boot.js
CHANGED
|
@@ -267,7 +267,7 @@ async function bootManual(opts) {
|
|
|
267
267
|
throw new Error('the process service did not answer');
|
|
268
268
|
return rows;
|
|
269
269
|
},
|
|
270
|
-
// The verdict reaches the alert centre (one
|
|
270
|
+
// The verdict reaches the alert centre (one critical alert).
|
|
271
271
|
onResult: (check) => {
|
|
272
272
|
eventBusService.emit({
|
|
273
273
|
id: (0, node_crypto_1.randomUUID)(),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@camstack/server",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.603",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"private": false,
|
|
6
6
|
"files": [
|
|
@@ -38,19 +38,19 @@
|
|
|
38
38
|
]
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@camstack/addon-admin-ui": "1.2.
|
|
42
|
-
"@camstack/addon-agent-ui": "1.2.
|
|
43
|
-
"@camstack/addon-auth": "1.2.
|
|
44
|
-
"@camstack/addon-decoder-nodeav": "1.2.
|
|
45
|
-
"@camstack/addon-notifiers": "1.2.
|
|
46
|
-
"@camstack/addon-pipeline": "1.2.
|
|
47
|
-
"@camstack/addon-pipeline-orchestrator": "1.2.
|
|
48
|
-
"@camstack/addon-post-analysis": "1.2.
|
|
49
|
-
"@camstack/develop": "0.1.
|
|
50
|
-
"@camstack/sdk": "1.2.
|
|
51
|
-
"@camstack/system": "1.2.
|
|
52
|
-
"@camstack/types": "1.2.
|
|
53
|
-
"@camstack/ui-library": "1.2.
|
|
41
|
+
"@camstack/addon-admin-ui": "1.2.440",
|
|
42
|
+
"@camstack/addon-agent-ui": "1.2.246",
|
|
43
|
+
"@camstack/addon-auth": "1.2.151",
|
|
44
|
+
"@camstack/addon-decoder-nodeav": "1.2.125",
|
|
45
|
+
"@camstack/addon-notifiers": "1.2.603",
|
|
46
|
+
"@camstack/addon-pipeline": "1.2.603",
|
|
47
|
+
"@camstack/addon-pipeline-orchestrator": "1.2.604",
|
|
48
|
+
"@camstack/addon-post-analysis": "1.2.608",
|
|
49
|
+
"@camstack/develop": "0.1.15",
|
|
50
|
+
"@camstack/sdk": "1.2.135",
|
|
51
|
+
"@camstack/system": "1.2.454",
|
|
52
|
+
"@camstack/types": "1.2.385",
|
|
53
|
+
"@camstack/ui-library": "1.2.302",
|
|
54
54
|
"@fastify/compress": "^9.0.0",
|
|
55
55
|
"@fastify/cookie": "^11.0.2",
|
|
56
56
|
"@fastify/cors": "^11.2.0",
|