@camstack/addon-provider-rtsp 1.2.10 → 1.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
@@ -23,7 +23,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
23
23
  //#endregion
24
24
  let node_net = require("node:net");
25
25
  node_net = __toESM(node_net);
26
- //#region ../types/dist/event-category-41fKf-q9.mjs
26
+ //#region ../types/dist/event-category-Cv9dO26A.mjs
27
27
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
28
28
  EventCategory["SystemBoot"] = "system.boot";
29
29
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -39,6 +39,13 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
39
39
  */
40
40
  EventCategory["SystemRestartCompleted"] = "system.restart-completed";
41
41
  /**
42
+ * A newer addon or server-root package version was found by the
43
+ * authoritative registry check. Emitted once per
44
+ * `(target, packageName, currentVersion, latestVersion)` transition; repeated
45
+ * polling of the same result is deduplicated by the checker.
46
+ */
47
+ EventCategory["UpdateAvailable"] = "update.available";
48
+ /**
42
49
  * Readiness transition for a capability provider. Every producer emits
43
50
  * this event on `onInitialize` completion, `onDestroy`, and
44
51
  * `$node.reconnect`; every consumer that needs to gate on a cross-process
@@ -14108,8 +14115,35 @@ var NcDeliverySchema = _enum([
14108
14115
  "immediate",
14109
14116
  "track-end",
14110
14117
  "device-event",
14111
- "package-event"
14118
+ "package-event",
14119
+ "system-event"
14120
+ ]);
14121
+ /**
14122
+ * Stable Notification Center vocabulary over infrastructure/liveness events.
14123
+ * Bus categories are normalized into these intent-level kinds so rules do not
14124
+ * depend on a provider's raw event name or payload shape.
14125
+ */
14126
+ var NcSystemEventKindSchema = _enum([
14127
+ "camera-online",
14128
+ "camera-offline",
14129
+ "stream-online",
14130
+ "stream-offline",
14131
+ "node-online",
14132
+ "node-offline",
14133
+ "addon-update-available",
14134
+ "server-update-available"
14112
14135
  ]);
14136
+ /**
14137
+ * One coherent system-event condition. `kinds` is the required opt-in safety
14138
+ * gate; the remaining lists are optional narrowing filters relevant to the
14139
+ * selected kinds.
14140
+ */
14141
+ var NcSystemEventConditionSchema = object({
14142
+ kinds: array(NcSystemEventKindSchema).min(1),
14143
+ deviceIds: array(number().int()).min(1).optional(),
14144
+ nodeIds: array(string().min(1)).min(1).optional(),
14145
+ packageNames: array(string().min(1)).min(1).optional()
14146
+ });
14113
14147
  /** Weekly schedule — OR of windows; absence on the rule = always active. */
14114
14148
  var NcScheduleSchema = object({
14115
14149
  windows: array(object({
@@ -14414,6 +14448,8 @@ var NcConditionsSchema = object({
14414
14448
  "picked-up",
14415
14449
  "both"
14416
14450
  ]).optional(),
14451
+ /** Infrastructure/liveness/update event matcher (`system-event` delivery). */
14452
+ systemEvent: NcSystemEventConditionSchema.optional(),
14417
14453
  /**
14418
14454
  * PERSONAL-RULE custom zones (viewer-drawn). Inline normalized polygons
14419
14455
  * (MaskShape vocabulary). A record passes when its bbox overlaps ANY
@@ -14633,7 +14669,8 @@ var NcTestResultSchema = object({
14633
14669
  "object-event",
14634
14670
  "track",
14635
14671
  "device-event",
14636
- "package-event"
14672
+ "package-event",
14673
+ "system-event"
14637
14674
  ]),
14638
14675
  deviceId: number(),
14639
14676
  timestamp: number(),
@@ -14655,7 +14692,8 @@ var NcConditionDescriptorSchema = object({
14655
14692
  "schedule",
14656
14693
  "device",
14657
14694
  "package",
14658
- "occupancy"
14695
+ "occupancy",
14696
+ "system"
14659
14697
  ]),
14660
14698
  label: string(),
14661
14699
  /** Editor widget the UI renders — never hardcode per-condition forms. */
@@ -14673,7 +14711,8 @@ var NcConditionDescriptorSchema = object({
14673
14711
  "crossingSelect",
14674
14712
  "polygonDraw",
14675
14713
  "occupancy",
14676
- "deviceState"
14714
+ "deviceState",
14715
+ "systemEvent"
14677
14716
  ]),
14678
14717
  operator: _enum([
14679
14718
  "in",
@@ -14732,7 +14771,8 @@ var NcHistoryRecordKindSchema = _enum([
14732
14771
  "object-event",
14733
14772
  "track-end",
14734
14773
  "device-event",
14735
- "package-event"
14774
+ "package-event",
14775
+ "system-event"
14736
14776
  ]);
14737
14777
  /** Subject summary frozen on the row at fire time (survives rule/record edits). */
14738
14778
  var NcHistorySubjectSchema = object({
@@ -14740,7 +14780,14 @@ var NcHistorySubjectSchema = object({
14740
14780
  label: string().optional(),
14741
14781
  confidence: number().optional(),
14742
14782
  zones: array(string()),
14743
- timestamp: number()
14783
+ timestamp: number(),
14784
+ systemEvent: object({
14785
+ kind: NcSystemEventKindSchema,
14786
+ subject: string(),
14787
+ title: string(),
14788
+ body: string(),
14789
+ data: record(string(), unknown())
14790
+ }).optional()
14744
14791
  });
14745
14792
  /**
14746
14793
  * One delivery-history row. This is a read-only VIEW over the durable
@@ -15100,6 +15147,76 @@ object({
15100
15147
  * Each provider returns a static descriptor; the core enumerates them
15101
15148
  * to validate the `integration=` query param and resolve the consent
15102
15149
  * label + the scopes baked into the issued token.
15150
+ *
15151
+ * ## Declaring one
15152
+ *
15153
+ * An OAuth client is integration-specific knowledge — who the client is, what
15154
+ * it may ask for, where it may be sent — so it is declared by the ADDON that
15155
+ * owns the integration, never by the kernel and never as a branch inside
15156
+ * `oauth2-routes.ts` ([D101](../../../../docs/decisions/adr-0101.md)). Three
15157
+ * steps, no others:
15158
+ *
15159
+ * 1. Add `{ "name": "oauth-integration" }` to the addon's `camstack.addons[]`
15160
+ * manifest entry. This is also what tells the hub, at addon-LOAD time, that
15161
+ * a descriptor is owed — see "the boot window" below.
15162
+ * 2. Return a provider from `onInitialize()`:
15163
+ *
15164
+ * ```ts
15165
+ * const provider: IOauthIntegrationProvider = {
15166
+ * getDescriptor: async () => ({
15167
+ * integrationId: 'my-thing', // the `integration=` query param
15168
+ * displayName: 'My Thing',
15169
+ * requestedScopes: [ … ], // see below
15170
+ * allowedRedirectPrefixes: ['https://callback.example/'],
15171
+ * }),
15172
+ * }
15173
+ * return [{ capability: oauthIntegrationCapability, provider }]
15174
+ * ```
15175
+ *
15176
+ * The descriptor must be **static** — it is read on the authorize path, so
15177
+ * never put an await on network or disk behind it, and never register it
15178
+ * behind one either (a provider is registered only once `onInitialize`
15179
+ * RETURNS, so anything awaited before the return delays linking).
15180
+ * 3. Nothing else. There is no allow-list to join, no id to register with the
15181
+ * core, and no per-integration branch anywhere: `/api/oauth2/authorize` and
15182
+ * `/api/oauth2/integrations` are built from this collection alone.
15183
+ *
15184
+ * **Scopes. `requestedScopes` has exactly ONE meaning: what the integration
15185
+ * NEEDS to function.** Not a blast radius, not a conservative
15186
+ * under-declaration, not a description of some other path the addon happens to
15187
+ * have. Derive it from what the client actually calls **with this token** —
15188
+ * every tRPC path against `METHOD_ACCESS_MAP`, plus an `addon:` grant for every
15189
+ * addon HTTP route it posts to — and write the call that justifies each entry
15190
+ * next to it. Two integrations once used this field to mean two different
15191
+ * things; the operator ruled there is one meaning, and any third integration
15192
+ * inherits it (2026-08-09).
15193
+ *
15194
+ * This is not documentation, it is the ENFORCEMENT INPUT. Since
15195
+ * [D103](../../../../docs/decisions/adr-0103.md) the `/addon/:addonId/*` gate
15196
+ * checks an integration token's grant before letting it reach an
15197
+ * `access: 'authenticated'` route, so an **under-declaration is an integration
15198
+ * that stops working** — a missing `addon:` entry means `403 Token scope
15199
+ * mismatch` on every control the client tries to actuate. Widen the descriptor
15200
+ * honestly rather than weakening a check to make a route pass.
15201
+ *
15202
+ * Prefer a narrow `capability:` scope to a `category:` one unless the client
15203
+ * genuinely needs a whole family; a category scope grants every future member
15204
+ * of that category too. `category:system [create]` has been rejected once and
15205
+ * should stay rejected: it hands `addons.installPackage` to an integration.
15206
+ *
15207
+ * Calls the ADDON itself makes over `ctx.api` run as the addon and are not
15208
+ * scope-checked, so they are not what this field describes — but reaching the
15209
+ * addon's route in the first place IS, and that is the entry to declare.
15210
+ *
15211
+ * **The boot window.** An addon registers its provider after its runner forks
15212
+ * and initialises, so between hub start and that moment this collection is
15213
+ * incomplete and an `integrationId` can be legitimately absent. The core does
15214
+ * not wait, poll or cache around this ([D3](../../../../docs/decisions/adr-0003.md)):
15215
+ * it compares the manifest declarers against the registered providers and
15216
+ * answers `503 temporarily_unavailable` (with `Retry-After` and the pending
15217
+ * addon ids) instead of `400 unknown integration`, and reports
15218
+ * `complete: false` on `GET /api/oauth2/integrations`. A client should retry
15219
+ * while the list is incomplete rather than conclude the hub cannot do OAuth.
15103
15220
  */
15104
15221
  var OauthIntegrationDescriptorSchema = object({
15105
15222
  /** Stable id used as the `integration=` query param, e.g. 'export-alexa'. */
@@ -15130,7 +15247,30 @@ var OauthIntegrationDescriptorSchema = object({
15130
15247
  * present, /api/oauth2/authorize bakes THIS into the code instead of the
15131
15248
  * hub-global `publicHubUrl()`, so a forked exporter addon (which can't set
15132
15249
  * the hub's env) drives the claim that its cloud Lambda routes back on. */
15133
- hubUrl: string().optional()
15250
+ hubUrl: string().optional(),
15251
+ /**
15252
+ * How long a REFRESH token issued for this integration lives — seconds, or
15253
+ * `'never'` for a token minted with no `exp` claim at all. Omit to keep the
15254
+ * 30-day default, which is what every link used before this field existed.
15255
+ *
15256
+ * Declared here for the same reason `requestedScopes` is: the integration
15257
+ * knows what it needs. Amazon's account linking and a Home Assistant config
15258
+ * entry are both meant to survive indefinitely, and re-linking is a manual
15259
+ * user action, so a 30-day expiry silently unlinks a working integration.
15260
+ *
15261
+ * **The security posture, stated so it is owned deliberately.** A refresh
15262
+ * token that never expires is permanent access if it leaks. What bounds it is
15263
+ * revocation, not time: `oauthRefresh` re-reads the session on every use and
15264
+ * returns `null` once `revokedAt` is set, as does `oauthVerifyAccessToken`.
15265
+ * The one gap is the ACCESS token — it is a plain signed JWT that nothing
15266
+ * re-checks against the session on the `/trpc` and `/addon/*` paths, so
15267
+ * revoking a link takes effect there only after its remaining hour. That hour
15268
+ * is why the access TTL is not configurable.
15269
+ *
15270
+ * The value is baked into the authorization code at `/authorize` and travels
15271
+ * on the tokens, so editing this field changes FUTURE links only.
15272
+ */
15273
+ refreshTokenTtlSec: union([number().int().positive(), literal("never")]).optional()
15134
15274
  });
15135
15275
  method(_void(), OauthIntegrationDescriptorSchema);
15136
15276
  /**
@@ -18358,11 +18498,29 @@ var SsoBridgeClaimsSchema = object({
18358
18498
  codeChallenge: string().optional(),
18359
18499
  /** OAuth session registry id — set on `oauth-access`/`oauth-refresh`
18360
18500
  * tokens so the verify path can check the session is not revoked. */
18361
- sessionId: string().optional()
18501
+ sessionId: string().optional(),
18502
+ /**
18503
+ * The refresh lifetime this LINK was created with, in seconds, or `'never'`.
18504
+ * Baked into the code at `/authorize` from the integration's descriptor and
18505
+ * carried forward so `oauthRefresh` re-mints with the same lifetime. It rides
18506
+ * on the token rather than being re-read from the descriptor on purpose:
18507
+ * editing a descriptor must not retroactively extend or shorten a link the
18508
+ * operator already consented to.
18509
+ */
18510
+ refreshTtl: union([number().int().positive(), literal("never")]).optional()
18362
18511
  });
18363
18512
  method(object({
18364
18513
  claims: SsoBridgeClaimsSchema,
18365
- ttlSec: number().int().positive().optional()
18514
+ /**
18515
+ * Seconds, or `'never'` for a token minted with NO `exp` claim.
18516
+ *
18517
+ * `'never'` is a literal rather than `undefined`/`0` because omitting
18518
+ * this field already means "the 5-minute SSO hand-off default", and
18519
+ * `jwt.sign` THROWS on `{ expiresIn: undefined }` — a "no expiry" that
18520
+ * went through the numeric path would fail at mint time and break
18521
+ * linking rather than produce an eternal token.
18522
+ */
18523
+ ttlSec: union([number().int().positive(), literal("never")]).optional()
18366
18524
  }), object({ token: string() })), method(object({ token: string() }), SsoBridgeClaimsSchema.nullable());
18367
18525
  var ProviderListEntrySchema = discriminatedUnion("shouldSaveDiskSpace", [object({
18368
18526
  providerId: string().min(1),
@@ -25575,7 +25733,12 @@ method(_void(), array(UserSummarySchema), { auth: "admin" }), method(CreateUserI
25575
25733
  hubUrl: string(),
25576
25734
  /** PKCE (RFC 7636) S256 challenge. Baked into the signed code; a code
25577
25735
  * that carries one can ONLY be exchanged with the matching verifier. */
25578
- codeChallenge: string().optional()
25736
+ codeChallenge: string().optional(),
25737
+ /** The integration's declared refresh lifetime — seconds, or `'never'`.
25738
+ * From `OauthIntegrationDescriptor.refreshTokenTtlSec`. Baked into the
25739
+ * code so the link carries its own lifetime; omit for the 30-day
25740
+ * default. */
25741
+ refreshTtlSec: union([number().int().positive(), literal("never")]).optional()
25579
25742
  }), object({ code: string() }), {
25580
25743
  kind: "mutation",
25581
25744
  access: "create"
package/dist/addon.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import net from "node:net";
2
- //#region ../types/dist/event-category-41fKf-q9.mjs
2
+ //#region ../types/dist/event-category-Cv9dO26A.mjs
3
3
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
4
4
  EventCategory["SystemBoot"] = "system.boot";
5
5
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -15,6 +15,13 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
15
15
  */
16
16
  EventCategory["SystemRestartCompleted"] = "system.restart-completed";
17
17
  /**
18
+ * A newer addon or server-root package version was found by the
19
+ * authoritative registry check. Emitted once per
20
+ * `(target, packageName, currentVersion, latestVersion)` transition; repeated
21
+ * polling of the same result is deduplicated by the checker.
22
+ */
23
+ EventCategory["UpdateAvailable"] = "update.available";
24
+ /**
18
25
  * Readiness transition for a capability provider. Every producer emits
19
26
  * this event on `onInitialize` completion, `onDestroy`, and
20
27
  * `$node.reconnect`; every consumer that needs to gate on a cross-process
@@ -14084,8 +14091,35 @@ var NcDeliverySchema = _enum([
14084
14091
  "immediate",
14085
14092
  "track-end",
14086
14093
  "device-event",
14087
- "package-event"
14094
+ "package-event",
14095
+ "system-event"
14096
+ ]);
14097
+ /**
14098
+ * Stable Notification Center vocabulary over infrastructure/liveness events.
14099
+ * Bus categories are normalized into these intent-level kinds so rules do not
14100
+ * depend on a provider's raw event name or payload shape.
14101
+ */
14102
+ var NcSystemEventKindSchema = _enum([
14103
+ "camera-online",
14104
+ "camera-offline",
14105
+ "stream-online",
14106
+ "stream-offline",
14107
+ "node-online",
14108
+ "node-offline",
14109
+ "addon-update-available",
14110
+ "server-update-available"
14088
14111
  ]);
14112
+ /**
14113
+ * One coherent system-event condition. `kinds` is the required opt-in safety
14114
+ * gate; the remaining lists are optional narrowing filters relevant to the
14115
+ * selected kinds.
14116
+ */
14117
+ var NcSystemEventConditionSchema = object({
14118
+ kinds: array(NcSystemEventKindSchema).min(1),
14119
+ deviceIds: array(number().int()).min(1).optional(),
14120
+ nodeIds: array(string().min(1)).min(1).optional(),
14121
+ packageNames: array(string().min(1)).min(1).optional()
14122
+ });
14089
14123
  /** Weekly schedule — OR of windows; absence on the rule = always active. */
14090
14124
  var NcScheduleSchema = object({
14091
14125
  windows: array(object({
@@ -14390,6 +14424,8 @@ var NcConditionsSchema = object({
14390
14424
  "picked-up",
14391
14425
  "both"
14392
14426
  ]).optional(),
14427
+ /** Infrastructure/liveness/update event matcher (`system-event` delivery). */
14428
+ systemEvent: NcSystemEventConditionSchema.optional(),
14393
14429
  /**
14394
14430
  * PERSONAL-RULE custom zones (viewer-drawn). Inline normalized polygons
14395
14431
  * (MaskShape vocabulary). A record passes when its bbox overlaps ANY
@@ -14609,7 +14645,8 @@ var NcTestResultSchema = object({
14609
14645
  "object-event",
14610
14646
  "track",
14611
14647
  "device-event",
14612
- "package-event"
14648
+ "package-event",
14649
+ "system-event"
14613
14650
  ]),
14614
14651
  deviceId: number(),
14615
14652
  timestamp: number(),
@@ -14631,7 +14668,8 @@ var NcConditionDescriptorSchema = object({
14631
14668
  "schedule",
14632
14669
  "device",
14633
14670
  "package",
14634
- "occupancy"
14671
+ "occupancy",
14672
+ "system"
14635
14673
  ]),
14636
14674
  label: string(),
14637
14675
  /** Editor widget the UI renders — never hardcode per-condition forms. */
@@ -14649,7 +14687,8 @@ var NcConditionDescriptorSchema = object({
14649
14687
  "crossingSelect",
14650
14688
  "polygonDraw",
14651
14689
  "occupancy",
14652
- "deviceState"
14690
+ "deviceState",
14691
+ "systemEvent"
14653
14692
  ]),
14654
14693
  operator: _enum([
14655
14694
  "in",
@@ -14708,7 +14747,8 @@ var NcHistoryRecordKindSchema = _enum([
14708
14747
  "object-event",
14709
14748
  "track-end",
14710
14749
  "device-event",
14711
- "package-event"
14750
+ "package-event",
14751
+ "system-event"
14712
14752
  ]);
14713
14753
  /** Subject summary frozen on the row at fire time (survives rule/record edits). */
14714
14754
  var NcHistorySubjectSchema = object({
@@ -14716,7 +14756,14 @@ var NcHistorySubjectSchema = object({
14716
14756
  label: string().optional(),
14717
14757
  confidence: number().optional(),
14718
14758
  zones: array(string()),
14719
- timestamp: number()
14759
+ timestamp: number(),
14760
+ systemEvent: object({
14761
+ kind: NcSystemEventKindSchema,
14762
+ subject: string(),
14763
+ title: string(),
14764
+ body: string(),
14765
+ data: record(string(), unknown())
14766
+ }).optional()
14720
14767
  });
14721
14768
  /**
14722
14769
  * One delivery-history row. This is a read-only VIEW over the durable
@@ -15076,6 +15123,76 @@ object({
15076
15123
  * Each provider returns a static descriptor; the core enumerates them
15077
15124
  * to validate the `integration=` query param and resolve the consent
15078
15125
  * label + the scopes baked into the issued token.
15126
+ *
15127
+ * ## Declaring one
15128
+ *
15129
+ * An OAuth client is integration-specific knowledge — who the client is, what
15130
+ * it may ask for, where it may be sent — so it is declared by the ADDON that
15131
+ * owns the integration, never by the kernel and never as a branch inside
15132
+ * `oauth2-routes.ts` ([D101](../../../../docs/decisions/adr-0101.md)). Three
15133
+ * steps, no others:
15134
+ *
15135
+ * 1. Add `{ "name": "oauth-integration" }` to the addon's `camstack.addons[]`
15136
+ * manifest entry. This is also what tells the hub, at addon-LOAD time, that
15137
+ * a descriptor is owed — see "the boot window" below.
15138
+ * 2. Return a provider from `onInitialize()`:
15139
+ *
15140
+ * ```ts
15141
+ * const provider: IOauthIntegrationProvider = {
15142
+ * getDescriptor: async () => ({
15143
+ * integrationId: 'my-thing', // the `integration=` query param
15144
+ * displayName: 'My Thing',
15145
+ * requestedScopes: [ … ], // see below
15146
+ * allowedRedirectPrefixes: ['https://callback.example/'],
15147
+ * }),
15148
+ * }
15149
+ * return [{ capability: oauthIntegrationCapability, provider }]
15150
+ * ```
15151
+ *
15152
+ * The descriptor must be **static** — it is read on the authorize path, so
15153
+ * never put an await on network or disk behind it, and never register it
15154
+ * behind one either (a provider is registered only once `onInitialize`
15155
+ * RETURNS, so anything awaited before the return delays linking).
15156
+ * 3. Nothing else. There is no allow-list to join, no id to register with the
15157
+ * core, and no per-integration branch anywhere: `/api/oauth2/authorize` and
15158
+ * `/api/oauth2/integrations` are built from this collection alone.
15159
+ *
15160
+ * **Scopes. `requestedScopes` has exactly ONE meaning: what the integration
15161
+ * NEEDS to function.** Not a blast radius, not a conservative
15162
+ * under-declaration, not a description of some other path the addon happens to
15163
+ * have. Derive it from what the client actually calls **with this token** —
15164
+ * every tRPC path against `METHOD_ACCESS_MAP`, plus an `addon:` grant for every
15165
+ * addon HTTP route it posts to — and write the call that justifies each entry
15166
+ * next to it. Two integrations once used this field to mean two different
15167
+ * things; the operator ruled there is one meaning, and any third integration
15168
+ * inherits it (2026-08-09).
15169
+ *
15170
+ * This is not documentation, it is the ENFORCEMENT INPUT. Since
15171
+ * [D103](../../../../docs/decisions/adr-0103.md) the `/addon/:addonId/*` gate
15172
+ * checks an integration token's grant before letting it reach an
15173
+ * `access: 'authenticated'` route, so an **under-declaration is an integration
15174
+ * that stops working** — a missing `addon:` entry means `403 Token scope
15175
+ * mismatch` on every control the client tries to actuate. Widen the descriptor
15176
+ * honestly rather than weakening a check to make a route pass.
15177
+ *
15178
+ * Prefer a narrow `capability:` scope to a `category:` one unless the client
15179
+ * genuinely needs a whole family; a category scope grants every future member
15180
+ * of that category too. `category:system [create]` has been rejected once and
15181
+ * should stay rejected: it hands `addons.installPackage` to an integration.
15182
+ *
15183
+ * Calls the ADDON itself makes over `ctx.api` run as the addon and are not
15184
+ * scope-checked, so they are not what this field describes — but reaching the
15185
+ * addon's route in the first place IS, and that is the entry to declare.
15186
+ *
15187
+ * **The boot window.** An addon registers its provider after its runner forks
15188
+ * and initialises, so between hub start and that moment this collection is
15189
+ * incomplete and an `integrationId` can be legitimately absent. The core does
15190
+ * not wait, poll or cache around this ([D3](../../../../docs/decisions/adr-0003.md)):
15191
+ * it compares the manifest declarers against the registered providers and
15192
+ * answers `503 temporarily_unavailable` (with `Retry-After` and the pending
15193
+ * addon ids) instead of `400 unknown integration`, and reports
15194
+ * `complete: false` on `GET /api/oauth2/integrations`. A client should retry
15195
+ * while the list is incomplete rather than conclude the hub cannot do OAuth.
15079
15196
  */
15080
15197
  var OauthIntegrationDescriptorSchema = object({
15081
15198
  /** Stable id used as the `integration=` query param, e.g. 'export-alexa'. */
@@ -15106,7 +15223,30 @@ var OauthIntegrationDescriptorSchema = object({
15106
15223
  * present, /api/oauth2/authorize bakes THIS into the code instead of the
15107
15224
  * hub-global `publicHubUrl()`, so a forked exporter addon (which can't set
15108
15225
  * the hub's env) drives the claim that its cloud Lambda routes back on. */
15109
- hubUrl: string().optional()
15226
+ hubUrl: string().optional(),
15227
+ /**
15228
+ * How long a REFRESH token issued for this integration lives — seconds, or
15229
+ * `'never'` for a token minted with no `exp` claim at all. Omit to keep the
15230
+ * 30-day default, which is what every link used before this field existed.
15231
+ *
15232
+ * Declared here for the same reason `requestedScopes` is: the integration
15233
+ * knows what it needs. Amazon's account linking and a Home Assistant config
15234
+ * entry are both meant to survive indefinitely, and re-linking is a manual
15235
+ * user action, so a 30-day expiry silently unlinks a working integration.
15236
+ *
15237
+ * **The security posture, stated so it is owned deliberately.** A refresh
15238
+ * token that never expires is permanent access if it leaks. What bounds it is
15239
+ * revocation, not time: `oauthRefresh` re-reads the session on every use and
15240
+ * returns `null` once `revokedAt` is set, as does `oauthVerifyAccessToken`.
15241
+ * The one gap is the ACCESS token — it is a plain signed JWT that nothing
15242
+ * re-checks against the session on the `/trpc` and `/addon/*` paths, so
15243
+ * revoking a link takes effect there only after its remaining hour. That hour
15244
+ * is why the access TTL is not configurable.
15245
+ *
15246
+ * The value is baked into the authorization code at `/authorize` and travels
15247
+ * on the tokens, so editing this field changes FUTURE links only.
15248
+ */
15249
+ refreshTokenTtlSec: union([number().int().positive(), literal("never")]).optional()
15110
15250
  });
15111
15251
  method(_void(), OauthIntegrationDescriptorSchema);
15112
15252
  /**
@@ -18334,11 +18474,29 @@ var SsoBridgeClaimsSchema = object({
18334
18474
  codeChallenge: string().optional(),
18335
18475
  /** OAuth session registry id — set on `oauth-access`/`oauth-refresh`
18336
18476
  * tokens so the verify path can check the session is not revoked. */
18337
- sessionId: string().optional()
18477
+ sessionId: string().optional(),
18478
+ /**
18479
+ * The refresh lifetime this LINK was created with, in seconds, or `'never'`.
18480
+ * Baked into the code at `/authorize` from the integration's descriptor and
18481
+ * carried forward so `oauthRefresh` re-mints with the same lifetime. It rides
18482
+ * on the token rather than being re-read from the descriptor on purpose:
18483
+ * editing a descriptor must not retroactively extend or shorten a link the
18484
+ * operator already consented to.
18485
+ */
18486
+ refreshTtl: union([number().int().positive(), literal("never")]).optional()
18338
18487
  });
18339
18488
  method(object({
18340
18489
  claims: SsoBridgeClaimsSchema,
18341
- ttlSec: number().int().positive().optional()
18490
+ /**
18491
+ * Seconds, or `'never'` for a token minted with NO `exp` claim.
18492
+ *
18493
+ * `'never'` is a literal rather than `undefined`/`0` because omitting
18494
+ * this field already means "the 5-minute SSO hand-off default", and
18495
+ * `jwt.sign` THROWS on `{ expiresIn: undefined }` — a "no expiry" that
18496
+ * went through the numeric path would fail at mint time and break
18497
+ * linking rather than produce an eternal token.
18498
+ */
18499
+ ttlSec: union([number().int().positive(), literal("never")]).optional()
18342
18500
  }), object({ token: string() })), method(object({ token: string() }), SsoBridgeClaimsSchema.nullable());
18343
18501
  var ProviderListEntrySchema = discriminatedUnion("shouldSaveDiskSpace", [object({
18344
18502
  providerId: string().min(1),
@@ -25551,7 +25709,12 @@ method(_void(), array(UserSummarySchema), { auth: "admin" }), method(CreateUserI
25551
25709
  hubUrl: string(),
25552
25710
  /** PKCE (RFC 7636) S256 challenge. Baked into the signed code; a code
25553
25711
  * that carries one can ONLY be exchanged with the matching verifier. */
25554
- codeChallenge: string().optional()
25712
+ codeChallenge: string().optional(),
25713
+ /** The integration's declared refresh lifetime — seconds, or `'never'`.
25714
+ * From `OauthIntegrationDescriptor.refreshTokenTtlSec`. Baked into the
25715
+ * code so the link carries its own lifetime; omit for the 30-day
25716
+ * default. */
25717
+ refreshTtlSec: union([number().int().positive(), literal("never")]).optional()
25555
25718
  }), object({ code: string() }), {
25556
25719
  kind: "mutation",
25557
25720
  access: "create"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-rtsp",
3
- "version": "1.2.10",
3
+ "version": "1.2.11",
4
4
  "description": "Generic RTSP camera device provider addon for CamStack",
5
5
  "keywords": [
6
6
  "camstack",