@foir/sdk 0.19.0 → 0.21.0

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 (42) hide show
  1. package/CHANGELOG.md +330 -0
  2. package/dist/app/client.d.ts +109 -21
  3. package/dist/app/contract/proto/apps/v1/apps_pb.d.ts +223 -211
  4. package/dist/app/errors.d.ts +2 -2
  5. package/dist/app/index.d.ts +4 -4
  6. package/dist/app/manifest.d.ts +1 -1
  7. package/dist/app/vocabulary.generated.d.ts +13 -10
  8. package/dist/app-contract.js +27 -31
  9. package/dist/app.js +80 -23
  10. package/dist/auth/access.d.ts +106 -0
  11. package/dist/auth/grant-table.generated.d.ts +13 -0
  12. package/dist/auth/grants.d.ts +38 -0
  13. package/dist/auth/index.d.ts +2 -0
  14. package/dist/auth/operations.d.ts +6 -1
  15. package/dist/auth/ops.d.ts +1 -0
  16. package/dist/auth.js +321 -7
  17. package/dist/client/platform-client.d.ts +1 -1
  18. package/dist/client.js +28 -1
  19. package/dist/codegen/index.js +1 -1
  20. package/dist/data/builtins.d.ts +57 -42
  21. package/dist/data/document.d.ts +14 -0
  22. package/dist/data/index.d.ts +2 -2
  23. package/dist/data/transport.d.ts +27 -20
  24. package/dist/data.js +91 -13
  25. package/dist/index.js +24 -0
  26. package/dist/next.js +47 -5
  27. package/dist/placement/index.d.ts +26 -11
  28. package/dist/placement.js +219 -2
  29. package/dist/push.js +55 -6
  30. package/dist/react/FoirProvider.d.ts +40 -1
  31. package/dist/react/feedback-notes.d.ts +1 -1
  32. package/dist/react/index.d.ts +5 -4
  33. package/dist/react.js +391 -8
  34. package/dist/server/events-client.d.ts +7 -7
  35. package/dist/server/index.d.ts +1 -1
  36. package/dist/server/require-scoped-token.d.ts +1 -1
  37. package/dist/server/server-client.d.ts +1 -1
  38. package/dist/server/verify-scoped-token.d.ts +18 -23
  39. package/dist/server.js +228 -14
  40. package/dist/transport/foir-http.d.ts +44 -5
  41. package/dist/transport/key-kind.d.ts +40 -0
  42. package/package.json +6 -5
package/dist/placement.js CHANGED
@@ -1,3 +1,197 @@
1
+ // src/auth/grant-table.generated.ts
2
+ var GRANT_TABLE = [
3
+ ["records:read", true, ["own", "all"], []],
4
+ ["records:create", true, [], []],
5
+ ["records:write", true, ["own", "all"], []],
6
+ ["records:delete", true, ["own", "all"], []],
7
+ ["records:publish", true, ["own", "all"], []],
8
+ ["records:attribute", true, [], []],
9
+ ["search:read", true, [], []],
10
+ ["embeddings:read", false, [], []],
11
+ ["embeddings:write", false, [], []],
12
+ ["schemas:read", false, [], []],
13
+ ["drafts:read", false, [], []],
14
+ ["files:read", false, ["own", "all"], []],
15
+ ["files:write", false, ["own", "all"], []],
16
+ ["files:delete", false, ["own", "all"], []],
17
+ ["notifications:read", false, ["own", "all"], []],
18
+ ["notifications:write", false, ["own", "named", "all"], []],
19
+ ["notifications:consent", false, [], []],
20
+ ["pushcredentials:read", false, [], []],
21
+ ["pushcredentials:write", false, [], []],
22
+ ["notes:read", false, ["own", "all"], []],
23
+ ["notes:write", false, ["own", "all"], []],
24
+ ["notes:create", false, [], []],
25
+ ["approvals:read", false, [], []],
26
+ ["approvals:write", false, [], []],
27
+ ["rooms:read", true, [], []],
28
+ ["rooms:write", true, [], []],
29
+ ["presence:read", true, [], []],
30
+ ["operations:read", false, [], []],
31
+ ["operations:execute", true, [], []],
32
+ ["operations:write", false, [], []],
33
+ ["operations:complete", false, [], []],
34
+ ["status:write", false, [], []],
35
+ ["appconfig:read", false, [], []],
36
+ ["schedules:read", false, [], []],
37
+ ["schedules:write", false, [], []],
38
+ ["hooks:read", false, [], []],
39
+ ["hooks:write", false, [], []],
40
+ ["relyingparties:read", false, [], []],
41
+ ["relyingparties:write", false, [], []],
42
+ ["domains:read", false, [], []],
43
+ ["domains:write", false, [], []],
44
+ ["secrets:read", false, [], []],
45
+ ["secrets:list", false, [], []],
46
+ ["secrets:put", false, [], []],
47
+ ["secrets:delete", false, [], []],
48
+ ["appsecrets:read", false, [], []],
49
+ ["appsecrets:list", false, [], []],
50
+ ["appsecrets:put", false, [], []],
51
+ ["appsecrets:delete", false, [], []],
52
+ ["opsecrets:read", false, [], []],
53
+ ["opsecrets:write", false, [], []],
54
+ ["settings:read", false, [], []],
55
+ ["settings:write", false, [], []],
56
+ ["designtokens:read", false, [], []],
57
+ ["designtokens:write", false, [], []],
58
+ ["models:read", false, [], []],
59
+ ["models:write", false, [], []],
60
+ ["configs:read", false, [], []],
61
+ ["configs:write", false, [], []],
62
+ ["apps:read", false, [], []],
63
+ ["apps:write", false, [], []],
64
+ ["events:read", false, [], []],
65
+ ["targeting:debug", false, [], []],
66
+ ["visitors:forward", false, [], []],
67
+ ["apikeys:read", false, [], []],
68
+ ["apikeys:write", false, [], []],
69
+ ["organizations:read", false, [], []],
70
+ ["organizations:write", false, [], []],
71
+ ["organizations:create", false, [], []],
72
+ ["organizations:delete", false, [], []],
73
+ ["organizations:reach", false, [], []],
74
+ ["members:read", false, [], []],
75
+ ["members:write", false, [], []],
76
+ ["roles:read", false, [], []],
77
+ ["roles:write", false, [], []],
78
+ ["projects:read", false, [], []],
79
+ ["projects:write", false, [], []],
80
+ ["project:manage", false, [], ["records:read.all", "records:create", "records:write.all", "records:delete.all", "records:publish.all", "records:attribute", "search:read", "embeddings:read", "embeddings:write", "schemas:read", "drafts:read", "files:read.all", "files:write.all", "files:delete.all", "users:read.all", "identities:read", "users:create", "identities:delete", "users:write.all", "users:delete.all", "notifications:read.all", "notifications:write.all", "notifications:consent", "pushcredentials:read", "pushcredentials:write", "notes:read.all", "notes:write.all", "notes:create", "approvals:read", "approvals:write", "rooms:read", "rooms:write", "presence:read", "operations:read", "operations:execute", "operations:write", "appconfig:read", "schedules:read", "schedules:write", "hooks:read", "hooks:write", "auth:login_with_foir", "relyingparties:read", "relyingparties:write", "domains:read", "domains:write", "secrets:read", "secrets:list", "secrets:put", "secrets:delete", "appsecrets:read", "appsecrets:list", "appsecrets:put", "appsecrets:delete", "usersecrets:read", "usersecrets:list", "usersecrets:put", "usersecrets:delete", "opsecrets:read", "opsecrets:write", "settings:read", "settings:write", "designtokens:read", "designtokens:write", "models:read", "models:write", "configs:read", "configs:write", "signinproviders:read", "signinproviders:write", "apps:read", "apps:write", "events:read", "targeting:debug", "visitors:forward", "apikeys:read", "apikeys:write", "organizations:read", "organizations:create", "organizations:delete", "organizations:write", "organizations:reach", "members:read", "members:write", "roles:read", "roles:write", "projects:read", "projects:write", "audit:read"]],
81
+ ["projects:create", false, [], []],
82
+ ["projects:delete", false, [], []],
83
+ ["tenants:read", false, [], []],
84
+ ["tenants:write", false, [], []],
85
+ ["tenants:delete", false, [], []],
86
+ ["audit:read", false, [], []],
87
+ ["export:run", false, [], []],
88
+ ["billing:read", false, [], []],
89
+ ["billing:manage", false, [], []],
90
+ ["usage:read", false, [], []],
91
+ ["controlkeys:read", false, [], []],
92
+ ["controlkeys:write", false, [], []],
93
+ ["tenanthooks:read", false, [], []],
94
+ ["tenanthooks:write", false, [], []],
95
+ ["apps:install", false, [], []],
96
+ ["tenant:manage", false, [], ["project:manage", "projects:create", "projects:delete", "tenants:read", "tenants:write", "tenants:delete", "export:run", "billing:read", "billing:manage", "usage:read", "controlkeys:read", "controlkeys:write", "tenanthooks:read", "tenanthooks:write", "apps:install"]],
97
+ ["platform:read", false, [], []],
98
+ ["platform:manage", false, [], ["tenant:manage", "platform:read", "platform:create_workspace", "users:setup"]],
99
+ ["platform:create_workspace", false, [], []],
100
+ ["users:read", false, ["own", "named", "all"], []],
101
+ ["users:write", false, ["own", "all"], []],
102
+ ["users:setup", false, [], []],
103
+ ["users:delete", false, ["own", "all"], []],
104
+ ["users:create", false, [], []],
105
+ ["identities:read", false, [], []],
106
+ ["identities:delete", false, [], []],
107
+ ["usersecrets:read", false, [], []],
108
+ ["usersecrets:list", false, [], []],
109
+ ["usersecrets:put", false, [], []],
110
+ ["usersecrets:delete", false, [], []],
111
+ ["auth:login_with_foir", false, [], []],
112
+ ["signinproviders:read", false, [], []],
113
+ ["signinproviders:write", false, [], []]
114
+ ];
115
+
116
+ // src/auth/grants.ts
117
+ var WILDCARD = "*";
118
+ var RESOURCE_WILDCARD = ":*";
119
+ var index;
120
+ function catalogue() {
121
+ if (!index) {
122
+ const byName = new Map(GRANT_TABLE.map((row) => [row[0], row]));
123
+ const longestFirst = [...byName.keys()].sort(
124
+ (a, b) => a.length !== b.length ? b.length - a.length : a < b ? -1 : 1
125
+ );
126
+ index = { byName, longestFirst };
127
+ }
128
+ return index;
129
+ }
130
+ var reachRank = { own: 1, named: 2, all: 3 };
131
+ function hasReach(permission, reach) {
132
+ const reaches = permission[2];
133
+ return reaches.length === 0 ? reach === "own" : reaches.includes(reach);
134
+ }
135
+ function resourceOf(name) {
136
+ const i = name.indexOf(":");
137
+ return i < 0 ? name : name.slice(0, i);
138
+ }
139
+ function parseGrant(s) {
140
+ const { byName, longestFirst } = catalogue();
141
+ for (const name of longestFirst) {
142
+ if (!s.startsWith(name)) continue;
143
+ let rest = s.slice(name.length);
144
+ if (rest !== "" && rest[0] !== "." && rest[0] !== ":") continue;
145
+ const permission = byName.get(name);
146
+ let reach = "own";
147
+ if (rest.startsWith(".")) {
148
+ const after = rest.slice(1);
149
+ const colon = after.indexOf(":");
150
+ const word = colon < 0 ? after : after.slice(0, colon);
151
+ if (word !== "named" && word !== "all") return null;
152
+ if (!hasReach(permission, word)) return null;
153
+ reach = word;
154
+ rest = after.slice(word.length);
155
+ }
156
+ let target = "";
157
+ if (rest.startsWith(":")) {
158
+ target = rest.slice(1);
159
+ if (!permission[1]) return null;
160
+ if (target === "" || /[: \t\n*]/.test(target)) return null;
161
+ }
162
+ return { permission, reach, target };
163
+ }
164
+ return null;
165
+ }
166
+ function covers(held, required) {
167
+ return held.permission === required.permission && reachRank[held.reach] >= reachRank[required.reach] && (held.target === "" || held.target === required.target);
168
+ }
169
+ function resourceWildcard(held) {
170
+ if (!held.endsWith(RESOURCE_WILDCARD)) return null;
171
+ const resource = held.slice(0, -RESOURCE_WILDCARD.length);
172
+ return resource.includes(":") ? null : resource;
173
+ }
174
+ function holdsAt(held, required, depth) {
175
+ for (const h of held) {
176
+ if (h === WILDCARD) return true;
177
+ const resource = resourceWildcard(h);
178
+ if (resource !== null) {
179
+ if (resource === resourceOf(required.permission[0])) return true;
180
+ continue;
181
+ }
182
+ const grant = parseGrant(h);
183
+ if (grant === null) continue;
184
+ if (covers(grant, required)) return true;
185
+ const implies = grant.permission[3];
186
+ if (depth < 4 && implies.length > 0 && holdsAt(implies, required, depth + 1)) return true;
187
+ }
188
+ return false;
189
+ }
190
+ function holds(held, required) {
191
+ const grant = parseGrant(required);
192
+ return grant !== null && holdsAt(held, grant, 0);
193
+ }
194
+
1
195
  // src/placement/index.ts
2
196
  var PLACEMENT_MESSAGE = {
3
197
  /** Page to console: the page is listening and wants a token. */
@@ -13,6 +207,27 @@ var PLACEMENT_MESSAGE = {
13
207
  /** Console to page: how the console looks, now and whenever it changes. */
14
208
  appearance: "foir:placement:appearance"
15
209
  };
210
+ function strings(value) {
211
+ return Array.isArray(value) ? value.filter((v) => typeof v === "string") : null;
212
+ }
213
+ function reasons(value) {
214
+ if (!Array.isArray(value)) return null;
215
+ const out = [];
216
+ for (const entry of value) {
217
+ const e = entry;
218
+ if (typeof e?.grant === "string") out.push({ grant: e.grant, reason: typeof e.reason === "string" ? e.reason : "" });
219
+ }
220
+ return out;
221
+ }
222
+ function grantFrom(sent) {
223
+ return {
224
+ accessToken: sent.accessToken,
225
+ expiresAt: sent.expiresAt,
226
+ orgId: sent.orgId,
227
+ grants: strings(sent.grants) ?? [],
228
+ withheldGrants: reasons(sent.withheldGrants) ?? []
229
+ };
230
+ }
16
231
  var FOIR_CONSOLE_ORIGINS = {
17
232
  production: ["https://app.foir.io"],
18
233
  dev: ["https://app-dev.foir.io"]
@@ -87,7 +302,9 @@ function connectPlacement(options) {
87
302
  get appearance() {
88
303
  return appearance;
89
304
  },
90
- can: (scope) => grant?.grantedScopes.includes(scope) ?? false,
305
+ // As written, for a list a console of the release before sent in the
306
+ // names a placement declared then; and by what the list covers.
307
+ can: (asked) => grant ? grant.grants.includes(asked) || holds(grant.grants, asked) : false,
91
308
  async request(document, variables) {
92
309
  if (!grant) throw new PlacementRefused("the operator has signed out");
93
310
  const res = await doFetch(`${apiUrl}/graphql`, {
@@ -113,7 +330,7 @@ function connectPlacement(options) {
113
330
  const msg = event.data;
114
331
  switch (msg.type) {
115
332
  case PLACEMENT_MESSAGE.token:
116
- grant = msg.grant;
333
+ grant = grantFrom(msg.grant);
117
334
  scheduleRefresh();
118
335
  if (!settled) {
119
336
  settled = true;
package/dist/push.js CHANGED
@@ -1,6 +1,30 @@
1
1
  // src/graphql/foir-request.ts
2
2
  import { print as print2 } from "graphql";
3
3
 
4
+ // src/transport/key-kind.ts
5
+ var KEY_PREFIXES = [
6
+ ["foir_pub_", "publishable"],
7
+ ["foir_srv_", "server"],
8
+ ["foir_adm_", "admin"],
9
+ ["pk_", "publishable"],
10
+ ["sk_", "older"],
11
+ ["tc_", "older"]
12
+ ];
13
+ var FOIR_CREDENTIALS_DOC = "https://docs.foir.dev/credentials";
14
+ function foirKeyKind(apiKey) {
15
+ if (typeof apiKey !== "string") return void 0;
16
+ for (const [prefix, kind] of KEY_PREFIXES) {
17
+ if (apiKey.startsWith(prefix)) return kind;
18
+ }
19
+ return void 0;
20
+ }
21
+ function adminKeyRefusal(client) {
22
+ return `[foir] ${client} was given an admin key. An admin key is a machine's own: nobody signs in through it, and an app never holds one. Build this client from your publishable key, or from your server key on the server that renders your pages. A machine that acts for itself uses createServerClient from @foir/sdk/server. See ${FOIR_CREDENTIALS_DOC}`;
23
+ }
24
+ function isPublishableFoirKey(apiKey) {
25
+ return foirKeyKind(apiKey) === "publishable";
26
+ }
27
+
4
28
  // src/graphql/cdn-route.ts
5
29
  import { print } from "graphql";
6
30
 
@@ -125,6 +149,28 @@ function refuseCredentialHeaders(extra) {
125
149
  }
126
150
  }
127
151
  var FOIR_VISITOR_IP_HEADER = "X-Foir-Visitor-IP";
152
+ function sentCoordinate(value) {
153
+ return Math.round(value * 100) / 100;
154
+ }
155
+ async function resolveFoirVisitor(source) {
156
+ const value = typeof source === "function" ? await source() : source;
157
+ if (value === null || value === void 0) return { place: {} };
158
+ if (typeof value === "string") {
159
+ const address2 = value.trim();
160
+ return { address: address2 || void 0, place: {} };
161
+ }
162
+ const address = value.address?.trim();
163
+ const country = value.country?.trim();
164
+ const place = {};
165
+ if (country) place.country = country;
166
+ if (Number.isFinite(value.latitude) && Number.isFinite(value.longitude)) {
167
+ place.location = {
168
+ latitude: sentCoordinate(value.latitude),
169
+ longitude: sentCoordinate(value.longitude)
170
+ };
171
+ }
172
+ return { address: address || void 0, place };
173
+ }
128
174
  function buildFoirHeaders(input) {
129
175
  const headers = {
130
176
  "Content-Type": "application/json"
@@ -277,7 +323,7 @@ function isCdnEligible(env, documentText, bearerToken, orgId) {
277
323
  if (orgId) return false;
278
324
  if (isFoirPreview(env)) return false;
279
325
  const apiKey = resolveFoirApiKey(env);
280
- if (!apiKey?.startsWith("pk_")) return false;
326
+ if (!isPublishableFoirKey(apiKey)) return false;
281
327
  return !/\bmutation\b/.test(documentText);
282
328
  }
283
329
 
@@ -290,6 +336,9 @@ async function foirRequest(env, document, variables, options) {
290
336
  "[foir] PLATFORM_API_URL or PLATFORM_API_KEY is not set \u2014 add them to .env"
291
337
  );
292
338
  }
339
+ if (foirKeyKind(apiKey) === "admin") {
340
+ throw new FoirRequestError(adminKeyRefusal("foirRequest"));
341
+ }
293
342
  const headers = buildFoirHeaders({
294
343
  apiKey,
295
344
  draftChannel: isFoirPreview(env),
@@ -852,10 +901,7 @@ function foirDataEndpoint(apiUrl, path) {
852
901
  return foirApiEndpoint(apiUrl, path);
853
902
  }
854
903
  async function resolveVisitorAddress(config) {
855
- const source = config.visitor;
856
- const value = typeof source === "function" ? await source() : source;
857
- const address = value?.trim();
858
- return address ? address : void 0;
904
+ return (await resolveFoirVisitor(config.visitor)).address;
859
905
  }
860
906
  async function resolveBearerToken(config) {
861
907
  const source = config.bearerToken;
@@ -896,7 +942,7 @@ async function resolveFoirAuth(config) {
896
942
  };
897
943
  }
898
944
  function foirRoute(input) {
899
- if (!input.apiKey.startsWith("pk_")) return { lane: "origin", reason: "key" };
945
+ if (!isPublishableFoirKey(input.apiKey)) return { lane: "origin", reason: "key" };
900
946
  if (input.bearerToken) return { lane: "origin", reason: "bearer" };
901
947
  if (input.orgId || input.orgUrl) return { lane: "origin", reason: "org" };
902
948
  if (input.mutation) return { lane: "origin", reason: "operation" };
@@ -1034,6 +1080,9 @@ var AUTH_OPS = {
1034
1080
  // Post-credential self reads.
1035
1081
  currentUser: "currentUser",
1036
1082
  myProviderToken: "myProviderToken",
1083
+ // What the calling credential may do, and for a signed-in customer the roles
1084
+ // they hold. `./access` reads it.
1085
+ whoami: "whoami",
1037
1086
  // Methods.
1038
1087
  customerLogin: "customerLogin",
1039
1088
  customerRegister: "customerRegister",
@@ -1,13 +1,25 @@
1
1
  import type { ReactNode } from 'react';
2
- import type { AuthProviderPublic, FoirCustomer, FoirSessionConfig, SessionAdapter } from '../auth';
2
+ import type { AuthProviderPublic, FoirAccess, FoirCustomer, FoirSessionConfig, SessionAdapter } from '../auth';
3
3
  import type { ProviderCallbackInput, ProviderLoginInput } from '../auth';
4
4
  import type { FoirEnv } from '../graphql/foir-request';
5
5
  /** Where the session hydration stands. */
6
6
  export type FoirStatus = 'loading' | 'authenticated' | 'unauthenticated';
7
+ /**
8
+ * Where reading the signed-in customer's access stands. `idle` until something
9
+ * asks for it; `unavailable` when the platform could not be asked.
10
+ */
11
+ export type FoirAccessStatus = 'idle' | 'loading' | 'ready' | 'unavailable';
7
12
  /** The full context value. Read it through the hooks below, not directly. */
8
13
  export interface FoirContextValue {
9
14
  status: FoirStatus;
10
15
  customer: FoirCustomer | null;
16
+ /** The signed-in customer's access, once read. See {@link useFoirAccess}. */
17
+ access: FoirAccess | null;
18
+ accessStatus: FoirAccessStatus;
19
+ /** Ask the platform for the signed-in customer's access, again if it was read before. */
20
+ loadAccess(): Promise<void>;
21
+ /** The same, unless a read is already under way: then that one is awaited. */
22
+ ensureAccess(): Promise<void>;
11
23
  /** The API config; exposed so advanced consumers can call `@foir/sdk/auth` directly. */
12
24
  env: FoirEnv;
13
25
  /** The session store backing this provider. */
@@ -89,6 +101,33 @@ export interface FoirSession {
89
101
  export declare function useFoirSession(): FoirSession;
90
102
  /** The signed-in customer (`{ id, email }`), or null. */
91
103
  export declare function useFoirUser(): FoirCustomer | null;
104
+ /** What {@link useFoirAccess} returns. */
105
+ export interface FoirAccessState {
106
+ /**
107
+ * `loading` until the platform has answered for this session, `ready` once it
108
+ * has (and at once when nobody is signed in), `unavailable` when it could not
109
+ * be asked.
110
+ */
111
+ status: 'loading' | 'ready' | 'unavailable';
112
+ isLoading: boolean;
113
+ /** The signed-in customer's access, or null: signed out, loading or unavailable. */
114
+ access: FoirAccess | null;
115
+ /** Whether the customer holds the role with this key. False until known. */
116
+ hasRole(key: string): boolean;
117
+ /** Whether the customer's scopes cover this one. False until known. */
118
+ hasScope(scope: string): boolean;
119
+ /** Ask the platform again, for a role given or taken away since. */
120
+ reload(): Promise<void>;
121
+ }
122
+ /**
123
+ * The roles the signed-in customer holds and the scopes they resolve to, as the
124
+ * platform resolved them. Read on first use, again when the session refreshes,
125
+ * and whenever `reload` is called.
126
+ *
127
+ * Use it to decide what to show. It protects nothing: the platform checks every
128
+ * request whatever the page offers.
129
+ */
130
+ export declare function useFoirAccess(): FoirAccessState;
92
131
  /** The sign-in / sign-out actions. */
93
132
  export declare function useFoirAuth(): {
94
133
  signIn: (vars: {
@@ -45,7 +45,7 @@ export interface FoirFeedbackClient {
45
45
  *
46
46
  * The distinction earns its place because the three have different audiences.
47
47
  * `signIn` is the visitor's to fix. `scope` is the OPERATOR's — a role that does
48
- * not grant `notes:write` produces `missing required scope: notes:write`, which
48
+ * not grant `notes:create` produces `missing required scope: notes:create`, which
49
49
  * reads like a bad API key and is not one; the key is fine and the customer's
50
50
  * role is short a capability. `other` is everything the widget will not pretend
51
51
  * to understand.
@@ -2,7 +2,8 @@
2
2
  * `@foir/sdk/react` — drop-in React components for Foir customers.
3
3
  *
4
4
  * Wrap your app in `<FoirProvider>`, then read the session with
5
- * `useFoirSession` / `useFoirUser` and drive it with `useFoirAuth` — or drop in
5
+ * `useFoirSession` / `useFoirUser`, ask what the customer may see with
6
+ * `useFoirAccess`, and drive it with `useFoirAuth` — or drop in
6
7
  * the `<SignIn>`, `<OAuthButtons>`, and `<UserButton>` components. Built entirely
7
8
  * on `@foir/sdk/auth`; `react` is the only peer dependency.
8
9
  *
@@ -10,8 +11,8 @@
10
11
  * RECORD the page was rendered from rather than to its URL. It takes the data
11
12
  * client from `@foir/sdk/data` and the subject registry you built for it.
12
13
  */
13
- export { FoirProvider, useFoirSession, useFoirUser, useFoirAuth, } from './FoirProvider';
14
- export type { FoirProviderProps, FoirContextValue, FoirSession, FoirStatus, } from './FoirProvider';
14
+ export { FoirProvider, useFoirSession, useFoirUser, useFoirAuth, useFoirAccess, } from './FoirProvider';
15
+ export type { FoirProviderProps, FoirContextValue, FoirSession, FoirStatus, FoirAccessState, FoirAccessStatus, } from './FoirProvider';
15
16
  export { SignIn } from './SignIn';
16
17
  export type { SignInProps } from './SignIn';
17
18
  export { OAuthButtons } from './OAuthButtons';
@@ -21,5 +22,5 @@ export type { UserButtonProps } from './UserButton';
21
22
  export { FoirFeedback } from './FoirFeedback';
22
23
  export type { FoirFeedbackProps, FoirFeedbackClient } from './FoirFeedback';
23
24
  export { createLocalStorageAdapter } from './localStorageAdapter';
24
- export type { FoirCustomer, SessionAdapter, AuthProviderPublic } from '../auth';
25
+ export type { FoirAccess, FoirCustomer, SessionAdapter, AuthProviderPublic } from '../auth';
25
26
  export type { FoirEnv } from '../graphql/foir-request';