@camstack/addon-provider-dreo 0.2.10 → 0.2.12
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/addon.js +216 -11
- package/dist/addon.mjs +216 -11
- package/package.json +1 -1
package/dist/addon.js
CHANGED
|
@@ -35,7 +35,7 @@ var __toCommonJS = (mod) => __hasOwnProp.call(mod, "module.exports") ? mod["modu
|
|
|
35
35
|
//#endregion
|
|
36
36
|
let crypto$1 = require("crypto");
|
|
37
37
|
let events = require("events");
|
|
38
|
-
//#region ../types/dist/event-category-
|
|
38
|
+
//#region ../types/dist/event-category-Cv9dO26A.mjs
|
|
39
39
|
var EventCategory = /* @__PURE__ */ function(EventCategory) {
|
|
40
40
|
EventCategory["SystemBoot"] = "system.boot";
|
|
41
41
|
EventCategory["SystemAddonsReady"] = "system.addons-ready";
|
|
@@ -51,6 +51,13 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
|
|
|
51
51
|
*/
|
|
52
52
|
EventCategory["SystemRestartCompleted"] = "system.restart-completed";
|
|
53
53
|
/**
|
|
54
|
+
* A newer addon or server-root package version was found by the
|
|
55
|
+
* authoritative registry check. Emitted once per
|
|
56
|
+
* `(target, packageName, currentVersion, latestVersion)` transition; repeated
|
|
57
|
+
* polling of the same result is deduplicated by the checker.
|
|
58
|
+
*/
|
|
59
|
+
EventCategory["UpdateAvailable"] = "update.available";
|
|
60
|
+
/**
|
|
54
61
|
* Readiness transition for a capability provider. Every producer emits
|
|
55
62
|
* this event on `onInitialize` completion, `onDestroy`, and
|
|
56
63
|
* `$node.reconnect`; every consumer that needs to gate on a cross-process
|
|
@@ -14091,8 +14098,35 @@ var NcDeliverySchema = _enum([
|
|
|
14091
14098
|
"immediate",
|
|
14092
14099
|
"track-end",
|
|
14093
14100
|
"device-event",
|
|
14094
|
-
"package-event"
|
|
14101
|
+
"package-event",
|
|
14102
|
+
"system-event"
|
|
14103
|
+
]);
|
|
14104
|
+
/**
|
|
14105
|
+
* Stable Notification Center vocabulary over infrastructure/liveness events.
|
|
14106
|
+
* Bus categories are normalized into these intent-level kinds so rules do not
|
|
14107
|
+
* depend on a provider's raw event name or payload shape.
|
|
14108
|
+
*/
|
|
14109
|
+
var NcSystemEventKindSchema = _enum([
|
|
14110
|
+
"camera-online",
|
|
14111
|
+
"camera-offline",
|
|
14112
|
+
"stream-online",
|
|
14113
|
+
"stream-offline",
|
|
14114
|
+
"node-online",
|
|
14115
|
+
"node-offline",
|
|
14116
|
+
"addon-update-available",
|
|
14117
|
+
"server-update-available"
|
|
14095
14118
|
]);
|
|
14119
|
+
/**
|
|
14120
|
+
* One coherent system-event condition. `kinds` is the required opt-in safety
|
|
14121
|
+
* gate; the remaining lists are optional narrowing filters relevant to the
|
|
14122
|
+
* selected kinds.
|
|
14123
|
+
*/
|
|
14124
|
+
var NcSystemEventConditionSchema = object({
|
|
14125
|
+
kinds: array(NcSystemEventKindSchema).min(1),
|
|
14126
|
+
deviceIds: array(number().int()).min(1).optional(),
|
|
14127
|
+
nodeIds: array(string().min(1)).min(1).optional(),
|
|
14128
|
+
packageNames: array(string().min(1)).min(1).optional()
|
|
14129
|
+
});
|
|
14096
14130
|
/** Weekly schedule — OR of windows; absence on the rule = always active. */
|
|
14097
14131
|
var NcScheduleSchema = object({
|
|
14098
14132
|
windows: array(object({
|
|
@@ -14397,6 +14431,8 @@ var NcConditionsSchema = object({
|
|
|
14397
14431
|
"picked-up",
|
|
14398
14432
|
"both"
|
|
14399
14433
|
]).optional(),
|
|
14434
|
+
/** Infrastructure/liveness/update event matcher (`system-event` delivery). */
|
|
14435
|
+
systemEvent: NcSystemEventConditionSchema.optional(),
|
|
14400
14436
|
/**
|
|
14401
14437
|
* PERSONAL-RULE custom zones (viewer-drawn). Inline normalized polygons
|
|
14402
14438
|
* (MaskShape vocabulary). A record passes when its bbox overlaps ANY
|
|
@@ -14616,7 +14652,8 @@ var NcTestResultSchema = object({
|
|
|
14616
14652
|
"object-event",
|
|
14617
14653
|
"track",
|
|
14618
14654
|
"device-event",
|
|
14619
|
-
"package-event"
|
|
14655
|
+
"package-event",
|
|
14656
|
+
"system-event"
|
|
14620
14657
|
]),
|
|
14621
14658
|
deviceId: number(),
|
|
14622
14659
|
timestamp: number(),
|
|
@@ -14638,7 +14675,8 @@ var NcConditionDescriptorSchema = object({
|
|
|
14638
14675
|
"schedule",
|
|
14639
14676
|
"device",
|
|
14640
14677
|
"package",
|
|
14641
|
-
"occupancy"
|
|
14678
|
+
"occupancy",
|
|
14679
|
+
"system"
|
|
14642
14680
|
]),
|
|
14643
14681
|
label: string(),
|
|
14644
14682
|
/** Editor widget the UI renders — never hardcode per-condition forms. */
|
|
@@ -14656,7 +14694,8 @@ var NcConditionDescriptorSchema = object({
|
|
|
14656
14694
|
"crossingSelect",
|
|
14657
14695
|
"polygonDraw",
|
|
14658
14696
|
"occupancy",
|
|
14659
|
-
"deviceState"
|
|
14697
|
+
"deviceState",
|
|
14698
|
+
"systemEvent"
|
|
14660
14699
|
]),
|
|
14661
14700
|
operator: _enum([
|
|
14662
14701
|
"in",
|
|
@@ -14715,7 +14754,8 @@ var NcHistoryRecordKindSchema = _enum([
|
|
|
14715
14754
|
"object-event",
|
|
14716
14755
|
"track-end",
|
|
14717
14756
|
"device-event",
|
|
14718
|
-
"package-event"
|
|
14757
|
+
"package-event",
|
|
14758
|
+
"system-event"
|
|
14719
14759
|
]);
|
|
14720
14760
|
/** Subject summary frozen on the row at fire time (survives rule/record edits). */
|
|
14721
14761
|
var NcHistorySubjectSchema = object({
|
|
@@ -14723,7 +14763,14 @@ var NcHistorySubjectSchema = object({
|
|
|
14723
14763
|
label: string().optional(),
|
|
14724
14764
|
confidence: number().optional(),
|
|
14725
14765
|
zones: array(string()),
|
|
14726
|
-
timestamp: number()
|
|
14766
|
+
timestamp: number(),
|
|
14767
|
+
systemEvent: object({
|
|
14768
|
+
kind: NcSystemEventKindSchema,
|
|
14769
|
+
subject: string(),
|
|
14770
|
+
title: string(),
|
|
14771
|
+
body: string(),
|
|
14772
|
+
data: record(string(), unknown())
|
|
14773
|
+
}).optional()
|
|
14727
14774
|
});
|
|
14728
14775
|
/**
|
|
14729
14776
|
* One delivery-history row. This is a read-only VIEW over the durable
|
|
@@ -15083,6 +15130,76 @@ object({
|
|
|
15083
15130
|
* Each provider returns a static descriptor; the core enumerates them
|
|
15084
15131
|
* to validate the `integration=` query param and resolve the consent
|
|
15085
15132
|
* label + the scopes baked into the issued token.
|
|
15133
|
+
*
|
|
15134
|
+
* ## Declaring one
|
|
15135
|
+
*
|
|
15136
|
+
* An OAuth client is integration-specific knowledge — who the client is, what
|
|
15137
|
+
* it may ask for, where it may be sent — so it is declared by the ADDON that
|
|
15138
|
+
* owns the integration, never by the kernel and never as a branch inside
|
|
15139
|
+
* `oauth2-routes.ts` ([D101](../../../../docs/decisions/adr-0101.md)). Three
|
|
15140
|
+
* steps, no others:
|
|
15141
|
+
*
|
|
15142
|
+
* 1. Add `{ "name": "oauth-integration" }` to the addon's `camstack.addons[]`
|
|
15143
|
+
* manifest entry. This is also what tells the hub, at addon-LOAD time, that
|
|
15144
|
+
* a descriptor is owed — see "the boot window" below.
|
|
15145
|
+
* 2. Return a provider from `onInitialize()`:
|
|
15146
|
+
*
|
|
15147
|
+
* ```ts
|
|
15148
|
+
* const provider: IOauthIntegrationProvider = {
|
|
15149
|
+
* getDescriptor: async () => ({
|
|
15150
|
+
* integrationId: 'my-thing', // the `integration=` query param
|
|
15151
|
+
* displayName: 'My Thing',
|
|
15152
|
+
* requestedScopes: [ … ], // see below
|
|
15153
|
+
* allowedRedirectPrefixes: ['https://callback.example/'],
|
|
15154
|
+
* }),
|
|
15155
|
+
* }
|
|
15156
|
+
* return [{ capability: oauthIntegrationCapability, provider }]
|
|
15157
|
+
* ```
|
|
15158
|
+
*
|
|
15159
|
+
* The descriptor must be **static** — it is read on the authorize path, so
|
|
15160
|
+
* never put an await on network or disk behind it, and never register it
|
|
15161
|
+
* behind one either (a provider is registered only once `onInitialize`
|
|
15162
|
+
* RETURNS, so anything awaited before the return delays linking).
|
|
15163
|
+
* 3. Nothing else. There is no allow-list to join, no id to register with the
|
|
15164
|
+
* core, and no per-integration branch anywhere: `/api/oauth2/authorize` and
|
|
15165
|
+
* `/api/oauth2/integrations` are built from this collection alone.
|
|
15166
|
+
*
|
|
15167
|
+
* **Scopes. `requestedScopes` has exactly ONE meaning: what the integration
|
|
15168
|
+
* NEEDS to function.** Not a blast radius, not a conservative
|
|
15169
|
+
* under-declaration, not a description of some other path the addon happens to
|
|
15170
|
+
* have. Derive it from what the client actually calls **with this token** —
|
|
15171
|
+
* every tRPC path against `METHOD_ACCESS_MAP`, plus an `addon:` grant for every
|
|
15172
|
+
* addon HTTP route it posts to — and write the call that justifies each entry
|
|
15173
|
+
* next to it. Two integrations once used this field to mean two different
|
|
15174
|
+
* things; the operator ruled there is one meaning, and any third integration
|
|
15175
|
+
* inherits it (2026-08-09).
|
|
15176
|
+
*
|
|
15177
|
+
* This is not documentation, it is the ENFORCEMENT INPUT. Since
|
|
15178
|
+
* [D103](../../../../docs/decisions/adr-0103.md) the `/addon/:addonId/*` gate
|
|
15179
|
+
* checks an integration token's grant before letting it reach an
|
|
15180
|
+
* `access: 'authenticated'` route, so an **under-declaration is an integration
|
|
15181
|
+
* that stops working** — a missing `addon:` entry means `403 Token scope
|
|
15182
|
+
* mismatch` on every control the client tries to actuate. Widen the descriptor
|
|
15183
|
+
* honestly rather than weakening a check to make a route pass.
|
|
15184
|
+
*
|
|
15185
|
+
* Prefer a narrow `capability:` scope to a `category:` one unless the client
|
|
15186
|
+
* genuinely needs a whole family; a category scope grants every future member
|
|
15187
|
+
* of that category too. `category:system [create]` has been rejected once and
|
|
15188
|
+
* should stay rejected: it hands `addons.installPackage` to an integration.
|
|
15189
|
+
*
|
|
15190
|
+
* Calls the ADDON itself makes over `ctx.api` run as the addon and are not
|
|
15191
|
+
* scope-checked, so they are not what this field describes — but reaching the
|
|
15192
|
+
* addon's route in the first place IS, and that is the entry to declare.
|
|
15193
|
+
*
|
|
15194
|
+
* **The boot window.** An addon registers its provider after its runner forks
|
|
15195
|
+
* and initialises, so between hub start and that moment this collection is
|
|
15196
|
+
* incomplete and an `integrationId` can be legitimately absent. The core does
|
|
15197
|
+
* not wait, poll or cache around this ([D3](../../../../docs/decisions/adr-0003.md)):
|
|
15198
|
+
* it compares the manifest declarers against the registered providers and
|
|
15199
|
+
* answers `503 temporarily_unavailable` (with `Retry-After` and the pending
|
|
15200
|
+
* addon ids) instead of `400 unknown integration`, and reports
|
|
15201
|
+
* `complete: false` on `GET /api/oauth2/integrations`. A client should retry
|
|
15202
|
+
* while the list is incomplete rather than conclude the hub cannot do OAuth.
|
|
15086
15203
|
*/
|
|
15087
15204
|
var OauthIntegrationDescriptorSchema = object({
|
|
15088
15205
|
/** Stable id used as the `integration=` query param, e.g. 'export-alexa'. */
|
|
@@ -15113,7 +15230,30 @@ var OauthIntegrationDescriptorSchema = object({
|
|
|
15113
15230
|
* present, /api/oauth2/authorize bakes THIS into the code instead of the
|
|
15114
15231
|
* hub-global `publicHubUrl()`, so a forked exporter addon (which can't set
|
|
15115
15232
|
* the hub's env) drives the claim that its cloud Lambda routes back on. */
|
|
15116
|
-
hubUrl: string().optional()
|
|
15233
|
+
hubUrl: string().optional(),
|
|
15234
|
+
/**
|
|
15235
|
+
* How long a REFRESH token issued for this integration lives — seconds, or
|
|
15236
|
+
* `'never'` for a token minted with no `exp` claim at all. Omit to keep the
|
|
15237
|
+
* 30-day default, which is what every link used before this field existed.
|
|
15238
|
+
*
|
|
15239
|
+
* Declared here for the same reason `requestedScopes` is: the integration
|
|
15240
|
+
* knows what it needs. Amazon's account linking and a Home Assistant config
|
|
15241
|
+
* entry are both meant to survive indefinitely, and re-linking is a manual
|
|
15242
|
+
* user action, so a 30-day expiry silently unlinks a working integration.
|
|
15243
|
+
*
|
|
15244
|
+
* **The security posture, stated so it is owned deliberately.** A refresh
|
|
15245
|
+
* token that never expires is permanent access if it leaks. What bounds it is
|
|
15246
|
+
* revocation, not time: `oauthRefresh` re-reads the session on every use and
|
|
15247
|
+
* returns `null` once `revokedAt` is set, as does `oauthVerifyAccessToken`.
|
|
15248
|
+
* The one gap is the ACCESS token — it is a plain signed JWT that nothing
|
|
15249
|
+
* re-checks against the session on the `/trpc` and `/addon/*` paths, so
|
|
15250
|
+
* revoking a link takes effect there only after its remaining hour. That hour
|
|
15251
|
+
* is why the access TTL is not configurable.
|
|
15252
|
+
*
|
|
15253
|
+
* The value is baked into the authorization code at `/authorize` and travels
|
|
15254
|
+
* on the tokens, so editing this field changes FUTURE links only.
|
|
15255
|
+
*/
|
|
15256
|
+
refreshTokenTtlSec: union([number().int().positive(), literal("never")]).optional()
|
|
15117
15257
|
});
|
|
15118
15258
|
method(_void(), OauthIntegrationDescriptorSchema);
|
|
15119
15259
|
/**
|
|
@@ -18250,11 +18390,29 @@ var SsoBridgeClaimsSchema = object({
|
|
|
18250
18390
|
codeChallenge: string().optional(),
|
|
18251
18391
|
/** OAuth session registry id — set on `oauth-access`/`oauth-refresh`
|
|
18252
18392
|
* tokens so the verify path can check the session is not revoked. */
|
|
18253
|
-
sessionId: string().optional()
|
|
18393
|
+
sessionId: string().optional(),
|
|
18394
|
+
/**
|
|
18395
|
+
* The refresh lifetime this LINK was created with, in seconds, or `'never'`.
|
|
18396
|
+
* Baked into the code at `/authorize` from the integration's descriptor and
|
|
18397
|
+
* carried forward so `oauthRefresh` re-mints with the same lifetime. It rides
|
|
18398
|
+
* on the token rather than being re-read from the descriptor on purpose:
|
|
18399
|
+
* editing a descriptor must not retroactively extend or shorten a link the
|
|
18400
|
+
* operator already consented to.
|
|
18401
|
+
*/
|
|
18402
|
+
refreshTtl: union([number().int().positive(), literal("never")]).optional()
|
|
18254
18403
|
});
|
|
18255
18404
|
method(object({
|
|
18256
18405
|
claims: SsoBridgeClaimsSchema,
|
|
18257
|
-
|
|
18406
|
+
/**
|
|
18407
|
+
* Seconds, or `'never'` for a token minted with NO `exp` claim.
|
|
18408
|
+
*
|
|
18409
|
+
* `'never'` is a literal rather than `undefined`/`0` because omitting
|
|
18410
|
+
* this field already means "the 5-minute SSO hand-off default", and
|
|
18411
|
+
* `jwt.sign` THROWS on `{ expiresIn: undefined }` — a "no expiry" that
|
|
18412
|
+
* went through the numeric path would fail at mint time and break
|
|
18413
|
+
* linking rather than produce an eternal token.
|
|
18414
|
+
*/
|
|
18415
|
+
ttlSec: union([number().int().positive(), literal("never")]).optional()
|
|
18258
18416
|
}), object({ token: string() })), method(object({ token: string() }), SsoBridgeClaimsSchema.nullable());
|
|
18259
18417
|
var ProviderListEntrySchema = discriminatedUnion("shouldSaveDiskSpace", [object({
|
|
18260
18418
|
providerId: string().min(1),
|
|
@@ -18457,6 +18615,22 @@ var TerminalProfileInfoSchema = object({
|
|
|
18457
18615
|
label: string(),
|
|
18458
18616
|
description: string().optional()
|
|
18459
18617
|
});
|
|
18618
|
+
var TerminalOutputEventSchema = discriminatedUnion("kind", [object({
|
|
18619
|
+
seq: number().int().positive(),
|
|
18620
|
+
kind: literal("data"),
|
|
18621
|
+
data: string()
|
|
18622
|
+
}), object({
|
|
18623
|
+
seq: number().int().positive(),
|
|
18624
|
+
kind: literal("exit"),
|
|
18625
|
+
exitCode: number().int(),
|
|
18626
|
+
signal: number().int().optional()
|
|
18627
|
+
})]);
|
|
18628
|
+
var TerminalOutputBatchSchema = object({
|
|
18629
|
+
cursor: number().int().nonnegative(),
|
|
18630
|
+
reset: boolean(),
|
|
18631
|
+
snapshot: string().optional(),
|
|
18632
|
+
events: array(TerminalOutputEventSchema).readonly()
|
|
18633
|
+
});
|
|
18460
18634
|
method(_void(), array(TerminalProfileInfoSchema).readonly(), { auth: "admin" }), method(_void(), array(TerminalSessionInfoSchema).readonly(), { auth: "admin" }), method(object({
|
|
18461
18635
|
profileId: string(),
|
|
18462
18636
|
cols: number().int().positive(),
|
|
@@ -18471,6 +18645,20 @@ method(_void(), array(TerminalProfileInfoSchema).readonly(), { auth: "admin" }),
|
|
|
18471
18645
|
}), _void(), {
|
|
18472
18646
|
kind: "mutation",
|
|
18473
18647
|
auth: "admin"
|
|
18648
|
+
}), method(object({
|
|
18649
|
+
sessionId: string(),
|
|
18650
|
+
afterSeq: number().int().nonnegative(),
|
|
18651
|
+
waitMs: number().int().min(0).max(2e3).default(0)
|
|
18652
|
+
}), TerminalOutputBatchSchema, {
|
|
18653
|
+
kind: "mutation",
|
|
18654
|
+
auth: "admin",
|
|
18655
|
+
timeoutMs: 15e3
|
|
18656
|
+
}), method(object({
|
|
18657
|
+
sessionId: string(),
|
|
18658
|
+
data: string().max(64 * 1024)
|
|
18659
|
+
}), _void(), {
|
|
18660
|
+
kind: "mutation",
|
|
18661
|
+
auth: "admin"
|
|
18474
18662
|
}), method(object({ sessionId: string() }), _void(), {
|
|
18475
18663
|
kind: "mutation",
|
|
18476
18664
|
auth: "admin"
|
|
@@ -25454,7 +25642,12 @@ method(_void(), array(UserSummarySchema), { auth: "admin" }), method(CreateUserI
|
|
|
25454
25642
|
hubUrl: string(),
|
|
25455
25643
|
/** PKCE (RFC 7636) S256 challenge. Baked into the signed code; a code
|
|
25456
25644
|
* that carries one can ONLY be exchanged with the matching verifier. */
|
|
25457
|
-
codeChallenge: string().optional()
|
|
25645
|
+
codeChallenge: string().optional(),
|
|
25646
|
+
/** The integration's declared refresh lifetime — seconds, or `'never'`.
|
|
25647
|
+
* From `OauthIntegrationDescriptor.refreshTokenTtlSec`. Baked into the
|
|
25648
|
+
* code so the link carries its own lifetime; omit for the 30-day
|
|
25649
|
+
* default. */
|
|
25650
|
+
refreshTtlSec: union([number().int().positive(), literal("never")]).optional()
|
|
25458
25651
|
}), object({ code: string() }), {
|
|
25459
25652
|
kind: "mutation",
|
|
25460
25653
|
access: "create"
|
|
@@ -32072,12 +32265,24 @@ Object.freeze({
|
|
|
32072
32265
|
addonId: null,
|
|
32073
32266
|
access: "create"
|
|
32074
32267
|
},
|
|
32268
|
+
"terminalSession.pullOutput": {
|
|
32269
|
+
capName: "terminal-session",
|
|
32270
|
+
capScope: "system",
|
|
32271
|
+
addonId: null,
|
|
32272
|
+
access: "create"
|
|
32273
|
+
},
|
|
32075
32274
|
"terminalSession.resize": {
|
|
32076
32275
|
capName: "terminal-session",
|
|
32077
32276
|
capScope: "system",
|
|
32078
32277
|
addonId: null,
|
|
32079
32278
|
access: "create"
|
|
32080
32279
|
},
|
|
32280
|
+
"terminalSession.writeInput": {
|
|
32281
|
+
capName: "terminal-session",
|
|
32282
|
+
capScope: "system",
|
|
32283
|
+
addonId: null,
|
|
32284
|
+
access: "create"
|
|
32285
|
+
},
|
|
32081
32286
|
"toast.onToast": {
|
|
32082
32287
|
capName: "toast",
|
|
32083
32288
|
capScope: "system",
|
package/dist/addon.mjs
CHANGED
|
@@ -36,7 +36,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
36
36
|
var __toCommonJS = (mod) => __hasOwnProp.call(mod, "module.exports") ? mod["module.exports"] : __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
37
37
|
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
38
38
|
//#endregion
|
|
39
|
-
//#region ../types/dist/event-category-
|
|
39
|
+
//#region ../types/dist/event-category-Cv9dO26A.mjs
|
|
40
40
|
var EventCategory = /* @__PURE__ */ function(EventCategory) {
|
|
41
41
|
EventCategory["SystemBoot"] = "system.boot";
|
|
42
42
|
EventCategory["SystemAddonsReady"] = "system.addons-ready";
|
|
@@ -52,6 +52,13 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
|
|
|
52
52
|
*/
|
|
53
53
|
EventCategory["SystemRestartCompleted"] = "system.restart-completed";
|
|
54
54
|
/**
|
|
55
|
+
* A newer addon or server-root package version was found by the
|
|
56
|
+
* authoritative registry check. Emitted once per
|
|
57
|
+
* `(target, packageName, currentVersion, latestVersion)` transition; repeated
|
|
58
|
+
* polling of the same result is deduplicated by the checker.
|
|
59
|
+
*/
|
|
60
|
+
EventCategory["UpdateAvailable"] = "update.available";
|
|
61
|
+
/**
|
|
55
62
|
* Readiness transition for a capability provider. Every producer emits
|
|
56
63
|
* this event on `onInitialize` completion, `onDestroy`, and
|
|
57
64
|
* `$node.reconnect`; every consumer that needs to gate on a cross-process
|
|
@@ -14092,8 +14099,35 @@ var NcDeliverySchema = _enum([
|
|
|
14092
14099
|
"immediate",
|
|
14093
14100
|
"track-end",
|
|
14094
14101
|
"device-event",
|
|
14095
|
-
"package-event"
|
|
14102
|
+
"package-event",
|
|
14103
|
+
"system-event"
|
|
14104
|
+
]);
|
|
14105
|
+
/**
|
|
14106
|
+
* Stable Notification Center vocabulary over infrastructure/liveness events.
|
|
14107
|
+
* Bus categories are normalized into these intent-level kinds so rules do not
|
|
14108
|
+
* depend on a provider's raw event name or payload shape.
|
|
14109
|
+
*/
|
|
14110
|
+
var NcSystemEventKindSchema = _enum([
|
|
14111
|
+
"camera-online",
|
|
14112
|
+
"camera-offline",
|
|
14113
|
+
"stream-online",
|
|
14114
|
+
"stream-offline",
|
|
14115
|
+
"node-online",
|
|
14116
|
+
"node-offline",
|
|
14117
|
+
"addon-update-available",
|
|
14118
|
+
"server-update-available"
|
|
14096
14119
|
]);
|
|
14120
|
+
/**
|
|
14121
|
+
* One coherent system-event condition. `kinds` is the required opt-in safety
|
|
14122
|
+
* gate; the remaining lists are optional narrowing filters relevant to the
|
|
14123
|
+
* selected kinds.
|
|
14124
|
+
*/
|
|
14125
|
+
var NcSystemEventConditionSchema = object({
|
|
14126
|
+
kinds: array(NcSystemEventKindSchema).min(1),
|
|
14127
|
+
deviceIds: array(number().int()).min(1).optional(),
|
|
14128
|
+
nodeIds: array(string().min(1)).min(1).optional(),
|
|
14129
|
+
packageNames: array(string().min(1)).min(1).optional()
|
|
14130
|
+
});
|
|
14097
14131
|
/** Weekly schedule — OR of windows; absence on the rule = always active. */
|
|
14098
14132
|
var NcScheduleSchema = object({
|
|
14099
14133
|
windows: array(object({
|
|
@@ -14398,6 +14432,8 @@ var NcConditionsSchema = object({
|
|
|
14398
14432
|
"picked-up",
|
|
14399
14433
|
"both"
|
|
14400
14434
|
]).optional(),
|
|
14435
|
+
/** Infrastructure/liveness/update event matcher (`system-event` delivery). */
|
|
14436
|
+
systemEvent: NcSystemEventConditionSchema.optional(),
|
|
14401
14437
|
/**
|
|
14402
14438
|
* PERSONAL-RULE custom zones (viewer-drawn). Inline normalized polygons
|
|
14403
14439
|
* (MaskShape vocabulary). A record passes when its bbox overlaps ANY
|
|
@@ -14617,7 +14653,8 @@ var NcTestResultSchema = object({
|
|
|
14617
14653
|
"object-event",
|
|
14618
14654
|
"track",
|
|
14619
14655
|
"device-event",
|
|
14620
|
-
"package-event"
|
|
14656
|
+
"package-event",
|
|
14657
|
+
"system-event"
|
|
14621
14658
|
]),
|
|
14622
14659
|
deviceId: number(),
|
|
14623
14660
|
timestamp: number(),
|
|
@@ -14639,7 +14676,8 @@ var NcConditionDescriptorSchema = object({
|
|
|
14639
14676
|
"schedule",
|
|
14640
14677
|
"device",
|
|
14641
14678
|
"package",
|
|
14642
|
-
"occupancy"
|
|
14679
|
+
"occupancy",
|
|
14680
|
+
"system"
|
|
14643
14681
|
]),
|
|
14644
14682
|
label: string(),
|
|
14645
14683
|
/** Editor widget the UI renders — never hardcode per-condition forms. */
|
|
@@ -14657,7 +14695,8 @@ var NcConditionDescriptorSchema = object({
|
|
|
14657
14695
|
"crossingSelect",
|
|
14658
14696
|
"polygonDraw",
|
|
14659
14697
|
"occupancy",
|
|
14660
|
-
"deviceState"
|
|
14698
|
+
"deviceState",
|
|
14699
|
+
"systemEvent"
|
|
14661
14700
|
]),
|
|
14662
14701
|
operator: _enum([
|
|
14663
14702
|
"in",
|
|
@@ -14716,7 +14755,8 @@ var NcHistoryRecordKindSchema = _enum([
|
|
|
14716
14755
|
"object-event",
|
|
14717
14756
|
"track-end",
|
|
14718
14757
|
"device-event",
|
|
14719
|
-
"package-event"
|
|
14758
|
+
"package-event",
|
|
14759
|
+
"system-event"
|
|
14720
14760
|
]);
|
|
14721
14761
|
/** Subject summary frozen on the row at fire time (survives rule/record edits). */
|
|
14722
14762
|
var NcHistorySubjectSchema = object({
|
|
@@ -14724,7 +14764,14 @@ var NcHistorySubjectSchema = object({
|
|
|
14724
14764
|
label: string().optional(),
|
|
14725
14765
|
confidence: number().optional(),
|
|
14726
14766
|
zones: array(string()),
|
|
14727
|
-
timestamp: number()
|
|
14767
|
+
timestamp: number(),
|
|
14768
|
+
systemEvent: object({
|
|
14769
|
+
kind: NcSystemEventKindSchema,
|
|
14770
|
+
subject: string(),
|
|
14771
|
+
title: string(),
|
|
14772
|
+
body: string(),
|
|
14773
|
+
data: record(string(), unknown())
|
|
14774
|
+
}).optional()
|
|
14728
14775
|
});
|
|
14729
14776
|
/**
|
|
14730
14777
|
* One delivery-history row. This is a read-only VIEW over the durable
|
|
@@ -15084,6 +15131,76 @@ object({
|
|
|
15084
15131
|
* Each provider returns a static descriptor; the core enumerates them
|
|
15085
15132
|
* to validate the `integration=` query param and resolve the consent
|
|
15086
15133
|
* label + the scopes baked into the issued token.
|
|
15134
|
+
*
|
|
15135
|
+
* ## Declaring one
|
|
15136
|
+
*
|
|
15137
|
+
* An OAuth client is integration-specific knowledge — who the client is, what
|
|
15138
|
+
* it may ask for, where it may be sent — so it is declared by the ADDON that
|
|
15139
|
+
* owns the integration, never by the kernel and never as a branch inside
|
|
15140
|
+
* `oauth2-routes.ts` ([D101](../../../../docs/decisions/adr-0101.md)). Three
|
|
15141
|
+
* steps, no others:
|
|
15142
|
+
*
|
|
15143
|
+
* 1. Add `{ "name": "oauth-integration" }` to the addon's `camstack.addons[]`
|
|
15144
|
+
* manifest entry. This is also what tells the hub, at addon-LOAD time, that
|
|
15145
|
+
* a descriptor is owed — see "the boot window" below.
|
|
15146
|
+
* 2. Return a provider from `onInitialize()`:
|
|
15147
|
+
*
|
|
15148
|
+
* ```ts
|
|
15149
|
+
* const provider: IOauthIntegrationProvider = {
|
|
15150
|
+
* getDescriptor: async () => ({
|
|
15151
|
+
* integrationId: 'my-thing', // the `integration=` query param
|
|
15152
|
+
* displayName: 'My Thing',
|
|
15153
|
+
* requestedScopes: [ … ], // see below
|
|
15154
|
+
* allowedRedirectPrefixes: ['https://callback.example/'],
|
|
15155
|
+
* }),
|
|
15156
|
+
* }
|
|
15157
|
+
* return [{ capability: oauthIntegrationCapability, provider }]
|
|
15158
|
+
* ```
|
|
15159
|
+
*
|
|
15160
|
+
* The descriptor must be **static** — it is read on the authorize path, so
|
|
15161
|
+
* never put an await on network or disk behind it, and never register it
|
|
15162
|
+
* behind one either (a provider is registered only once `onInitialize`
|
|
15163
|
+
* RETURNS, so anything awaited before the return delays linking).
|
|
15164
|
+
* 3. Nothing else. There is no allow-list to join, no id to register with the
|
|
15165
|
+
* core, and no per-integration branch anywhere: `/api/oauth2/authorize` and
|
|
15166
|
+
* `/api/oauth2/integrations` are built from this collection alone.
|
|
15167
|
+
*
|
|
15168
|
+
* **Scopes. `requestedScopes` has exactly ONE meaning: what the integration
|
|
15169
|
+
* NEEDS to function.** Not a blast radius, not a conservative
|
|
15170
|
+
* under-declaration, not a description of some other path the addon happens to
|
|
15171
|
+
* have. Derive it from what the client actually calls **with this token** —
|
|
15172
|
+
* every tRPC path against `METHOD_ACCESS_MAP`, plus an `addon:` grant for every
|
|
15173
|
+
* addon HTTP route it posts to — and write the call that justifies each entry
|
|
15174
|
+
* next to it. Two integrations once used this field to mean two different
|
|
15175
|
+
* things; the operator ruled there is one meaning, and any third integration
|
|
15176
|
+
* inherits it (2026-08-09).
|
|
15177
|
+
*
|
|
15178
|
+
* This is not documentation, it is the ENFORCEMENT INPUT. Since
|
|
15179
|
+
* [D103](../../../../docs/decisions/adr-0103.md) the `/addon/:addonId/*` gate
|
|
15180
|
+
* checks an integration token's grant before letting it reach an
|
|
15181
|
+
* `access: 'authenticated'` route, so an **under-declaration is an integration
|
|
15182
|
+
* that stops working** — a missing `addon:` entry means `403 Token scope
|
|
15183
|
+
* mismatch` on every control the client tries to actuate. Widen the descriptor
|
|
15184
|
+
* honestly rather than weakening a check to make a route pass.
|
|
15185
|
+
*
|
|
15186
|
+
* Prefer a narrow `capability:` scope to a `category:` one unless the client
|
|
15187
|
+
* genuinely needs a whole family; a category scope grants every future member
|
|
15188
|
+
* of that category too. `category:system [create]` has been rejected once and
|
|
15189
|
+
* should stay rejected: it hands `addons.installPackage` to an integration.
|
|
15190
|
+
*
|
|
15191
|
+
* Calls the ADDON itself makes over `ctx.api` run as the addon and are not
|
|
15192
|
+
* scope-checked, so they are not what this field describes — but reaching the
|
|
15193
|
+
* addon's route in the first place IS, and that is the entry to declare.
|
|
15194
|
+
*
|
|
15195
|
+
* **The boot window.** An addon registers its provider after its runner forks
|
|
15196
|
+
* and initialises, so between hub start and that moment this collection is
|
|
15197
|
+
* incomplete and an `integrationId` can be legitimately absent. The core does
|
|
15198
|
+
* not wait, poll or cache around this ([D3](../../../../docs/decisions/adr-0003.md)):
|
|
15199
|
+
* it compares the manifest declarers against the registered providers and
|
|
15200
|
+
* answers `503 temporarily_unavailable` (with `Retry-After` and the pending
|
|
15201
|
+
* addon ids) instead of `400 unknown integration`, and reports
|
|
15202
|
+
* `complete: false` on `GET /api/oauth2/integrations`. A client should retry
|
|
15203
|
+
* while the list is incomplete rather than conclude the hub cannot do OAuth.
|
|
15087
15204
|
*/
|
|
15088
15205
|
var OauthIntegrationDescriptorSchema = object({
|
|
15089
15206
|
/** Stable id used as the `integration=` query param, e.g. 'export-alexa'. */
|
|
@@ -15114,7 +15231,30 @@ var OauthIntegrationDescriptorSchema = object({
|
|
|
15114
15231
|
* present, /api/oauth2/authorize bakes THIS into the code instead of the
|
|
15115
15232
|
* hub-global `publicHubUrl()`, so a forked exporter addon (which can't set
|
|
15116
15233
|
* the hub's env) drives the claim that its cloud Lambda routes back on. */
|
|
15117
|
-
hubUrl: string().optional()
|
|
15234
|
+
hubUrl: string().optional(),
|
|
15235
|
+
/**
|
|
15236
|
+
* How long a REFRESH token issued for this integration lives — seconds, or
|
|
15237
|
+
* `'never'` for a token minted with no `exp` claim at all. Omit to keep the
|
|
15238
|
+
* 30-day default, which is what every link used before this field existed.
|
|
15239
|
+
*
|
|
15240
|
+
* Declared here for the same reason `requestedScopes` is: the integration
|
|
15241
|
+
* knows what it needs. Amazon's account linking and a Home Assistant config
|
|
15242
|
+
* entry are both meant to survive indefinitely, and re-linking is a manual
|
|
15243
|
+
* user action, so a 30-day expiry silently unlinks a working integration.
|
|
15244
|
+
*
|
|
15245
|
+
* **The security posture, stated so it is owned deliberately.** A refresh
|
|
15246
|
+
* token that never expires is permanent access if it leaks. What bounds it is
|
|
15247
|
+
* revocation, not time: `oauthRefresh` re-reads the session on every use and
|
|
15248
|
+
* returns `null` once `revokedAt` is set, as does `oauthVerifyAccessToken`.
|
|
15249
|
+
* The one gap is the ACCESS token — it is a plain signed JWT that nothing
|
|
15250
|
+
* re-checks against the session on the `/trpc` and `/addon/*` paths, so
|
|
15251
|
+
* revoking a link takes effect there only after its remaining hour. That hour
|
|
15252
|
+
* is why the access TTL is not configurable.
|
|
15253
|
+
*
|
|
15254
|
+
* The value is baked into the authorization code at `/authorize` and travels
|
|
15255
|
+
* on the tokens, so editing this field changes FUTURE links only.
|
|
15256
|
+
*/
|
|
15257
|
+
refreshTokenTtlSec: union([number().int().positive(), literal("never")]).optional()
|
|
15118
15258
|
});
|
|
15119
15259
|
method(_void(), OauthIntegrationDescriptorSchema);
|
|
15120
15260
|
/**
|
|
@@ -18251,11 +18391,29 @@ var SsoBridgeClaimsSchema = object({
|
|
|
18251
18391
|
codeChallenge: string().optional(),
|
|
18252
18392
|
/** OAuth session registry id — set on `oauth-access`/`oauth-refresh`
|
|
18253
18393
|
* tokens so the verify path can check the session is not revoked. */
|
|
18254
|
-
sessionId: string().optional()
|
|
18394
|
+
sessionId: string().optional(),
|
|
18395
|
+
/**
|
|
18396
|
+
* The refresh lifetime this LINK was created with, in seconds, or `'never'`.
|
|
18397
|
+
* Baked into the code at `/authorize` from the integration's descriptor and
|
|
18398
|
+
* carried forward so `oauthRefresh` re-mints with the same lifetime. It rides
|
|
18399
|
+
* on the token rather than being re-read from the descriptor on purpose:
|
|
18400
|
+
* editing a descriptor must not retroactively extend or shorten a link the
|
|
18401
|
+
* operator already consented to.
|
|
18402
|
+
*/
|
|
18403
|
+
refreshTtl: union([number().int().positive(), literal("never")]).optional()
|
|
18255
18404
|
});
|
|
18256
18405
|
method(object({
|
|
18257
18406
|
claims: SsoBridgeClaimsSchema,
|
|
18258
|
-
|
|
18407
|
+
/**
|
|
18408
|
+
* Seconds, or `'never'` for a token minted with NO `exp` claim.
|
|
18409
|
+
*
|
|
18410
|
+
* `'never'` is a literal rather than `undefined`/`0` because omitting
|
|
18411
|
+
* this field already means "the 5-minute SSO hand-off default", and
|
|
18412
|
+
* `jwt.sign` THROWS on `{ expiresIn: undefined }` — a "no expiry" that
|
|
18413
|
+
* went through the numeric path would fail at mint time and break
|
|
18414
|
+
* linking rather than produce an eternal token.
|
|
18415
|
+
*/
|
|
18416
|
+
ttlSec: union([number().int().positive(), literal("never")]).optional()
|
|
18259
18417
|
}), object({ token: string() })), method(object({ token: string() }), SsoBridgeClaimsSchema.nullable());
|
|
18260
18418
|
var ProviderListEntrySchema = discriminatedUnion("shouldSaveDiskSpace", [object({
|
|
18261
18419
|
providerId: string().min(1),
|
|
@@ -18458,6 +18616,22 @@ var TerminalProfileInfoSchema = object({
|
|
|
18458
18616
|
label: string(),
|
|
18459
18617
|
description: string().optional()
|
|
18460
18618
|
});
|
|
18619
|
+
var TerminalOutputEventSchema = discriminatedUnion("kind", [object({
|
|
18620
|
+
seq: number().int().positive(),
|
|
18621
|
+
kind: literal("data"),
|
|
18622
|
+
data: string()
|
|
18623
|
+
}), object({
|
|
18624
|
+
seq: number().int().positive(),
|
|
18625
|
+
kind: literal("exit"),
|
|
18626
|
+
exitCode: number().int(),
|
|
18627
|
+
signal: number().int().optional()
|
|
18628
|
+
})]);
|
|
18629
|
+
var TerminalOutputBatchSchema = object({
|
|
18630
|
+
cursor: number().int().nonnegative(),
|
|
18631
|
+
reset: boolean(),
|
|
18632
|
+
snapshot: string().optional(),
|
|
18633
|
+
events: array(TerminalOutputEventSchema).readonly()
|
|
18634
|
+
});
|
|
18461
18635
|
method(_void(), array(TerminalProfileInfoSchema).readonly(), { auth: "admin" }), method(_void(), array(TerminalSessionInfoSchema).readonly(), { auth: "admin" }), method(object({
|
|
18462
18636
|
profileId: string(),
|
|
18463
18637
|
cols: number().int().positive(),
|
|
@@ -18472,6 +18646,20 @@ method(_void(), array(TerminalProfileInfoSchema).readonly(), { auth: "admin" }),
|
|
|
18472
18646
|
}), _void(), {
|
|
18473
18647
|
kind: "mutation",
|
|
18474
18648
|
auth: "admin"
|
|
18649
|
+
}), method(object({
|
|
18650
|
+
sessionId: string(),
|
|
18651
|
+
afterSeq: number().int().nonnegative(),
|
|
18652
|
+
waitMs: number().int().min(0).max(2e3).default(0)
|
|
18653
|
+
}), TerminalOutputBatchSchema, {
|
|
18654
|
+
kind: "mutation",
|
|
18655
|
+
auth: "admin",
|
|
18656
|
+
timeoutMs: 15e3
|
|
18657
|
+
}), method(object({
|
|
18658
|
+
sessionId: string(),
|
|
18659
|
+
data: string().max(64 * 1024)
|
|
18660
|
+
}), _void(), {
|
|
18661
|
+
kind: "mutation",
|
|
18662
|
+
auth: "admin"
|
|
18475
18663
|
}), method(object({ sessionId: string() }), _void(), {
|
|
18476
18664
|
kind: "mutation",
|
|
18477
18665
|
auth: "admin"
|
|
@@ -25455,7 +25643,12 @@ method(_void(), array(UserSummarySchema), { auth: "admin" }), method(CreateUserI
|
|
|
25455
25643
|
hubUrl: string(),
|
|
25456
25644
|
/** PKCE (RFC 7636) S256 challenge. Baked into the signed code; a code
|
|
25457
25645
|
* that carries one can ONLY be exchanged with the matching verifier. */
|
|
25458
|
-
codeChallenge: string().optional()
|
|
25646
|
+
codeChallenge: string().optional(),
|
|
25647
|
+
/** The integration's declared refresh lifetime — seconds, or `'never'`.
|
|
25648
|
+
* From `OauthIntegrationDescriptor.refreshTokenTtlSec`. Baked into the
|
|
25649
|
+
* code so the link carries its own lifetime; omit for the 30-day
|
|
25650
|
+
* default. */
|
|
25651
|
+
refreshTtlSec: union([number().int().positive(), literal("never")]).optional()
|
|
25459
25652
|
}), object({ code: string() }), {
|
|
25460
25653
|
kind: "mutation",
|
|
25461
25654
|
access: "create"
|
|
@@ -32073,12 +32266,24 @@ Object.freeze({
|
|
|
32073
32266
|
addonId: null,
|
|
32074
32267
|
access: "create"
|
|
32075
32268
|
},
|
|
32269
|
+
"terminalSession.pullOutput": {
|
|
32270
|
+
capName: "terminal-session",
|
|
32271
|
+
capScope: "system",
|
|
32272
|
+
addonId: null,
|
|
32273
|
+
access: "create"
|
|
32274
|
+
},
|
|
32076
32275
|
"terminalSession.resize": {
|
|
32077
32276
|
capName: "terminal-session",
|
|
32078
32277
|
capScope: "system",
|
|
32079
32278
|
addonId: null,
|
|
32080
32279
|
access: "create"
|
|
32081
32280
|
},
|
|
32281
|
+
"terminalSession.writeInput": {
|
|
32282
|
+
capName: "terminal-session",
|
|
32283
|
+
capScope: "system",
|
|
32284
|
+
addonId: null,
|
|
32285
|
+
access: "create"
|
|
32286
|
+
},
|
|
32082
32287
|
"toast.onToast": {
|
|
32083
32288
|
capName: "toast",
|
|
32084
32289
|
capScope: "system",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@camstack/addon-provider-dreo",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.12",
|
|
4
4
|
"description": "Dreo smart-device (fan / air-circulator / purifier / heater / humidifier) device-provider addon for CamStack — wraps the @apocaliss92/nodedreo Dreo cloud client (REST + WebSocket)",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"camstack",
|