@camstack/addon-provider-dreame 0.2.10 → 0.2.11

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.
Files changed (3) hide show
  1. package/dist/addon.js +174 -11
  2. package/dist/addon.mjs +174 -11
  3. package/package.json +1 -1
package/dist/addon.js CHANGED
@@ -50504,7 +50504,7 @@ objectType({
50504
50504
  })
50505
50505
  });
50506
50506
  //#endregion
50507
- //#region ../types/dist/event-category-41fKf-q9.mjs
50507
+ //#region ../types/dist/event-category-Cv9dO26A.mjs
50508
50508
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
50509
50509
  EventCategory["SystemBoot"] = "system.boot";
50510
50510
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -50520,6 +50520,13 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
50520
50520
  */
50521
50521
  EventCategory["SystemRestartCompleted"] = "system.restart-completed";
50522
50522
  /**
50523
+ * A newer addon or server-root package version was found by the
50524
+ * authoritative registry check. Emitted once per
50525
+ * `(target, packageName, currentVersion, latestVersion)` transition; repeated
50526
+ * polling of the same result is deduplicated by the checker.
50527
+ */
50528
+ EventCategory["UpdateAvailable"] = "update.available";
50529
+ /**
50523
50530
  * Readiness transition for a capability provider. Every producer emits
50524
50531
  * this event on `onInitialize` completion, `onDestroy`, and
50525
50532
  * `$node.reconnect`; every consumer that needs to gate on a cross-process
@@ -64574,8 +64581,35 @@ var NcDeliverySchema = _enum([
64574
64581
  "immediate",
64575
64582
  "track-end",
64576
64583
  "device-event",
64577
- "package-event"
64584
+ "package-event",
64585
+ "system-event"
64586
+ ]);
64587
+ /**
64588
+ * Stable Notification Center vocabulary over infrastructure/liveness events.
64589
+ * Bus categories are normalized into these intent-level kinds so rules do not
64590
+ * depend on a provider's raw event name or payload shape.
64591
+ */
64592
+ var NcSystemEventKindSchema = _enum([
64593
+ "camera-online",
64594
+ "camera-offline",
64595
+ "stream-online",
64596
+ "stream-offline",
64597
+ "node-online",
64598
+ "node-offline",
64599
+ "addon-update-available",
64600
+ "server-update-available"
64578
64601
  ]);
64602
+ /**
64603
+ * One coherent system-event condition. `kinds` is the required opt-in safety
64604
+ * gate; the remaining lists are optional narrowing filters relevant to the
64605
+ * selected kinds.
64606
+ */
64607
+ var NcSystemEventConditionSchema = object({
64608
+ kinds: array(NcSystemEventKindSchema).min(1),
64609
+ deviceIds: array(number().int()).min(1).optional(),
64610
+ nodeIds: array(string().min(1)).min(1).optional(),
64611
+ packageNames: array(string().min(1)).min(1).optional()
64612
+ });
64579
64613
  /** Weekly schedule — OR of windows; absence on the rule = always active. */
64580
64614
  var NcScheduleSchema = object({
64581
64615
  windows: array(object({
@@ -64880,6 +64914,8 @@ var NcConditionsSchema = object({
64880
64914
  "picked-up",
64881
64915
  "both"
64882
64916
  ]).optional(),
64917
+ /** Infrastructure/liveness/update event matcher (`system-event` delivery). */
64918
+ systemEvent: NcSystemEventConditionSchema.optional(),
64883
64919
  /**
64884
64920
  * PERSONAL-RULE custom zones (viewer-drawn). Inline normalized polygons
64885
64921
  * (MaskShape vocabulary). A record passes when its bbox overlaps ANY
@@ -65099,7 +65135,8 @@ var NcTestResultSchema = object({
65099
65135
  "object-event",
65100
65136
  "track",
65101
65137
  "device-event",
65102
- "package-event"
65138
+ "package-event",
65139
+ "system-event"
65103
65140
  ]),
65104
65141
  deviceId: number(),
65105
65142
  timestamp: number(),
@@ -65121,7 +65158,8 @@ var NcConditionDescriptorSchema = object({
65121
65158
  "schedule",
65122
65159
  "device",
65123
65160
  "package",
65124
- "occupancy"
65161
+ "occupancy",
65162
+ "system"
65125
65163
  ]),
65126
65164
  label: string(),
65127
65165
  /** Editor widget the UI renders — never hardcode per-condition forms. */
@@ -65139,7 +65177,8 @@ var NcConditionDescriptorSchema = object({
65139
65177
  "crossingSelect",
65140
65178
  "polygonDraw",
65141
65179
  "occupancy",
65142
- "deviceState"
65180
+ "deviceState",
65181
+ "systemEvent"
65143
65182
  ]),
65144
65183
  operator: _enum([
65145
65184
  "in",
@@ -65198,7 +65237,8 @@ var NcHistoryRecordKindSchema = _enum([
65198
65237
  "object-event",
65199
65238
  "track-end",
65200
65239
  "device-event",
65201
- "package-event"
65240
+ "package-event",
65241
+ "system-event"
65202
65242
  ]);
65203
65243
  /** Subject summary frozen on the row at fire time (survives rule/record edits). */
65204
65244
  var NcHistorySubjectSchema = object({
@@ -65206,7 +65246,14 @@ var NcHistorySubjectSchema = object({
65206
65246
  label: string().optional(),
65207
65247
  confidence: number().optional(),
65208
65248
  zones: array(string()),
65209
- timestamp: number()
65249
+ timestamp: number(),
65250
+ systemEvent: object({
65251
+ kind: NcSystemEventKindSchema,
65252
+ subject: string(),
65253
+ title: string(),
65254
+ body: string(),
65255
+ data: record(string(), unknown())
65256
+ }).optional()
65210
65257
  });
65211
65258
  /**
65212
65259
  * One delivery-history row. This is a read-only VIEW over the durable
@@ -65566,6 +65613,76 @@ object({
65566
65613
  * Each provider returns a static descriptor; the core enumerates them
65567
65614
  * to validate the `integration=` query param and resolve the consent
65568
65615
  * label + the scopes baked into the issued token.
65616
+ *
65617
+ * ## Declaring one
65618
+ *
65619
+ * An OAuth client is integration-specific knowledge — who the client is, what
65620
+ * it may ask for, where it may be sent — so it is declared by the ADDON that
65621
+ * owns the integration, never by the kernel and never as a branch inside
65622
+ * `oauth2-routes.ts` ([D101](../../../../docs/decisions/adr-0101.md)). Three
65623
+ * steps, no others:
65624
+ *
65625
+ * 1. Add `{ "name": "oauth-integration" }` to the addon's `camstack.addons[]`
65626
+ * manifest entry. This is also what tells the hub, at addon-LOAD time, that
65627
+ * a descriptor is owed — see "the boot window" below.
65628
+ * 2. Return a provider from `onInitialize()`:
65629
+ *
65630
+ * ```ts
65631
+ * const provider: IOauthIntegrationProvider = {
65632
+ * getDescriptor: async () => ({
65633
+ * integrationId: 'my-thing', // the `integration=` query param
65634
+ * displayName: 'My Thing',
65635
+ * requestedScopes: [ … ], // see below
65636
+ * allowedRedirectPrefixes: ['https://callback.example/'],
65637
+ * }),
65638
+ * }
65639
+ * return [{ capability: oauthIntegrationCapability, provider }]
65640
+ * ```
65641
+ *
65642
+ * The descriptor must be **static** — it is read on the authorize path, so
65643
+ * never put an await on network or disk behind it, and never register it
65644
+ * behind one either (a provider is registered only once `onInitialize`
65645
+ * RETURNS, so anything awaited before the return delays linking).
65646
+ * 3. Nothing else. There is no allow-list to join, no id to register with the
65647
+ * core, and no per-integration branch anywhere: `/api/oauth2/authorize` and
65648
+ * `/api/oauth2/integrations` are built from this collection alone.
65649
+ *
65650
+ * **Scopes. `requestedScopes` has exactly ONE meaning: what the integration
65651
+ * NEEDS to function.** Not a blast radius, not a conservative
65652
+ * under-declaration, not a description of some other path the addon happens to
65653
+ * have. Derive it from what the client actually calls **with this token** —
65654
+ * every tRPC path against `METHOD_ACCESS_MAP`, plus an `addon:` grant for every
65655
+ * addon HTTP route it posts to — and write the call that justifies each entry
65656
+ * next to it. Two integrations once used this field to mean two different
65657
+ * things; the operator ruled there is one meaning, and any third integration
65658
+ * inherits it (2026-08-09).
65659
+ *
65660
+ * This is not documentation, it is the ENFORCEMENT INPUT. Since
65661
+ * [D103](../../../../docs/decisions/adr-0103.md) the `/addon/:addonId/*` gate
65662
+ * checks an integration token's grant before letting it reach an
65663
+ * `access: 'authenticated'` route, so an **under-declaration is an integration
65664
+ * that stops working** — a missing `addon:` entry means `403 Token scope
65665
+ * mismatch` on every control the client tries to actuate. Widen the descriptor
65666
+ * honestly rather than weakening a check to make a route pass.
65667
+ *
65668
+ * Prefer a narrow `capability:` scope to a `category:` one unless the client
65669
+ * genuinely needs a whole family; a category scope grants every future member
65670
+ * of that category too. `category:system [create]` has been rejected once and
65671
+ * should stay rejected: it hands `addons.installPackage` to an integration.
65672
+ *
65673
+ * Calls the ADDON itself makes over `ctx.api` run as the addon and are not
65674
+ * scope-checked, so they are not what this field describes — but reaching the
65675
+ * addon's route in the first place IS, and that is the entry to declare.
65676
+ *
65677
+ * **The boot window.** An addon registers its provider after its runner forks
65678
+ * and initialises, so between hub start and that moment this collection is
65679
+ * incomplete and an `integrationId` can be legitimately absent. The core does
65680
+ * not wait, poll or cache around this ([D3](../../../../docs/decisions/adr-0003.md)):
65681
+ * it compares the manifest declarers against the registered providers and
65682
+ * answers `503 temporarily_unavailable` (with `Retry-After` and the pending
65683
+ * addon ids) instead of `400 unknown integration`, and reports
65684
+ * `complete: false` on `GET /api/oauth2/integrations`. A client should retry
65685
+ * while the list is incomplete rather than conclude the hub cannot do OAuth.
65569
65686
  */
65570
65687
  var OauthIntegrationDescriptorSchema = object({
65571
65688
  /** Stable id used as the `integration=` query param, e.g. 'export-alexa'. */
@@ -65596,7 +65713,30 @@ var OauthIntegrationDescriptorSchema = object({
65596
65713
  * present, /api/oauth2/authorize bakes THIS into the code instead of the
65597
65714
  * hub-global `publicHubUrl()`, so a forked exporter addon (which can't set
65598
65715
  * the hub's env) drives the claim that its cloud Lambda routes back on. */
65599
- hubUrl: string().optional()
65716
+ hubUrl: string().optional(),
65717
+ /**
65718
+ * How long a REFRESH token issued for this integration lives — seconds, or
65719
+ * `'never'` for a token minted with no `exp` claim at all. Omit to keep the
65720
+ * 30-day default, which is what every link used before this field existed.
65721
+ *
65722
+ * Declared here for the same reason `requestedScopes` is: the integration
65723
+ * knows what it needs. Amazon's account linking and a Home Assistant config
65724
+ * entry are both meant to survive indefinitely, and re-linking is a manual
65725
+ * user action, so a 30-day expiry silently unlinks a working integration.
65726
+ *
65727
+ * **The security posture, stated so it is owned deliberately.** A refresh
65728
+ * token that never expires is permanent access if it leaks. What bounds it is
65729
+ * revocation, not time: `oauthRefresh` re-reads the session on every use and
65730
+ * returns `null` once `revokedAt` is set, as does `oauthVerifyAccessToken`.
65731
+ * The one gap is the ACCESS token — it is a plain signed JWT that nothing
65732
+ * re-checks against the session on the `/trpc` and `/addon/*` paths, so
65733
+ * revoking a link takes effect there only after its remaining hour. That hour
65734
+ * is why the access TTL is not configurable.
65735
+ *
65736
+ * The value is baked into the authorization code at `/authorize` and travels
65737
+ * on the tokens, so editing this field changes FUTURE links only.
65738
+ */
65739
+ refreshTokenTtlSec: union([number().int().positive(), literal("never")]).optional()
65600
65740
  });
65601
65741
  method(_void(), OauthIntegrationDescriptorSchema);
65602
65742
  /**
@@ -68733,11 +68873,29 @@ var SsoBridgeClaimsSchema = object({
68733
68873
  codeChallenge: string().optional(),
68734
68874
  /** OAuth session registry id — set on `oauth-access`/`oauth-refresh`
68735
68875
  * tokens so the verify path can check the session is not revoked. */
68736
- sessionId: string().optional()
68876
+ sessionId: string().optional(),
68877
+ /**
68878
+ * The refresh lifetime this LINK was created with, in seconds, or `'never'`.
68879
+ * Baked into the code at `/authorize` from the integration's descriptor and
68880
+ * carried forward so `oauthRefresh` re-mints with the same lifetime. It rides
68881
+ * on the token rather than being re-read from the descriptor on purpose:
68882
+ * editing a descriptor must not retroactively extend or shorten a link the
68883
+ * operator already consented to.
68884
+ */
68885
+ refreshTtl: union([number().int().positive(), literal("never")]).optional()
68737
68886
  });
68738
68887
  method(object({
68739
68888
  claims: SsoBridgeClaimsSchema,
68740
- ttlSec: number().int().positive().optional()
68889
+ /**
68890
+ * Seconds, or `'never'` for a token minted with NO `exp` claim.
68891
+ *
68892
+ * `'never'` is a literal rather than `undefined`/`0` because omitting
68893
+ * this field already means "the 5-minute SSO hand-off default", and
68894
+ * `jwt.sign` THROWS on `{ expiresIn: undefined }` — a "no expiry" that
68895
+ * went through the numeric path would fail at mint time and break
68896
+ * linking rather than produce an eternal token.
68897
+ */
68898
+ ttlSec: union([number().int().positive(), literal("never")]).optional()
68741
68899
  }), object({ token: string() })), method(object({ token: string() }), SsoBridgeClaimsSchema.nullable());
68742
68900
  var ProviderListEntrySchema = discriminatedUnion("shouldSaveDiskSpace", [object({
68743
68901
  providerId: string().min(1),
@@ -75954,7 +76112,12 @@ method(_void(), array(UserSummarySchema), { auth: "admin" }), method(CreateUserI
75954
76112
  hubUrl: string(),
75955
76113
  /** PKCE (RFC 7636) S256 challenge. Baked into the signed code; a code
75956
76114
  * that carries one can ONLY be exchanged with the matching verifier. */
75957
- codeChallenge: string().optional()
76115
+ codeChallenge: string().optional(),
76116
+ /** The integration's declared refresh lifetime — seconds, or `'never'`.
76117
+ * From `OauthIntegrationDescriptor.refreshTokenTtlSec`. Baked into the
76118
+ * code so the link carries its own lifetime; omit for the 30-day
76119
+ * default. */
76120
+ refreshTtlSec: union([number().int().positive(), literal("never")]).optional()
75958
76121
  }), object({ code: string() }), {
75959
76122
  kind: "mutation",
75960
76123
  access: "create"
package/dist/addon.mjs CHANGED
@@ -50504,7 +50504,7 @@ objectType({
50504
50504
  })
50505
50505
  });
50506
50506
  //#endregion
50507
- //#region ../types/dist/event-category-41fKf-q9.mjs
50507
+ //#region ../types/dist/event-category-Cv9dO26A.mjs
50508
50508
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
50509
50509
  EventCategory["SystemBoot"] = "system.boot";
50510
50510
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -50520,6 +50520,13 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
50520
50520
  */
50521
50521
  EventCategory["SystemRestartCompleted"] = "system.restart-completed";
50522
50522
  /**
50523
+ * A newer addon or server-root package version was found by the
50524
+ * authoritative registry check. Emitted once per
50525
+ * `(target, packageName, currentVersion, latestVersion)` transition; repeated
50526
+ * polling of the same result is deduplicated by the checker.
50527
+ */
50528
+ EventCategory["UpdateAvailable"] = "update.available";
50529
+ /**
50523
50530
  * Readiness transition for a capability provider. Every producer emits
50524
50531
  * this event on `onInitialize` completion, `onDestroy`, and
50525
50532
  * `$node.reconnect`; every consumer that needs to gate on a cross-process
@@ -64574,8 +64581,35 @@ var NcDeliverySchema = _enum([
64574
64581
  "immediate",
64575
64582
  "track-end",
64576
64583
  "device-event",
64577
- "package-event"
64584
+ "package-event",
64585
+ "system-event"
64586
+ ]);
64587
+ /**
64588
+ * Stable Notification Center vocabulary over infrastructure/liveness events.
64589
+ * Bus categories are normalized into these intent-level kinds so rules do not
64590
+ * depend on a provider's raw event name or payload shape.
64591
+ */
64592
+ var NcSystemEventKindSchema = _enum([
64593
+ "camera-online",
64594
+ "camera-offline",
64595
+ "stream-online",
64596
+ "stream-offline",
64597
+ "node-online",
64598
+ "node-offline",
64599
+ "addon-update-available",
64600
+ "server-update-available"
64578
64601
  ]);
64602
+ /**
64603
+ * One coherent system-event condition. `kinds` is the required opt-in safety
64604
+ * gate; the remaining lists are optional narrowing filters relevant to the
64605
+ * selected kinds.
64606
+ */
64607
+ var NcSystemEventConditionSchema = object({
64608
+ kinds: array(NcSystemEventKindSchema).min(1),
64609
+ deviceIds: array(number().int()).min(1).optional(),
64610
+ nodeIds: array(string().min(1)).min(1).optional(),
64611
+ packageNames: array(string().min(1)).min(1).optional()
64612
+ });
64579
64613
  /** Weekly schedule — OR of windows; absence on the rule = always active. */
64580
64614
  var NcScheduleSchema = object({
64581
64615
  windows: array(object({
@@ -64880,6 +64914,8 @@ var NcConditionsSchema = object({
64880
64914
  "picked-up",
64881
64915
  "both"
64882
64916
  ]).optional(),
64917
+ /** Infrastructure/liveness/update event matcher (`system-event` delivery). */
64918
+ systemEvent: NcSystemEventConditionSchema.optional(),
64883
64919
  /**
64884
64920
  * PERSONAL-RULE custom zones (viewer-drawn). Inline normalized polygons
64885
64921
  * (MaskShape vocabulary). A record passes when its bbox overlaps ANY
@@ -65099,7 +65135,8 @@ var NcTestResultSchema = object({
65099
65135
  "object-event",
65100
65136
  "track",
65101
65137
  "device-event",
65102
- "package-event"
65138
+ "package-event",
65139
+ "system-event"
65103
65140
  ]),
65104
65141
  deviceId: number(),
65105
65142
  timestamp: number(),
@@ -65121,7 +65158,8 @@ var NcConditionDescriptorSchema = object({
65121
65158
  "schedule",
65122
65159
  "device",
65123
65160
  "package",
65124
- "occupancy"
65161
+ "occupancy",
65162
+ "system"
65125
65163
  ]),
65126
65164
  label: string(),
65127
65165
  /** Editor widget the UI renders — never hardcode per-condition forms. */
@@ -65139,7 +65177,8 @@ var NcConditionDescriptorSchema = object({
65139
65177
  "crossingSelect",
65140
65178
  "polygonDraw",
65141
65179
  "occupancy",
65142
- "deviceState"
65180
+ "deviceState",
65181
+ "systemEvent"
65143
65182
  ]),
65144
65183
  operator: _enum([
65145
65184
  "in",
@@ -65198,7 +65237,8 @@ var NcHistoryRecordKindSchema = _enum([
65198
65237
  "object-event",
65199
65238
  "track-end",
65200
65239
  "device-event",
65201
- "package-event"
65240
+ "package-event",
65241
+ "system-event"
65202
65242
  ]);
65203
65243
  /** Subject summary frozen on the row at fire time (survives rule/record edits). */
65204
65244
  var NcHistorySubjectSchema = object({
@@ -65206,7 +65246,14 @@ var NcHistorySubjectSchema = object({
65206
65246
  label: string().optional(),
65207
65247
  confidence: number().optional(),
65208
65248
  zones: array(string()),
65209
- timestamp: number()
65249
+ timestamp: number(),
65250
+ systemEvent: object({
65251
+ kind: NcSystemEventKindSchema,
65252
+ subject: string(),
65253
+ title: string(),
65254
+ body: string(),
65255
+ data: record(string(), unknown())
65256
+ }).optional()
65210
65257
  });
65211
65258
  /**
65212
65259
  * One delivery-history row. This is a read-only VIEW over the durable
@@ -65566,6 +65613,76 @@ object({
65566
65613
  * Each provider returns a static descriptor; the core enumerates them
65567
65614
  * to validate the `integration=` query param and resolve the consent
65568
65615
  * label + the scopes baked into the issued token.
65616
+ *
65617
+ * ## Declaring one
65618
+ *
65619
+ * An OAuth client is integration-specific knowledge — who the client is, what
65620
+ * it may ask for, where it may be sent — so it is declared by the ADDON that
65621
+ * owns the integration, never by the kernel and never as a branch inside
65622
+ * `oauth2-routes.ts` ([D101](../../../../docs/decisions/adr-0101.md)). Three
65623
+ * steps, no others:
65624
+ *
65625
+ * 1. Add `{ "name": "oauth-integration" }` to the addon's `camstack.addons[]`
65626
+ * manifest entry. This is also what tells the hub, at addon-LOAD time, that
65627
+ * a descriptor is owed — see "the boot window" below.
65628
+ * 2. Return a provider from `onInitialize()`:
65629
+ *
65630
+ * ```ts
65631
+ * const provider: IOauthIntegrationProvider = {
65632
+ * getDescriptor: async () => ({
65633
+ * integrationId: 'my-thing', // the `integration=` query param
65634
+ * displayName: 'My Thing',
65635
+ * requestedScopes: [ … ], // see below
65636
+ * allowedRedirectPrefixes: ['https://callback.example/'],
65637
+ * }),
65638
+ * }
65639
+ * return [{ capability: oauthIntegrationCapability, provider }]
65640
+ * ```
65641
+ *
65642
+ * The descriptor must be **static** — it is read on the authorize path, so
65643
+ * never put an await on network or disk behind it, and never register it
65644
+ * behind one either (a provider is registered only once `onInitialize`
65645
+ * RETURNS, so anything awaited before the return delays linking).
65646
+ * 3. Nothing else. There is no allow-list to join, no id to register with the
65647
+ * core, and no per-integration branch anywhere: `/api/oauth2/authorize` and
65648
+ * `/api/oauth2/integrations` are built from this collection alone.
65649
+ *
65650
+ * **Scopes. `requestedScopes` has exactly ONE meaning: what the integration
65651
+ * NEEDS to function.** Not a blast radius, not a conservative
65652
+ * under-declaration, not a description of some other path the addon happens to
65653
+ * have. Derive it from what the client actually calls **with this token** —
65654
+ * every tRPC path against `METHOD_ACCESS_MAP`, plus an `addon:` grant for every
65655
+ * addon HTTP route it posts to — and write the call that justifies each entry
65656
+ * next to it. Two integrations once used this field to mean two different
65657
+ * things; the operator ruled there is one meaning, and any third integration
65658
+ * inherits it (2026-08-09).
65659
+ *
65660
+ * This is not documentation, it is the ENFORCEMENT INPUT. Since
65661
+ * [D103](../../../../docs/decisions/adr-0103.md) the `/addon/:addonId/*` gate
65662
+ * checks an integration token's grant before letting it reach an
65663
+ * `access: 'authenticated'` route, so an **under-declaration is an integration
65664
+ * that stops working** — a missing `addon:` entry means `403 Token scope
65665
+ * mismatch` on every control the client tries to actuate. Widen the descriptor
65666
+ * honestly rather than weakening a check to make a route pass.
65667
+ *
65668
+ * Prefer a narrow `capability:` scope to a `category:` one unless the client
65669
+ * genuinely needs a whole family; a category scope grants every future member
65670
+ * of that category too. `category:system [create]` has been rejected once and
65671
+ * should stay rejected: it hands `addons.installPackage` to an integration.
65672
+ *
65673
+ * Calls the ADDON itself makes over `ctx.api` run as the addon and are not
65674
+ * scope-checked, so they are not what this field describes — but reaching the
65675
+ * addon's route in the first place IS, and that is the entry to declare.
65676
+ *
65677
+ * **The boot window.** An addon registers its provider after its runner forks
65678
+ * and initialises, so between hub start and that moment this collection is
65679
+ * incomplete and an `integrationId` can be legitimately absent. The core does
65680
+ * not wait, poll or cache around this ([D3](../../../../docs/decisions/adr-0003.md)):
65681
+ * it compares the manifest declarers against the registered providers and
65682
+ * answers `503 temporarily_unavailable` (with `Retry-After` and the pending
65683
+ * addon ids) instead of `400 unknown integration`, and reports
65684
+ * `complete: false` on `GET /api/oauth2/integrations`. A client should retry
65685
+ * while the list is incomplete rather than conclude the hub cannot do OAuth.
65569
65686
  */
65570
65687
  var OauthIntegrationDescriptorSchema = object({
65571
65688
  /** Stable id used as the `integration=` query param, e.g. 'export-alexa'. */
@@ -65596,7 +65713,30 @@ var OauthIntegrationDescriptorSchema = object({
65596
65713
  * present, /api/oauth2/authorize bakes THIS into the code instead of the
65597
65714
  * hub-global `publicHubUrl()`, so a forked exporter addon (which can't set
65598
65715
  * the hub's env) drives the claim that its cloud Lambda routes back on. */
65599
- hubUrl: string().optional()
65716
+ hubUrl: string().optional(),
65717
+ /**
65718
+ * How long a REFRESH token issued for this integration lives — seconds, or
65719
+ * `'never'` for a token minted with no `exp` claim at all. Omit to keep the
65720
+ * 30-day default, which is what every link used before this field existed.
65721
+ *
65722
+ * Declared here for the same reason `requestedScopes` is: the integration
65723
+ * knows what it needs. Amazon's account linking and a Home Assistant config
65724
+ * entry are both meant to survive indefinitely, and re-linking is a manual
65725
+ * user action, so a 30-day expiry silently unlinks a working integration.
65726
+ *
65727
+ * **The security posture, stated so it is owned deliberately.** A refresh
65728
+ * token that never expires is permanent access if it leaks. What bounds it is
65729
+ * revocation, not time: `oauthRefresh` re-reads the session on every use and
65730
+ * returns `null` once `revokedAt` is set, as does `oauthVerifyAccessToken`.
65731
+ * The one gap is the ACCESS token — it is a plain signed JWT that nothing
65732
+ * re-checks against the session on the `/trpc` and `/addon/*` paths, so
65733
+ * revoking a link takes effect there only after its remaining hour. That hour
65734
+ * is why the access TTL is not configurable.
65735
+ *
65736
+ * The value is baked into the authorization code at `/authorize` and travels
65737
+ * on the tokens, so editing this field changes FUTURE links only.
65738
+ */
65739
+ refreshTokenTtlSec: union([number().int().positive(), literal("never")]).optional()
65600
65740
  });
65601
65741
  method(_void(), OauthIntegrationDescriptorSchema);
65602
65742
  /**
@@ -68733,11 +68873,29 @@ var SsoBridgeClaimsSchema = object({
68733
68873
  codeChallenge: string().optional(),
68734
68874
  /** OAuth session registry id — set on `oauth-access`/`oauth-refresh`
68735
68875
  * tokens so the verify path can check the session is not revoked. */
68736
- sessionId: string().optional()
68876
+ sessionId: string().optional(),
68877
+ /**
68878
+ * The refresh lifetime this LINK was created with, in seconds, or `'never'`.
68879
+ * Baked into the code at `/authorize` from the integration's descriptor and
68880
+ * carried forward so `oauthRefresh` re-mints with the same lifetime. It rides
68881
+ * on the token rather than being re-read from the descriptor on purpose:
68882
+ * editing a descriptor must not retroactively extend or shorten a link the
68883
+ * operator already consented to.
68884
+ */
68885
+ refreshTtl: union([number().int().positive(), literal("never")]).optional()
68737
68886
  });
68738
68887
  method(object({
68739
68888
  claims: SsoBridgeClaimsSchema,
68740
- ttlSec: number().int().positive().optional()
68889
+ /**
68890
+ * Seconds, or `'never'` for a token minted with NO `exp` claim.
68891
+ *
68892
+ * `'never'` is a literal rather than `undefined`/`0` because omitting
68893
+ * this field already means "the 5-minute SSO hand-off default", and
68894
+ * `jwt.sign` THROWS on `{ expiresIn: undefined }` — a "no expiry" that
68895
+ * went through the numeric path would fail at mint time and break
68896
+ * linking rather than produce an eternal token.
68897
+ */
68898
+ ttlSec: union([number().int().positive(), literal("never")]).optional()
68741
68899
  }), object({ token: string() })), method(object({ token: string() }), SsoBridgeClaimsSchema.nullable());
68742
68900
  var ProviderListEntrySchema = discriminatedUnion("shouldSaveDiskSpace", [object({
68743
68901
  providerId: string().min(1),
@@ -75954,7 +76112,12 @@ method(_void(), array(UserSummarySchema), { auth: "admin" }), method(CreateUserI
75954
76112
  hubUrl: string(),
75955
76113
  /** PKCE (RFC 7636) S256 challenge. Baked into the signed code; a code
75956
76114
  * that carries one can ONLY be exchanged with the matching verifier. */
75957
- codeChallenge: string().optional()
76115
+ codeChallenge: string().optional(),
76116
+ /** The integration's declared refresh lifetime — seconds, or `'never'`.
76117
+ * From `OauthIntegrationDescriptor.refreshTokenTtlSec`. Baked into the
76118
+ * code so the link carries its own lifetime; omit for the 30-day
76119
+ * default. */
76120
+ refreshTtlSec: union([number().int().positive(), literal("never")]).optional()
75958
76121
  }), object({ code: string() }), {
75959
76122
  kind: "mutation",
75960
76123
  access: "create"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-dreame",
3
- "version": "0.2.10",
3
+ "version": "0.2.11",
4
4
  "description": "Dreame robot-vacuum / lawn-mower device-provider addon for CamStack — wraps the @apocaliss92/nodedreame Dreamehome cloud client",
5
5
  "keywords": [
6
6
  "camstack",