@camstack/types 1.2.68 → 1.2.70
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 +1 -1
- package/dist/addon.mjs +1 -1
- package/dist/capabilities/intercom.cap.d.ts +46 -0
- package/dist/capabilities/notification-rules.cap.d.ts +10 -0
- package/dist/capabilities/oauth-integration.cap.d.ts +30 -6
- package/dist/capabilities/pipeline-analytics.cap.d.ts +25 -0
- package/dist/capabilities/sso-bridge.cap.d.ts +45 -9
- package/dist/capabilities/user-management.cap.d.ts +300 -60
- package/dist/generated/addon-api.d.ts +126 -0
- package/dist/generated/device-local-state.d.ts +3 -0
- package/dist/generated/device-proxy.d.ts +1 -0
- package/dist/generated/method-device-selectors.d.ts +27 -0
- package/dist/generated/runtime-state-durability.d.ts +1 -1
- package/dist/generated/scope-presets.d.ts +1 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.js +2310 -37
- package/dist/index.mjs +2299 -38
- package/dist/schemas/access-roles.d.ts +92 -0
- package/dist/schemas/auth-records.d.ts +104 -9
- package/dist/schemas/device-selector.d.ts +88 -0
- package/dist/{sleep-CSgK0rNX.mjs → sleep-CwERnGrD.mjs} +1 -0
- package/dist/{sleep-Dh1EJSqF.js → sleep-YuacOD41.js} +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The two questions the operator-facing Access panel asks, as pure logic.
|
|
3
|
+
*
|
|
4
|
+
* 1. WHAT can this user do? → an {@link AccessRoleId}
|
|
5
|
+
* 2. On WHICH devices? → a {@link DeviceSelector} + `includeLinked`
|
|
6
|
+
*
|
|
7
|
+
* and the composition of those two answers into the `TokenScope[]` the v3
|
|
8
|
+
* enforcement matcher actually reads.
|
|
9
|
+
*
|
|
10
|
+
* Why this is not a {@link SCOPE_PRESETS} entry: a preset is a frozen list of
|
|
11
|
+
* rows an operator drops into the raw picker. A ROLE is only half a grant set
|
|
12
|
+
* — its device row carries the selector chosen in question 2 — so it cannot be
|
|
13
|
+
* pre-baked. The two coexist: `SCOPE_PRESETS` fills the Advanced picker,
|
|
14
|
+
* `ACCESS_ROLES` drives the simple panel.
|
|
15
|
+
*
|
|
16
|
+
* The one rule that makes the pairing work: a role NEVER emits a
|
|
17
|
+
* `category:device` row. That row is unrestricted device reach
|
|
18
|
+
* (`hasUnrestrictedDeviceReach` in `device-selector.ts` / `scope-access.ts`)
|
|
19
|
+
* and would silently discard the operator's device choice — the panel would
|
|
20
|
+
* show "3 cameras" over a grant that reaches all of them. The device row is
|
|
21
|
+
* always `{type:'device', selector}` so question 2 is load-bearing; a
|
|
22
|
+
* selector of `{kind:'all'}` is the deliberate way to say "everything", and
|
|
23
|
+
* the matcher treats it as the same unrestricted reach.
|
|
24
|
+
*
|
|
25
|
+
* The `category:system` row is what makes a role USEFUL: recordings, the
|
|
26
|
+
* events feed, faces and playback manifests live on system-scope caps, so a
|
|
27
|
+
* device grant alone reaches almost nothing a viewer wants. It is safe at
|
|
28
|
+
* these tiers because (a) admin-tier methods are refused by the auth gate
|
|
29
|
+
* regardless of scope, and (b) every system-scope method that NAMES a device
|
|
30
|
+
* is cut back to the role's own device set by the F1 #3 device-reference gate.
|
|
31
|
+
*
|
|
32
|
+
* Lives in `@camstack/types` next to `device-selector.ts` for the same reason
|
|
33
|
+
* that file does: the admin UI composes these and the backend resolves them,
|
|
34
|
+
* and a hand-copied second derivation drifts (D103).
|
|
35
|
+
*/
|
|
36
|
+
import type { DeviceSelector, MethodAccess, TokenScope } from './auth-records.js';
|
|
37
|
+
/** The three answers to "what can this user do". */
|
|
38
|
+
export type AccessRoleId = 'viewer' | 'operator' | 'manager';
|
|
39
|
+
export interface AccessRoleSpec {
|
|
40
|
+
readonly id: AccessRoleId;
|
|
41
|
+
/** Radio-button label. */
|
|
42
|
+
readonly label: string;
|
|
43
|
+
/** One sentence saying what the role can do — shown under the label. */
|
|
44
|
+
readonly description: string;
|
|
45
|
+
/** Access flavours carried by the role's `device` grant. */
|
|
46
|
+
readonly deviceAccess: readonly MethodAccess[];
|
|
47
|
+
/** Access flavours carried by the role's `category:system` grant. */
|
|
48
|
+
readonly systemAccess: readonly MethodAccess[];
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Ordered widest-last — the panel renders them top to bottom and the operator
|
|
52
|
+
* reads the list as an escalation.
|
|
53
|
+
*/
|
|
54
|
+
export declare const ACCESS_ROLES: readonly AccessRoleSpec[];
|
|
55
|
+
/** Look a role up by id. Throws on an unknown id — the union makes that
|
|
56
|
+
* unreachable from typed callers, and a silent fallback would grant the
|
|
57
|
+
* wrong thing. */
|
|
58
|
+
export declare function roleSpec(id: AccessRoleId): AccessRoleSpec;
|
|
59
|
+
/** A complete answer to both panel questions. */
|
|
60
|
+
export interface AccessRoleAssignment {
|
|
61
|
+
readonly roleId: AccessRoleId;
|
|
62
|
+
readonly selector: DeviceSelector;
|
|
63
|
+
/**
|
|
64
|
+
* Whether a grant on a parent device covers its accessory children.
|
|
65
|
+
* ALWAYS written explicitly — see {@link buildRoleScopes}.
|
|
66
|
+
*/
|
|
67
|
+
readonly includeLinked: boolean;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Compose an assignment into the grants the matcher reads.
|
|
71
|
+
*
|
|
72
|
+
* `includeLinked` is written explicitly in BOTH directions on purpose. Absent,
|
|
73
|
+
* the matcher derives it from the access flavour (`view` inherits, `create` /
|
|
74
|
+
* `delete` do not), so an omitted `false` would silently re-grant the
|
|
75
|
+
* accessories the operator just excluded, and the panel would be lying about
|
|
76
|
+
* its own toggle.
|
|
77
|
+
*/
|
|
78
|
+
export declare function buildRoleScopes(assignment: AccessRoleAssignment): TokenScope[];
|
|
79
|
+
/**
|
|
80
|
+
* Recover the assignment behind a grant set, or `null` when the grants are
|
|
81
|
+
* not something this panel authored.
|
|
82
|
+
*
|
|
83
|
+
* `null` is not an error — it is the "Advanced" case, and the panel MUST show
|
|
84
|
+
* it as such rather than defaulting the radio to a role the user does not
|
|
85
|
+
* have. Silently rendering an unrecognised grant set as "View" would make the
|
|
86
|
+
* next save a downgrade nobody asked for.
|
|
87
|
+
*
|
|
88
|
+
* Strict by design: exactly the two rows `buildRoleScopes` writes, an
|
|
89
|
+
* explicit `includeLinked`, and device/system access that agree on a single
|
|
90
|
+
* role. Anything else belongs in the raw editor.
|
|
91
|
+
*/
|
|
92
|
+
export declare function detectAccessRole(scopes: readonly TokenScope[]): AccessRoleAssignment | null;
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
* keeps the original name for backward compatibility.
|
|
11
11
|
*/
|
|
12
12
|
import { z } from 'zod';
|
|
13
|
+
import { DeviceType } from '../device/device-type.js';
|
|
13
14
|
/**
|
|
14
15
|
* Auth model (v2 — 2026-05-12 caps-only):
|
|
15
16
|
*
|
|
@@ -37,7 +38,62 @@ export declare const CapScopeSchema: z.ZodEnum<{
|
|
|
37
38
|
device: "device";
|
|
38
39
|
}>;
|
|
39
40
|
export type CapScope = z.infer<typeof CapScopeSchema>;
|
|
40
|
-
|
|
41
|
+
/**
|
|
42
|
+
* DeviceSelector (scope model v3 — 2026-08-12).
|
|
43
|
+
*
|
|
44
|
+
* A `device` grant no longer carries a frozen list of deviceIds. It carries
|
|
45
|
+
* a SELECTOR the matcher resolves against the live fleet, so the grant can be
|
|
46
|
+
* DYNAMIC: a `types:['camera']` selector automatically covers a camera added
|
|
47
|
+
* AFTER the grant was minted — no re-grant, no re-login.
|
|
48
|
+
*
|
|
49
|
+
* - `all` — every device in the deployment. The broad viewer/operator
|
|
50
|
+
* lever without a `category` grant (a `category` grant also covers device
|
|
51
|
+
* caps that carry no deviceId; `all` is specifically the device set).
|
|
52
|
+
* - `ids` — an explicit deviceId list. This is what a v2 `device:[…]`
|
|
53
|
+
* grant migrates to (see {@link TokenScopeSchema}); STATIC — a new camera
|
|
54
|
+
* is NOT covered until the grant is edited.
|
|
55
|
+
* - `types` — every device of a `DeviceType` (e.g. every `camera`).
|
|
56
|
+
* DYNAMIC. A device that changes type, or a new device of the type,
|
|
57
|
+
* re-resolves on the next request.
|
|
58
|
+
* - `locations` — every device whose operator-assigned `location` label is
|
|
59
|
+
* in the set (e.g. "Garden", "Front door"). DYNAMIC. A device with a
|
|
60
|
+
* null/unset location matches NO `locations` selector.
|
|
61
|
+
*/
|
|
62
|
+
export declare const DeviceSelectorSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
63
|
+
kind: z.ZodLiteral<"all">;
|
|
64
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
65
|
+
kind: z.ZodLiteral<"ids">;
|
|
66
|
+
ids: z.ZodArray<z.ZodNumber>;
|
|
67
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
68
|
+
kind: z.ZodLiteral<"types">;
|
|
69
|
+
types: z.ZodArray<z.ZodEnum<typeof DeviceType>>;
|
|
70
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
71
|
+
kind: z.ZodLiteral<"locations">;
|
|
72
|
+
locations: z.ZodArray<z.ZodString>;
|
|
73
|
+
}, z.core.$strip>], "kind">;
|
|
74
|
+
export type DeviceSelector = z.infer<typeof DeviceSelectorSchema>;
|
|
75
|
+
declare const DeviceTokenScopeSchema: z.ZodObject<{
|
|
76
|
+
type: z.ZodLiteral<"device">;
|
|
77
|
+
selector: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
78
|
+
kind: z.ZodLiteral<"all">;
|
|
79
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
80
|
+
kind: z.ZodLiteral<"ids">;
|
|
81
|
+
ids: z.ZodArray<z.ZodNumber>;
|
|
82
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
83
|
+
kind: z.ZodLiteral<"types">;
|
|
84
|
+
types: z.ZodArray<z.ZodEnum<typeof DeviceType>>;
|
|
85
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
86
|
+
kind: z.ZodLiteral<"locations">;
|
|
87
|
+
locations: z.ZodArray<z.ZodString>;
|
|
88
|
+
}, z.core.$strip>], "kind">;
|
|
89
|
+
access: z.ZodArray<z.ZodEnum<{
|
|
90
|
+
view: "view";
|
|
91
|
+
create: "create";
|
|
92
|
+
delete: "delete";
|
|
93
|
+
}>>;
|
|
94
|
+
includeLinked: z.ZodOptional<z.ZodBoolean>;
|
|
95
|
+
}, z.core.$strip>;
|
|
96
|
+
export declare const TokenScopeSchema: z.ZodPreprocess<z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
41
97
|
type: z.ZodLiteral<"category">;
|
|
42
98
|
target: z.ZodEnum<{
|
|
43
99
|
system: "system";
|
|
@@ -66,14 +122,28 @@ export declare const TokenScopeSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
66
122
|
}>>;
|
|
67
123
|
}, z.core.$strip>, z.ZodObject<{
|
|
68
124
|
type: z.ZodLiteral<"device">;
|
|
69
|
-
|
|
125
|
+
selector: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
126
|
+
kind: z.ZodLiteral<"all">;
|
|
127
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
128
|
+
kind: z.ZodLiteral<"ids">;
|
|
129
|
+
ids: z.ZodArray<z.ZodNumber>;
|
|
130
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
131
|
+
kind: z.ZodLiteral<"types">;
|
|
132
|
+
types: z.ZodArray<z.ZodEnum<typeof DeviceType>>;
|
|
133
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
134
|
+
kind: z.ZodLiteral<"locations">;
|
|
135
|
+
locations: z.ZodArray<z.ZodString>;
|
|
136
|
+
}, z.core.$strip>], "kind">;
|
|
70
137
|
access: z.ZodArray<z.ZodEnum<{
|
|
71
138
|
view: "view";
|
|
72
139
|
create: "create";
|
|
73
140
|
delete: "delete";
|
|
74
141
|
}>>;
|
|
75
|
-
|
|
142
|
+
includeLinked: z.ZodOptional<z.ZodBoolean>;
|
|
143
|
+
}, z.core.$strip>], "type">>;
|
|
76
144
|
export type TokenScope = z.infer<typeof TokenScopeSchema>;
|
|
145
|
+
/** The `device`-typed member of {@link TokenScope}, narrowed for matchers. */
|
|
146
|
+
export type DeviceTokenScope = z.infer<typeof DeviceTokenScopeSchema>;
|
|
77
147
|
export declare const UserRecordSchema: z.ZodObject<{
|
|
78
148
|
id: z.ZodString;
|
|
79
149
|
username: z.ZodString;
|
|
@@ -81,7 +151,7 @@ export declare const UserRecordSchema: z.ZodObject<{
|
|
|
81
151
|
isAdmin: z.ZodDefault<z.ZodBoolean>;
|
|
82
152
|
allowedProviders: z.ZodUnion<readonly [z.ZodLiteral<"*">, z.ZodArray<z.ZodString>]>;
|
|
83
153
|
allowedDevices: z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodLiteral<"*">, z.ZodArray<z.ZodString>]>>;
|
|
84
|
-
scopes: z.ZodDefault<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
154
|
+
scopes: z.ZodDefault<z.ZodArray<z.ZodPreprocess<z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
85
155
|
type: z.ZodLiteral<"category">;
|
|
86
156
|
target: z.ZodEnum<{
|
|
87
157
|
system: "system";
|
|
@@ -110,13 +180,25 @@ export declare const UserRecordSchema: z.ZodObject<{
|
|
|
110
180
|
}>>;
|
|
111
181
|
}, z.core.$strip>, z.ZodObject<{
|
|
112
182
|
type: z.ZodLiteral<"device">;
|
|
113
|
-
|
|
183
|
+
selector: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
184
|
+
kind: z.ZodLiteral<"all">;
|
|
185
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
186
|
+
kind: z.ZodLiteral<"ids">;
|
|
187
|
+
ids: z.ZodArray<z.ZodNumber>;
|
|
188
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
189
|
+
kind: z.ZodLiteral<"types">;
|
|
190
|
+
types: z.ZodArray<z.ZodEnum<typeof DeviceType>>;
|
|
191
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
192
|
+
kind: z.ZodLiteral<"locations">;
|
|
193
|
+
locations: z.ZodArray<z.ZodString>;
|
|
194
|
+
}, z.core.$strip>], "kind">;
|
|
114
195
|
access: z.ZodArray<z.ZodEnum<{
|
|
115
196
|
view: "view";
|
|
116
197
|
create: "create";
|
|
117
198
|
delete: "delete";
|
|
118
199
|
}>>;
|
|
119
|
-
|
|
200
|
+
includeLinked: z.ZodOptional<z.ZodBoolean>;
|
|
201
|
+
}, z.core.$strip>], "type">>>>;
|
|
120
202
|
createdAt: z.ZodNumber;
|
|
121
203
|
updatedAt: z.ZodNumber;
|
|
122
204
|
}, z.core.$strip>;
|
|
@@ -139,7 +221,7 @@ export declare const ScopedTokenSchema: z.ZodObject<{
|
|
|
139
221
|
name: z.ZodString;
|
|
140
222
|
tokenHash: z.ZodString;
|
|
141
223
|
tokenPrefix: z.ZodString;
|
|
142
|
-
scopes: z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
224
|
+
scopes: z.ZodArray<z.ZodPreprocess<z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
143
225
|
type: z.ZodLiteral<"category">;
|
|
144
226
|
target: z.ZodEnum<{
|
|
145
227
|
system: "system";
|
|
@@ -168,15 +250,28 @@ export declare const ScopedTokenSchema: z.ZodObject<{
|
|
|
168
250
|
}>>;
|
|
169
251
|
}, z.core.$strip>, z.ZodObject<{
|
|
170
252
|
type: z.ZodLiteral<"device">;
|
|
171
|
-
|
|
253
|
+
selector: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
254
|
+
kind: z.ZodLiteral<"all">;
|
|
255
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
256
|
+
kind: z.ZodLiteral<"ids">;
|
|
257
|
+
ids: z.ZodArray<z.ZodNumber>;
|
|
258
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
259
|
+
kind: z.ZodLiteral<"types">;
|
|
260
|
+
types: z.ZodArray<z.ZodEnum<typeof DeviceType>>;
|
|
261
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
262
|
+
kind: z.ZodLiteral<"locations">;
|
|
263
|
+
locations: z.ZodArray<z.ZodString>;
|
|
264
|
+
}, z.core.$strip>], "kind">;
|
|
172
265
|
access: z.ZodArray<z.ZodEnum<{
|
|
173
266
|
view: "view";
|
|
174
267
|
create: "create";
|
|
175
268
|
delete: "delete";
|
|
176
269
|
}>>;
|
|
177
|
-
|
|
270
|
+
includeLinked: z.ZodOptional<z.ZodBoolean>;
|
|
271
|
+
}, z.core.$strip>], "type">>>;
|
|
178
272
|
expiresAt: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
179
273
|
lastUsedAt: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
180
274
|
createdAt: z.ZodNumber;
|
|
181
275
|
}, z.core.$strip>;
|
|
182
276
|
export type ScopedToken = z.infer<typeof ScopedTokenSchema>;
|
|
277
|
+
export {};
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure DeviceSelector semantics — scope model v3 (2026-08-12).
|
|
3
|
+
*
|
|
4
|
+
* One derivation of "does this grant cover this device", reused by:
|
|
5
|
+
* - the enforcement matcher (`server/backend/.../scope-access.ts`), per
|
|
6
|
+
* request, against a single deviceId + its ancestors;
|
|
7
|
+
* - response projection + `auth.me`, against the WHOLE fleet, to compute
|
|
8
|
+
* the viewable device set and the effective-scope summary the UI reads.
|
|
9
|
+
*
|
|
10
|
+
* Lives in `@camstack/types` (framework, train-bound) because BOTH the
|
|
11
|
+
* backend enforcement path and any addon/UI need the same rule — the F0.x
|
|
12
|
+
* lesson that a hand-copied scope check drifts (D103).
|
|
13
|
+
*
|
|
14
|
+
* No I/O, no registry — the fleet + parentage are passed in.
|
|
15
|
+
*/
|
|
16
|
+
import type { DeviceSelector, DeviceTokenScope, MethodAccess, TokenScope } from './auth-records.js';
|
|
17
|
+
/** The minimal device shape selectors resolve against. */
|
|
18
|
+
export interface DeviceMetaLike {
|
|
19
|
+
readonly id: number;
|
|
20
|
+
/** `DeviceType` string value (e.g. `'camera'`). */
|
|
21
|
+
readonly type: string;
|
|
22
|
+
/** Operator-assigned location label; `null` when unset. */
|
|
23
|
+
readonly location: string | null;
|
|
24
|
+
readonly parentDeviceId: number | null;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Does `selector` cover `device` DIRECTLY (no inheritance)? Inheritance is
|
|
28
|
+
* layered on by the resolver/matcher, which tests this against a device's
|
|
29
|
+
* ancestors too.
|
|
30
|
+
*/
|
|
31
|
+
export declare function deviceSelectorMatches(selector: DeviceSelector, device: DeviceMetaLike): boolean;
|
|
32
|
+
/** Whether inheritance applies for a grant at `access` (view inherits, others
|
|
33
|
+
* don't, unless the grant overrides via `includeLinked`). */
|
|
34
|
+
export declare function scopeInherits(scope: DeviceTokenScope, access: MethodAccess): boolean;
|
|
35
|
+
/**
|
|
36
|
+
* Resolve the set of deviceIds the caller may reach at `access`, expanded
|
|
37
|
+
* against the live `fleet`. A `category:device` grant returns the whole
|
|
38
|
+
* fleet. Device selectors are matched per device — directly, or (when the
|
|
39
|
+
* grant inherits) against an ancestor, so a parent grant covers its
|
|
40
|
+
* accessories.
|
|
41
|
+
*/
|
|
42
|
+
export declare function resolveViewableDeviceIds(scopes: readonly TokenScope[], fleet: readonly DeviceMetaLike[], access: MethodAccess): Set<number>;
|
|
43
|
+
/**
|
|
44
|
+
* Normalise a raw scope array read off a JWT payload or a stored record into
|
|
45
|
+
* validated v3 `TokenScope`s, migrating any v2 `device:targets` grant on the
|
|
46
|
+
* way (via {@link TokenScopeSchema}'s preprocess). Applied at the request
|
|
47
|
+
* boundary (`resolveUser`) so the enforcement matcher only ever sees v3.
|
|
48
|
+
*
|
|
49
|
+
* Per-ITEM parse (not whole-array) so a single malformed scope is dropped
|
|
50
|
+
* without discarding the caller's valid grants — losing a good grant reads as
|
|
51
|
+
* a lockout, and a lockout reads as broken. A non-array input yields `[]`
|
|
52
|
+
* (fail closed).
|
|
53
|
+
*/
|
|
54
|
+
export declare function normalizeTokenScopes(raw: unknown): TokenScope[];
|
|
55
|
+
/** A capability/addon grant flattened for the UI. */
|
|
56
|
+
export interface ScopeGrantSummary {
|
|
57
|
+
readonly name: string;
|
|
58
|
+
readonly access: readonly MethodAccess[];
|
|
59
|
+
}
|
|
60
|
+
/** Per-access device reach — a COUNT and the "everything" flag, never the id
|
|
61
|
+
* list (the UI hides surfaces from counts; it never needs the ids). */
|
|
62
|
+
export interface DeviceReachSummary {
|
|
63
|
+
readonly all: boolean;
|
|
64
|
+
readonly deviceCount: number;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* The resolved, effective scope the caller actually has — what `auth.me`
|
|
68
|
+
* returns so the viewer (F3/F4) can HIDE out-of-scope surfaces. Selectors are
|
|
69
|
+
* expanded to counts/flags against the live fleet, never to raw id lists.
|
|
70
|
+
*/
|
|
71
|
+
export interface EffectiveScope {
|
|
72
|
+
readonly isAdmin: boolean;
|
|
73
|
+
/** true iff every device is viewable (admin, `category:device[view]`, or a
|
|
74
|
+
* device grant with an `all` selector). */
|
|
75
|
+
readonly allDevicesViewable: boolean;
|
|
76
|
+
/** How many devices the caller may VIEW right now. */
|
|
77
|
+
readonly viewableDeviceCount: number;
|
|
78
|
+
/** Device reach per access flavour. */
|
|
79
|
+
readonly device: Readonly<Record<MethodAccess, DeviceReachSummary>>;
|
|
80
|
+
/** `category:system` access flavours held. */
|
|
81
|
+
readonly system: readonly MethodAccess[];
|
|
82
|
+
/** `capability:<name>` grants. */
|
|
83
|
+
readonly capabilities: readonly ScopeGrantSummary[];
|
|
84
|
+
/** `addon:<id>` grants. */
|
|
85
|
+
readonly addons: readonly ScopeGrantSummary[];
|
|
86
|
+
}
|
|
87
|
+
/** Build the {@link EffectiveScope} for a caller from their scopes + the fleet. */
|
|
88
|
+
export declare function summarizeEffectiveScope(scopes: readonly TokenScope[], fleet: readonly DeviceMetaLike[], isAdmin: boolean): EffectiveScope;
|
|
@@ -2901,6 +2901,7 @@ function createDeviceProxy(api, binding, opts) {
|
|
|
2901
2901
|
humiditySensor: createSliceHandle(stateSource, binding.deviceId, "humidity-sensor"),
|
|
2902
2902
|
image: createSliceHandle(stateSource, binding.deviceId, "image"),
|
|
2903
2903
|
imageSettings: createSliceHandle(stateSource, binding.deviceId, "image-settings"),
|
|
2904
|
+
intercom: createSliceHandle(stateSource, binding.deviceId, "intercom"),
|
|
2904
2905
|
lawnMowerControl: createSliceHandle(stateSource, binding.deviceId, "lawn-mower-control"),
|
|
2905
2906
|
lockControl: createSliceHandle(stateSource, binding.deviceId, "lock-control"),
|
|
2906
2907
|
mediaPlayer: createSliceHandle(stateSource, binding.deviceId, "media-player"),
|
|
@@ -2901,6 +2901,7 @@ function createDeviceProxy(api, binding, opts) {
|
|
|
2901
2901
|
humiditySensor: createSliceHandle(stateSource, binding.deviceId, "humidity-sensor"),
|
|
2902
2902
|
image: createSliceHandle(stateSource, binding.deviceId, "image"),
|
|
2903
2903
|
imageSettings: createSliceHandle(stateSource, binding.deviceId, "image-settings"),
|
|
2904
|
+
intercom: createSliceHandle(stateSource, binding.deviceId, "intercom"),
|
|
2904
2905
|
lawnMowerControl: createSliceHandle(stateSource, binding.deviceId, "lawn-mower-control"),
|
|
2905
2906
|
lockControl: createSliceHandle(stateSource, binding.deviceId, "lock-control"),
|
|
2906
2907
|
mediaPlayer: createSliceHandle(stateSource, binding.deviceId, "media-player"),
|