@foir/sdk 0.27.1 → 0.29.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.
package/dist/data.js CHANGED
@@ -1380,6 +1380,8 @@ function printSubscriptionDocument(input) {
1380
1380
  }
1381
1381
 
1382
1382
  // src/data/ws-transport.ts
1383
+ var CLOSE_CREDENTIAL_ENDED = 4402;
1384
+ var CLOSE_FORBIDDEN = 4403;
1383
1385
  var GQL = {
1384
1386
  connectionInit: "connection_init",
1385
1387
  connectionAck: "connection_ack",
@@ -1414,6 +1416,9 @@ var FoirSocketTransport = class {
1414
1416
  disposed = false;
1415
1417
  keepAlive = null;
1416
1418
  retryTimer = null;
1419
+ // A bearer renewed after the socket's lapsed (4402), presented from the next
1420
+ // connect on in place of the client's own.
1421
+ renewedBearer = null;
1417
1422
  constructor(config) {
1418
1423
  if (config.dispatch !== void 0) {
1419
1424
  throw new FoirRequestError(
@@ -1486,12 +1491,14 @@ var FoirSocketTransport = class {
1486
1491
  socket.onmessage = (event) => this.receive(event.data);
1487
1492
  socket.onerror = () => {
1488
1493
  };
1489
- socket.onclose = () => this.handleClose();
1494
+ socket.onclose = (event) => this.handleClose(event);
1490
1495
  }
1491
1496
  async sendConnectionInit(socket) {
1492
1497
  this.connecting = false;
1493
1498
  try {
1494
- const { headers } = await resolveFoirAuth(this.config);
1499
+ const { headers } = await resolveFoirAuth(
1500
+ this.renewedBearer ? { ...this.config, bearerToken: this.renewedBearer } : this.config
1501
+ );
1495
1502
  const { "Content-Type": _contentType, ...params } = headers;
1496
1503
  const extra = await this.config.connectionParams?.() ?? {};
1497
1504
  this.send(socket, {
@@ -1505,19 +1512,53 @@ var FoirSocketTransport = class {
1505
1512
  this.teardownSocket(true);
1506
1513
  }
1507
1514
  }
1508
- handleClose() {
1515
+ handleClose(event) {
1516
+ const code = event?.code;
1509
1517
  const wasAcknowledged = this.acknowledged;
1510
1518
  this.teardownSocket(false);
1511
1519
  if (this.disposed || this.registrations.size === 0) return;
1512
1520
  for (const registration of this.registrations.values()) {
1513
1521
  registration.sent = false;
1514
1522
  }
1523
+ if (code === CLOSE_FORBIDDEN) {
1524
+ this.failAll(
1525
+ new FoirRequestError(
1526
+ "[foir] the subscription socket was refused where it asked to stand: the signed-in user is no member of the organization this client names"
1527
+ )
1528
+ );
1529
+ return;
1530
+ }
1531
+ if (code === CLOSE_CREDENTIAL_ENDED) {
1532
+ void this.reconnectWithARenewedBearer();
1533
+ return;
1534
+ }
1515
1535
  this.scheduleReconnect(
1516
1536
  new FoirRequestError(
1517
1537
  wasAcknowledged ? "[foir] the subscription socket closed and could not be re-established" : "[foir] the subscription socket closed before the credential was accepted"
1518
1538
  )
1519
1539
  );
1520
1540
  }
1541
+ /**
1542
+ * Reconnect after the socket's credential lapsed: with the bearer renewed
1543
+ * through the client's `refreshBearer` where it has one, failing every
1544
+ * subscription with `FoirSessionExpiredError` when the session is over. A
1545
+ * client without one reconnects at once, presenting what its bearer
1546
+ * provider gives now.
1547
+ */
1548
+ async reconnectWithARenewedBearer() {
1549
+ const refresh = this.config.refreshBearer;
1550
+ if (refresh) {
1551
+ const token = await renewBearerOnce(refresh).catch(() => null);
1552
+ if (this.disposed) return;
1553
+ if (!token) {
1554
+ this.failAll(new FoirSessionExpiredError());
1555
+ return;
1556
+ }
1557
+ this.renewedBearer = token;
1558
+ }
1559
+ this.attempt = 0;
1560
+ this.ensureConnected();
1561
+ }
1521
1562
  scheduleReconnect(error) {
1522
1563
  if (this.disposed || this.registrations.size === 0) return;
1523
1564
  if (this.attempt >= this.policy.maxAttempts) {
@@ -1,3 +1,4 @@
1
+ import type { Ref } from 'react';
1
2
  import type { ProgressiveImageSource, ProgressiveVideoSource, VariantTierKey } from './media';
2
3
  export interface ProgressiveMediaProps {
3
4
  image?: ProgressiveImageSource;
@@ -25,6 +26,31 @@ export interface ProgressiveMediaProps {
25
26
  muted?: boolean;
26
27
  /** Video playback — loop on end. Default true. */
27
28
  loop?: boolean;
29
+ /**
30
+ * Video playback: show the browser's controls. Defaults to `!autoplay`. A
31
+ * video with controls shows from the start, its poster standing until it
32
+ * plays, so the controls can always be reached.
33
+ */
34
+ controls?: boolean;
35
+ /** Video: how much the browser fetches before it plays (`<video preload>`). */
36
+ preload?: 'none' | 'metadata' | 'auto';
37
+ /**
38
+ * Where the media sits in its box when it is cropped to fit, as CSS
39
+ * `object-position` (`'30% 60%'`), for a focal point. Applies to the image,
40
+ * the video and its thumbnail. Default centre.
41
+ */
42
+ objectPosition?: string;
43
+ /**
44
+ * The `<video>` element, once it mounts: for a caller's own play and pause
45
+ * controls, and the events that keep them in step.
46
+ */
47
+ videoRef?: Ref<HTMLVideoElement>;
48
+ /**
49
+ * Called once when a video's source cannot be played. The video is then
50
+ * removed, and its thumbnail, labelled with `alt`, stands in for it over
51
+ * the colour and blurhash layers.
52
+ */
53
+ onVideoError?: () => void;
28
54
  /**
29
55
  * When false, skip loading the full media layer (no `<video>`/full `<img>`
30
56
  * mount, no HLS attach, no full-image preload). The lower layers — colour,
@@ -53,4 +79,4 @@ export interface ProgressiveMediaProps {
53
79
  */
54
80
  variantTiers?: readonly VariantTierKey[];
55
81
  }
56
- export declare function ProgressiveMedia({ image, video, alt, className, fill, fit, placeholder, autoplay, muted, loop, active, sizes, priority, variantTiers, }: ProgressiveMediaProps): import("react/jsx-runtime").JSX.Element | null;
82
+ export declare function ProgressiveMedia({ image, video, alt, className, fill, fit, placeholder, autoplay, muted, loop, controls, preload, objectPosition, videoRef: externalVideoRef, onVideoError, active, sizes, priority, variantTiers, }: ProgressiveMediaProps): import("react/jsx-runtime").JSX.Element | null;
package/dist/media.js CHANGED
@@ -213,6 +213,11 @@ function ProgressiveMedia({
213
213
  autoplay = true,
214
214
  muted = true,
215
215
  loop = true,
216
+ controls,
217
+ preload,
218
+ objectPosition,
219
+ videoRef: externalVideoRef,
220
+ onVideoError,
216
221
  active = true,
217
222
  sizes = "100vw",
218
223
  priority = false,
@@ -225,7 +230,13 @@ function ProgressiveMedia({
225
230
  const source = video ?? image;
226
231
  const isVideo = !!video;
227
232
  const [wrapperRef, inViewport] = useInViewport("300px", priority);
228
- const videoActive = isVideo && active && (priority || inViewport);
233
+ const [failedUrl, setFailedUrl] = useState2(void 0);
234
+ const videoFailed = isVideo && failedUrl !== void 0 && failedUrl === video?.url;
235
+ const [videoSize, setVideoSize] = useState2(
236
+ null
237
+ );
238
+ const videoActive = isVideo && active && !videoFailed && (priority || inViewport);
239
+ const showControls = controls ?? !autoplay;
229
240
  const imageActive = !isVideo && active;
230
241
  const hlsUrl = video?.hlsUrl;
231
242
  const useHls = isVideo && !!hlsUrl;
@@ -282,6 +293,8 @@ function ProgressiveMedia({
282
293
  cleanupRef.current = null;
283
294
  }
284
295
  videoRef.current = el;
296
+ if (typeof externalVideoRef === "function") externalVideoRef(el);
297
+ else if (externalVideoRef) externalVideoRef.current = el;
285
298
  if (!el) return;
286
299
  if (el.readyState >= 3) {
287
300
  onFullReady();
@@ -299,7 +312,7 @@ function ProgressiveMedia({
299
312
  el.removeEventListener("playing", handler);
300
313
  };
301
314
  },
302
- [onFullReady]
315
+ [onFullReady, externalVideoRef]
303
316
  );
304
317
  if (!source) {
305
318
  if (!placeholder) return null;
@@ -314,8 +327,9 @@ function ProgressiveMedia({
314
327
  );
315
328
  }
316
329
  const effective = !isVideo ? effectiveImageDimensions(image) : null;
317
- const w = effective?.width ?? source.width;
318
- const h = effective?.height ?? source.height;
330
+ const w = effective?.width ?? source.width ?? videoSize?.w;
331
+ const h = effective?.height ?? source.height ?? videoSize?.h;
332
+ const positioned = objectPosition ? { objectPosition } : void 0;
319
333
  const aspect = w && h ? w / h : 16 / 10;
320
334
  return /* @__PURE__ */ jsxs(
321
335
  "div",
@@ -342,13 +356,14 @@ function ProgressiveMedia({
342
356
  "img",
343
357
  {
344
358
  src: thumbnailUrl,
345
- alt: "",
346
- "aria-hidden": true,
359
+ alt: videoFailed ? alt ?? "" : "",
360
+ "aria-hidden": videoFailed ? void 0 : true,
347
361
  width: w,
348
362
  height: h,
349
363
  loading: priority ? "eager" : "lazy",
350
364
  fetchPriority: priority ? "high" : void 0,
351
- className: `absolute inset-0 h-full w-full ${fitClass} transition-opacity duration-400 ease-in-out ${stage === "thumbnail" || stage === "full" ? "opacity-100" : "opacity-0"}`
365
+ className: `absolute inset-0 h-full w-full ${fitClass} transition-opacity duration-400 ease-in-out ${stage === "thumbnail" || stage === "full" ? "opacity-100" : "opacity-0"}`,
366
+ style: positioned
352
367
  }
353
368
  ),
354
369
  videoActive ? /* @__PURE__ */ jsx2(
@@ -363,8 +378,21 @@ function ProgressiveMedia({
363
378
  muted,
364
379
  loop,
365
380
  playsInline: true,
366
- controls: !autoplay,
367
- className: `absolute inset-0 h-full w-full ${fitClass} transition-opacity duration-500 ease-in-out ${stage === "full" ? "opacity-100" : "opacity-0"}`
381
+ controls: showControls,
382
+ preload,
383
+ "aria-label": alt || void 0,
384
+ onLoadedMetadata: (e) => {
385
+ const v = e.currentTarget;
386
+ if (!source.width && v.videoWidth && v.videoHeight) {
387
+ setVideoSize({ w: v.videoWidth, h: v.videoHeight });
388
+ }
389
+ },
390
+ onError: () => {
391
+ setFailedUrl(video?.url);
392
+ onVideoError?.();
393
+ },
394
+ className: `absolute inset-0 h-full w-full ${fitClass} transition-opacity duration-500 ease-in-out ${stage === "full" || showControls ? "opacity-100" : "opacity-0"}`,
395
+ style: positioned
368
396
  }
369
397
  ) : imageActive ? (
370
398
  // <picture>: browser picks AVIF when it can decode it, else WebP. The
@@ -383,7 +411,8 @@ function ProgressiveMedia({
383
411
  height: h,
384
412
  loading: priority ? "eager" : "lazy",
385
413
  fetchPriority: priority ? "high" : void 0,
386
- className: `absolute inset-0 h-full w-full ${fitClass} transition-opacity duration-500 ease-in-out ${stage === "full" ? "opacity-100" : "opacity-0"}`
414
+ className: `absolute inset-0 h-full w-full ${fitClass} transition-opacity duration-500 ease-in-out ${stage === "full" ? "opacity-100" : "opacity-0"}`,
415
+ style: positioned
387
416
  }
388
417
  )
389
418
  ] })
@@ -27,8 +27,12 @@ interface PasswordArgs {
27
27
  }
28
28
  /** Password sign-in in a route handler; persists the session to cookies. */
29
29
  export declare function foirSignInWithPassword({ env, store, email, password, cookieOptions, }: PasswordArgs): Promise<FoirUser>;
30
- /** Register a user in a route handler; persists the session to cookies. */
31
- export declare function foirRegisterWithPassword({ env, store, email, password, cookieOptions, }: PasswordArgs): Promise<FoirUser>;
30
+ /**
31
+ * Start a sign-up in a route handler: the address is mailed a code, and
32
+ * {@link foirSignInWithOtp} with it finishes the account and persists the
33
+ * session to cookies.
34
+ */
35
+ export declare function foirRegisterWithPassword({ env, email, password, }: Pick<PasswordArgs, 'env' | 'email' | 'password'>): Promise<void>;
32
36
  /** Complete an OTP sign-in in a route handler; persists the session to cookies. */
33
37
  export declare function foirSignInWithOtp({ env, store, email, otp, cookieOptions, }: {
34
38
  env: FoirEnv;
package/dist/next.js CHANGED
@@ -1127,8 +1127,8 @@ async function getSessionUser(adapter) {
1127
1127
  async function signInWithPassword(adapter, env, vars) {
1128
1128
  return saveSession(adapter, await login(env, vars));
1129
1129
  }
1130
- async function signUpWithPassword(adapter, env, vars) {
1131
- return saveSession(adapter, await register(env, vars));
1130
+ async function signUpWithPassword(env, vars) {
1131
+ await register(env, vars);
1132
1132
  }
1133
1133
  async function signInWithOTP(adapter, env, vars) {
1134
1134
  return saveSession(adapter, await loginWithOTP(env, vars));
@@ -1158,15 +1158,10 @@ function foirSignInWithPassword({
1158
1158
  }
1159
1159
  function foirRegisterWithPassword({
1160
1160
  env,
1161
- store,
1162
1161
  email,
1163
- password,
1164
- cookieOptions
1162
+ password
1165
1163
  }) {
1166
- return signUpWithPassword(createFoirCookieAdapter(store, { cookieOptions }), env, {
1167
- email,
1168
- password
1169
- });
1164
+ return signUpWithPassword(env, { email, password });
1170
1165
  }
1171
1166
  function foirSignInWithOtp({
1172
1167
  env,
package/dist/placement.js CHANGED
@@ -70,6 +70,7 @@ var GRANT_TABLE = [
70
70
  ["apikeys:write", "none", [], []],
71
71
  ["organizations:read", "none", [], []],
72
72
  ["organizations:write", "none", [], []],
73
+ ["orgsettings:write", "none", [], []],
73
74
  ["organizations:create", "none", [], []],
74
75
  ["organizations:delete", "none", [], []],
75
76
  ["organizations:reach", "none", [], []],
@@ -80,7 +81,7 @@ var GRANT_TABLE = [
80
81
  ["projects:read", "none", [], []],
81
82
  ["projects:write", "none", [], []],
82
83
  ["project:stand", "none", [], []],
83
- ["project:manage", "none", [], ["project:stand", "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:setup", "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", "authclients:read", "authclients:write", "domains:read", "domains:write", "loginpages:read", "loginpages: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"]],
84
+ ["project:manage", "none", [], ["project:stand", "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:setup", "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", "authclients:read", "authclients:write", "domains:read", "domains:write", "loginpages:read", "loginpages: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", "orgsettings:write", "organizations:reach", "members:read", "members:write", "roles:read", "roles:write", "projects:read", "projects:write", "audit:read"]],
84
85
  ["projects:create", "none", [], []],
85
86
  ["projects:delete", "none", [], []],
86
87
  ["tenants:read", "none", [], []],
@@ -111,7 +112,6 @@ var GRANT_TABLE = [
111
112
  ["usersecrets:list", "none", [], []],
112
113
  ["usersecrets:put", "none", [], []],
113
114
  ["usersecrets:delete", "none", [], []],
114
- ["auth:login_with_foir", "none", [], []],
115
115
  ["signinproviders:read", "none", [], []],
116
116
  ["signinproviders:write", "none", [], []]
117
117
  ];
@@ -46,6 +46,25 @@ export interface FoirFeedbackProps {
46
46
  * button that calls it; given none, it says so and stops.
47
47
  */
48
48
  onSignIn?: () => void;
49
+ /**
50
+ * Whether a visitor can sign in on this site. Default true. `false` says
51
+ * they cannot, so where the project takes no notes from visitors who are not
52
+ * signed in, the widget says feedback is not open rather than asking them to
53
+ * sign in somewhere they cannot.
54
+ */
55
+ signIn?: boolean;
56
+ /**
57
+ * How the panel opens. `inline` (the default) opens it under the button, in
58
+ * the page's flow. `dialog` opens it over the page in a modal `<dialog>`,
59
+ * with a backdrop, closed by Escape, the backdrop or its Close button.
60
+ */
61
+ layout?: 'inline' | 'dialog';
62
+ /**
63
+ * The built-in styles' colours, radius and type. See
64
+ * `FoirFeedbackAppearance`: each is also a CSS custom property, so a
65
+ * stylesheet can set it instead. Ignored with `className`.
66
+ */
67
+ appearance?: FoirFeedbackAppearance;
49
68
  /** Called with a signed-in user's note once it is written. */
50
69
  onSubmitted?: (note: FoirUserNote) => void;
51
70
  /**
@@ -61,9 +80,45 @@ export interface FoirFeedbackProps {
61
80
  onError?: (error: unknown) => void;
62
81
  /**
63
82
  * Style it yourself. Passing a class turns OFF the built-in inline styles
64
- * entirely, so the `data-foir-feedback-*` attributes below are yours to target
65
- * without fighting anything.
83
+ * entirely, `appearance` with them, so the `data-foir-feedback-*` attributes
84
+ * are yours to target without fighting anything.
66
85
  */
67
86
  className?: string;
68
87
  }
69
- export declare function FoirFeedback({ client, subjects, subject, label, triggerLabel, title, placeholder, defaultOpen, onSignIn, onSubmitted, onVisitorSubmitted, onError, className, }: FoirFeedbackProps): import("react/jsx-runtime").JSX.Element;
88
+ /**
89
+ * The built-in styles' tokens.
90
+ *
91
+ * Each is a CSS custom property on the wrapper, `--foir-feedback-<name>`, which
92
+ * the built-in styles read with today's look as the fallback. So a token can be
93
+ * set here, or from a stylesheet, where a design system's own variables can
94
+ * feed it:
95
+ *
96
+ * ```css
97
+ * [data-foir-feedback] { --foir-feedback-radius: var(--radius); }
98
+ * ```
99
+ *
100
+ * A value is any CSS value the property takes.
101
+ */
102
+ export interface FoirFeedbackAppearance {
103
+ /** `--foir-feedback-text`. Default: inherited, or the browser's dialog text in a dialog. */
104
+ colorText?: string;
105
+ /** `--foir-feedback-muted`, the line naming what it is about. Default: the text at three quarters. */
106
+ colorMuted?: string;
107
+ /** `--foir-feedback-background`, the panel and its fields. Default: none, or the browser's dialog background in a dialog. */
108
+ colorBackground?: string;
109
+ /** `--foir-feedback-border`. Default `currentColor`. */
110
+ colorBorder?: string;
111
+ /** `--foir-feedback-primary`, the send button's fill and border. Default: no fill. */
112
+ colorPrimary?: string;
113
+ /** `--foir-feedback-primary-text`, the send button's text. Default: inherited. */
114
+ colorPrimaryText?: string;
115
+ /** `--foir-feedback-danger`, a failure. Default `#c0392b`. */
116
+ colorDanger?: string;
117
+ /** `--foir-feedback-radius`, the panel's corners; its controls take three quarters. Default `8px`. */
118
+ radius?: string;
119
+ /** `--foir-feedback-font-family`. Default: inherited. */
120
+ fontFamily?: string;
121
+ /** `--foir-feedback-font-size`. Default: inherited. */
122
+ fontSize?: string;
123
+ }
124
+ export declare function FoirFeedback({ client, subjects, subject, label, triggerLabel, title, placeholder, defaultOpen, onSignIn, signIn, layout, appearance, onSubmitted, onVisitorSubmitted, onError, className, }: FoirFeedbackProps): import("react/jsx-runtime").JSX.Element;
@@ -28,10 +28,14 @@ export interface FoirContextValue {
28
28
  email: string;
29
29
  password: string;
30
30
  }): Promise<FoirUser>;
31
+ /**
32
+ * Start a sign-up: the address is mailed a code, and `signInWithOtp` with it
33
+ * finishes the account and signs them in.
34
+ */
31
35
  register(vars: {
32
36
  email: string;
33
37
  password: string;
34
- }): Promise<FoirUser>;
38
+ }): Promise<void>;
35
39
  requestOtp(email: string): Promise<boolean>;
36
40
  signInWithOtp(vars: {
37
41
  email: string;
@@ -138,7 +142,7 @@ export declare function useFoirAuth(): {
138
142
  register: (vars: {
139
143
  email: string;
140
144
  password: string;
141
- }) => Promise<FoirUser>;
145
+ }) => Promise<void>;
142
146
  requestOtp: (email: string) => Promise<boolean>;
143
147
  signInWithOtp: (vars: {
144
148
  email: string;
@@ -20,7 +20,7 @@ export type { OAuthButtonsProps } from './OAuthButtons';
20
20
  export { UserButton } from './UserButton';
21
21
  export type { UserButtonProps } from './UserButton';
22
22
  export { FoirFeedback } from './FoirFeedback';
23
- export type { FoirFeedbackProps, FoirFeedbackClient } from './FoirFeedback';
23
+ export type { FoirFeedbackAppearance, FoirFeedbackClient, FoirFeedbackProps, } from './FoirFeedback';
24
24
  export { createLocalStorageAdapter } from './localStorageAdapter';
25
25
  export type { FoirAccess, FoirUser, SessionAdapter, AuthProviderPublic } from '../auth';
26
26
  export type { FoirEnv } from '../graphql/foir-request';