@foir/sdk 0.5.0 → 0.7.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 (82) hide show
  1. package/CHANGELOG.md +325 -0
  2. package/README.md +63 -0
  3. package/dist/auth/auth.d.ts +140 -0
  4. package/dist/auth/hosted.d.ts +40 -0
  5. package/dist/auth/index.d.ts +15 -0
  6. package/dist/auth/operations.d.ts +123 -0
  7. package/dist/auth/session-client.d.ts +76 -0
  8. package/dist/auth-session.js +137 -0
  9. package/dist/auth.js +238 -26
  10. package/dist/client/platform-client.d.ts +29 -0
  11. package/dist/client.js +281 -37
  12. package/dist/codegen/block-registry.d.ts +4 -8
  13. package/dist/codegen/block-scaffold.d.ts +4 -8
  14. package/dist/codegen/index.d.ts +8 -62
  15. package/dist/codegen/preset.d.ts +60 -0
  16. package/dist/codegen/shared.d.ts +25 -0
  17. package/dist/components/EditorProvider.d.ts +36 -0
  18. package/dist/content/content-renderer.d.ts +47 -0
  19. package/dist/content/index.d.ts +12 -0
  20. package/dist/content/richtext.d.ts +70 -0
  21. package/dist/data/builtins.d.ts +296 -0
  22. package/dist/data/client.d.ts +94 -0
  23. package/dist/data/index.d.ts +54 -0
  24. package/dist/data/manifest.d.ts +161 -0
  25. package/dist/data/schema.d.ts +243 -0
  26. package/dist/data/selection.d.ts +330 -0
  27. package/dist/data/transport.d.ts +107 -0
  28. package/dist/data.js +309 -0
  29. package/dist/graphql/cdn-route.d.ts +42 -0
  30. package/dist/graphql/client.d.ts +30 -0
  31. package/dist/graphql/content-client.d.ts +66 -0
  32. package/dist/graphql/foir-request.d.ts +47 -0
  33. package/dist/hooks/useAutoResize.d.ts +22 -0
  34. package/dist/hooks/useEditor.d.ts +16 -0
  35. package/dist/index.d.ts +10 -394
  36. package/dist/media/BlurhashCanvas.d.ts +10 -0
  37. package/dist/media/ProgressiveMedia.d.ts +56 -0
  38. package/dist/media/index.d.ts +14 -0
  39. package/dist/media/media.d.ts +139 -0
  40. package/dist/media/useProgressiveLoad.d.ts +23 -0
  41. package/dist/media.js +5 -0
  42. package/dist/next/cookieAdapter.d.ts +26 -0
  43. package/dist/next/index.d.ts +15 -0
  44. package/dist/next/middleware.d.ts +49 -0
  45. package/dist/next/session.d.ts +53 -0
  46. package/dist/next.js +230 -26
  47. package/dist/protocol/constants.d.ts +6 -0
  48. package/dist/protocol/messages.d.ts +155 -0
  49. package/dist/protocol/validation.d.ts +8 -0
  50. package/dist/push/index.d.ts +14 -0
  51. package/dist/push/operations.d.ts +22 -0
  52. package/dist/push/web-push.d.ts +39 -0
  53. package/dist/push.js +230 -26
  54. package/dist/react/FoirProvider.d.ts +116 -0
  55. package/dist/react/OAuthButtons.d.ts +20 -0
  56. package/dist/react/SignIn.d.ts +22 -0
  57. package/dist/react/UserButton.d.ts +13 -0
  58. package/dist/react/index.d.ts +19 -0
  59. package/dist/react/localStorageAdapter.d.ts +10 -0
  60. package/dist/react.js +230 -26
  61. package/dist/{realtime.d.ts → realtime/index.d.ts} +35 -16
  62. package/dist/server/callback-client.d.ts +92 -0
  63. package/dist/server/index.d.ts +8 -0
  64. package/dist/server/operation-types.d.ts +134 -0
  65. package/dist/server/require-scoped-token.d.ts +76 -0
  66. package/dist/server/server-client.d.ts +34 -0
  67. package/dist/server/types.d.ts +54 -0
  68. package/dist/server/verify-scoped-token.d.ts +100 -0
  69. package/dist/server/webhook-helpers.d.ts +36 -0
  70. package/dist/transport/foir-http.d.ts +217 -0
  71. package/dist/transport/retry.d.ts +61 -0
  72. package/dist/types/index.d.ts +124 -0
  73. package/package.json +23 -13
  74. package/dist/auth.d.ts +0 -382
  75. package/dist/client.d.ts +0 -135
  76. package/dist/content.d.ts +0 -96
  77. package/dist/media.d.ts +0 -234
  78. package/dist/next.d.ts +0 -178
  79. package/dist/push.d.ts +0 -109
  80. package/dist/react.d.ts +0 -319
  81. package/dist/server.d.ts +0 -534
  82. package/dist/shared-DkWlEd2Z.d.ts +0 -18
package/dist/push.js CHANGED
@@ -1,8 +1,16 @@
1
1
  // src/graphql/foir-request.ts
2
+ import { print as print2 } from "graphql";
3
+
4
+ // src/graphql/cdn-route.ts
2
5
  import { print } from "graphql";
6
+
7
+ // src/transport/foir-http.ts
3
8
  function isFoirPreview(env) {
4
9
  return env.FOIR_PREVIEW === "true";
5
10
  }
11
+ function resolveFoirApiKey(env) {
12
+ return isFoirPreview(env) ? env.PLATFORM_PREVIEW_API_KEY ?? env.PLATFORM_API_KEY : env.PLATFORM_API_KEY;
13
+ }
6
14
  var FoirRequestError = class extends Error {
7
15
  /** HTTP status code, when the request failed at the HTTP layer. */
8
16
  status;
@@ -16,41 +24,83 @@ var FoirRequestError = class extends Error {
16
24
  this.status = options?.status;
17
25
  this.graphQLErrors = options?.graphQLErrors;
18
26
  }
27
+ /**
28
+ * The code of the first error, if the response carried one.
29
+ *
30
+ * A getter rather than a stored field so it stays correct for an error
31
+ * constructed anywhere, including by a caller in a test.
32
+ */
33
+ get code() {
34
+ return this.graphQLErrors?.[0]?.extensions?.code;
35
+ }
36
+ /** The correlation id, if the response carried one. */
37
+ get requestId() {
38
+ return this.graphQLErrors?.[0]?.extensions?.requestId;
39
+ }
40
+ /**
41
+ * Whether retrying this request could succeed.
42
+ *
43
+ * The 429 is the one that has to be in here and is easy to leave out: it sits
44
+ * among the 4xx statuses that are otherwise permanent, so a policy written as
45
+ * "retry 5xx, never retry 4xx" excludes exactly the failure that clears on its
46
+ * own. A storefront wrote that policy, its key saturated, and every read
47
+ * failed on the first attempt for an hour.
48
+ */
49
+ get retryable() {
50
+ if (this.status === void 0) return true;
51
+ if (this.status === 429 || this.code === "RATE_LIMITED") return true;
52
+ if (this.status >= 500) return true;
53
+ return this.code === "UNAVAILABLE" || this.code === "TIMEOUT";
54
+ }
55
+ /**
56
+ * How long to wait before retrying, in milliseconds, when the server said.
57
+ * Undefined when it did not, which means the caller picks its own backoff.
58
+ */
59
+ get retryAfterMs() {
60
+ const seconds = this.graphQLErrors?.[0]?.extensions?.retryAfter;
61
+ return typeof seconds === "number" && seconds > 0 ? seconds * 1e3 : void 0;
62
+ }
19
63
  };
20
- async function foirRequest(env, document, variables, options) {
21
- const endpoint = env.PLATFORM_API_URL;
22
- const apiKey = isFoirPreview(env) ? env.PLATFORM_PREVIEW_API_KEY ?? env.PLATFORM_API_KEY : env.PLATFORM_API_KEY;
23
- if (!endpoint || !apiKey) {
24
- throw new FoirRequestError(
25
- "[foir] PLATFORM_API_URL or PLATFORM_API_KEY is not set \u2014 add them to .env"
26
- );
64
+ function readFoirRateLimit(headers) {
65
+ const raw = {
66
+ limit: headers.get("X-RateLimit-Limit"),
67
+ remaining: headers.get("X-RateLimit-Remaining"),
68
+ resetAt: headers.get("X-RateLimit-Reset")
69
+ };
70
+ if (raw.limit === null || raw.remaining === null || raw.resetAt === null) {
71
+ return void 0;
72
+ }
73
+ const limit = Number(raw.limit);
74
+ const remaining = Number(raw.remaining);
75
+ const resetAt = Number(raw.resetAt);
76
+ if (!Number.isFinite(limit) || !Number.isFinite(remaining) || !Number.isFinite(resetAt)) {
77
+ return void 0;
27
78
  }
79
+ return { limit, remaining, resetAt };
80
+ }
81
+ function readFoirRetryAfterMs(headers) {
82
+ const seconds = Number(headers.get("Retry-After"));
83
+ return Number.isFinite(seconds) && seconds > 0 ? seconds * 1e3 : void 0;
84
+ }
85
+ function buildFoirHeaders(input) {
28
86
  const headers = {
29
87
  "Content-Type": "application/json",
30
- "x-api-key": apiKey
88
+ "x-api-key": input.apiKey
31
89
  };
32
- if (isFoirPreview(env)) {
90
+ if (input.draftChannel) {
33
91
  headers["X-Foir-Schema-Channel"] = "draft";
34
92
  }
35
- if (options?.bearerToken) {
36
- headers["Authorization"] = `Bearer ${options.bearerToken}`;
93
+ if (input.bearerToken) {
94
+ headers["Authorization"] = `Bearer ${input.bearerToken}`;
37
95
  }
38
- let res;
39
- try {
40
- res = await fetch(endpoint, {
41
- method: "POST",
42
- headers,
43
- body: JSON.stringify({ query: print(document), variables })
44
- });
45
- } catch (err) {
46
- throw new FoirRequestError("[foir] network error", { cause: err });
47
- }
48
- if (!res.ok) {
49
- throw new FoirRequestError(`[foir] HTTP ${res.status} ${res.statusText}`, {
50
- status: res.status
51
- });
96
+ if (input.extra) {
97
+ for (const [key, value] of Object.entries(input.extra)) {
98
+ headers[key] = value;
99
+ }
52
100
  }
53
- const json = await res.json();
101
+ return headers;
102
+ }
103
+ function readFoirEnvelope(json) {
54
104
  if (json.errors?.length) {
55
105
  const msg = json.errors.map((e) => e.message).join("; ");
56
106
  if (json.data) {
@@ -67,6 +117,150 @@ async function foirRequest(env, document, variables, options) {
67
117
  return json.data;
68
118
  }
69
119
 
120
+ // src/graphql/cdn-route.ts
121
+ var CDN_PATH = "/cdn/graphql";
122
+ function isCdnEligible(env, documentText, bearerToken) {
123
+ if (bearerToken) return false;
124
+ if (isFoirPreview(env)) return false;
125
+ const apiKey = resolveFoirApiKey(env);
126
+ if (!apiKey?.startsWith("pk_")) return false;
127
+ return !/\bmutation\b/.test(documentText);
128
+ }
129
+ function buildCdnUrl(endpoint, apiKey, hash, variables) {
130
+ const url = new URL(endpoint);
131
+ url.pathname = url.pathname.replace(/\/graphql\/?$/, "") + CDN_PATH;
132
+ url.searchParams.set("key", apiKey);
133
+ url.searchParams.set("extensions", JSON.stringify(persistedQueryExtension(hash)));
134
+ if (variables && Object.keys(variables).length > 0) {
135
+ url.searchParams.set("variables", JSON.stringify(variables));
136
+ }
137
+ return url.toString();
138
+ }
139
+ function persistedQueryExtension(hash) {
140
+ return { persistedQuery: { version: 1, sha256Hash: hash } };
141
+ }
142
+ async function documentHash(documentText) {
143
+ const digest = await crypto.subtle.digest(
144
+ "SHA-256",
145
+ new TextEncoder().encode(documentText)
146
+ );
147
+ return Array.from(new Uint8Array(digest)).map((b) => b.toString(16).padStart(2, "0")).join("");
148
+ }
149
+ var PERSISTED_QUERY_NOT_FOUND = "PERSISTED_QUERY_NOT_FOUND";
150
+ var NOT_SHAREABLE = "NOT_SHAREABLE";
151
+
152
+ // src/graphql/foir-request.ts
153
+ async function foirRequest(env, document, variables, options) {
154
+ const endpoint = env.PLATFORM_API_URL;
155
+ const apiKey = resolveFoirApiKey(env);
156
+ if (!endpoint || !apiKey) {
157
+ throw new FoirRequestError(
158
+ "[foir] PLATFORM_API_URL or PLATFORM_API_KEY is not set \u2014 add them to .env"
159
+ );
160
+ }
161
+ const headers = buildFoirHeaders({
162
+ apiKey,
163
+ draftChannel: isFoirPreview(env),
164
+ bearerToken: options?.bearerToken
165
+ });
166
+ const documentText = print2(document);
167
+ let res;
168
+ try {
169
+ if (isCdnEligible(env, documentText, options?.bearerToken)) {
170
+ const hash = await documentHash(documentText);
171
+ let shared = await fetch(buildCdnUrl(endpoint, apiKey, hash, variables), {
172
+ method: "GET",
173
+ headers
174
+ });
175
+ if (await isPersistedQueryMiss(shared)) {
176
+ shared = await fetch(buildCdnUrl(endpoint, apiKey, hash, variables), {
177
+ method: "POST",
178
+ headers,
179
+ body: JSON.stringify({
180
+ query: documentText,
181
+ variables,
182
+ extensions: persistedQueryExtension(hash)
183
+ })
184
+ });
185
+ }
186
+ if (await isSharedRefusal(shared)) {
187
+ res = await fetch(endpoint, {
188
+ method: "POST",
189
+ headers,
190
+ body: JSON.stringify({ query: documentText, variables })
191
+ });
192
+ } else {
193
+ res = shared;
194
+ }
195
+ } else {
196
+ res = await fetch(endpoint, {
197
+ method: "POST",
198
+ headers,
199
+ body: JSON.stringify({ query: documentText, variables })
200
+ });
201
+ }
202
+ } catch (err) {
203
+ throw new FoirRequestError("[foir] network error", { cause: err });
204
+ }
205
+ options?.onRateLimit?.(readFoirRateLimit(res.headers));
206
+ if (!res.ok) {
207
+ const errors = await readErrorEnvelope(res);
208
+ const first = errors?.[0]?.message;
209
+ throw new FoirRequestError(
210
+ first ? `[foir] HTTP ${res.status} ${res.statusText}: ${first}` : `[foir] HTTP ${res.status} ${res.statusText}`,
211
+ { status: res.status, graphQLErrors: errors }
212
+ );
213
+ }
214
+ const json = await res.json();
215
+ return readFoirEnvelope(json);
216
+ }
217
+ async function readErrorEnvelope(res) {
218
+ let errors;
219
+ try {
220
+ const body = await res.json();
221
+ errors = body.errors;
222
+ } catch {
223
+ errors = void 0;
224
+ }
225
+ const headerRetryMs = readFoirRetryAfterMs(res.headers);
226
+ if (headerRetryMs === void 0) return errors;
227
+ const seconds = Math.round(headerRetryMs / 1e3);
228
+ if (!errors?.length) {
229
+ return [
230
+ {
231
+ message: res.statusText || `HTTP ${res.status}`,
232
+ extensions: { code: "RATE_LIMITED", retryAfter: seconds }
233
+ }
234
+ ];
235
+ }
236
+ if (errors[0].extensions?.retryAfter === void 0) {
237
+ errors[0] = {
238
+ ...errors[0],
239
+ extensions: { ...errors[0].extensions, retryAfter: seconds }
240
+ };
241
+ }
242
+ return errors;
243
+ }
244
+ async function isSharedRefusal(res) {
245
+ if (res.ok) return false;
246
+ try {
247
+ const body = await res.clone().json();
248
+ return body.errors?.some((e) => e.extensions?.code === NOT_SHAREABLE) ?? false;
249
+ } catch {
250
+ return false;
251
+ }
252
+ }
253
+ async function isPersistedQueryMiss(res) {
254
+ try {
255
+ const body = await res.clone().json();
256
+ return body.errors?.some(
257
+ (e) => e.extensions?.code === PERSISTED_QUERY_NOT_FOUND
258
+ ) ?? false;
259
+ } catch {
260
+ return false;
261
+ }
262
+ }
263
+
70
264
  // src/auth/operations.ts
71
265
  import { parse } from "graphql";
72
266
  var AUTH_RESULT_FIELDS = "token refreshToken customerId email";
@@ -115,6 +309,16 @@ var AuthProvidersDocument = parse(
115
309
  authProviders { key name type isDefault }
116
310
  }`
117
311
  );
312
+ var CustomerJoinWaitlistDocument = parse(
313
+ `mutation CustomerJoinWaitlist($email: String!) {
314
+ customerJoinWaitlist(email: $email) { id email status }
315
+ }`
316
+ );
317
+ var CustomerSignupContextDocument = parse(
318
+ `query CustomerSignupContext {
319
+ customerSignupContext { signupMode }
320
+ }`
321
+ );
118
322
 
119
323
  // src/auth/auth.ts
120
324
  var TOKEN_KEY = "foir:token";
@@ -0,0 +1,116 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { AuthProviderPublic, FoirCustomer, FoirSessionConfig, SessionAdapter } from '../auth';
3
+ import type { ProviderCallbackInput, ProviderLoginInput } from '../auth';
4
+ import type { FoirEnv } from '../graphql/foir-request';
5
+ /** Where the session hydration stands. */
6
+ export type FoirStatus = 'loading' | 'authenticated' | 'unauthenticated';
7
+ /** The full context value. Read it through the hooks below, not directly. */
8
+ export interface FoirContextValue {
9
+ status: FoirStatus;
10
+ customer: FoirCustomer | null;
11
+ /** The API config; exposed so advanced consumers can call `@foir/sdk/auth` directly. */
12
+ env: FoirEnv;
13
+ /** The session store backing this provider. */
14
+ adapter: SessionAdapter;
15
+ signIn(vars: {
16
+ email: string;
17
+ password: string;
18
+ }): Promise<FoirCustomer>;
19
+ register(vars: {
20
+ email: string;
21
+ password: string;
22
+ }): Promise<FoirCustomer>;
23
+ requestOtp(email: string): Promise<boolean>;
24
+ signInWithOtp(vars: {
25
+ email: string;
26
+ otp: string;
27
+ }): Promise<FoirCustomer>;
28
+ /** Start a provider (OAuth/OIDC) login. Redirect providers navigate away. */
29
+ signInWithProvider(providerKey: string, opts?: Omit<ProviderLoginInput, 'providerKey'>): Promise<void>;
30
+ /** Complete a provider login from your callback route (code + state). */
31
+ completeProviderLogin(input: Omit<ProviderCallbackInput, 'providerKey'> & {
32
+ providerKey: string;
33
+ }): Promise<FoirCustomer>;
34
+ signOut(): Promise<void>;
35
+ /** Rotate the access token; returns false (and signs out) if it can't. */
36
+ refresh(): Promise<boolean>;
37
+ /** The current access token, or null when signed out. */
38
+ getToken(): Promise<string | null>;
39
+ /** The project's configured auth providers (for OAuth buttons). */
40
+ getProviders(): Promise<AuthProviderPublic[]>;
41
+ /**
42
+ * In `hostedSession` mode, where to send the browser to sign in — sign-in is a
43
+ * redirect to your Foir login domain, not an in-page form. Null in token mode,
44
+ * where you call {@link FoirContextValue.signIn} instead.
45
+ */
46
+ hostedSignInUrl: ((returnTo?: string) => string) | null;
47
+ }
48
+ export interface FoirProviderProps {
49
+ /** API config: `{ PLATFORM_API_URL, PLATFORM_API_KEY }` (a publishable `pk_` key). */
50
+ env: FoirEnv;
51
+ /** Session store for token mode. Defaults to a browser localStorage adapter. */
52
+ adapter?: SessionAdapter;
53
+ /**
54
+ * Opt into Foir-hosted first-party sessions: the durable credential is an
55
+ * HttpOnly cookie on your own domain and the access token lives only in
56
+ * memory, so nothing authentication-related is reachable from JS. This is the
57
+ * secure default for a backend-less SPA, and it REPLACES `adapter` — pass one
58
+ * or the other, not both.
59
+ *
60
+ * Requires a verified custom login domain sharing a registrable domain with
61
+ * your app (`app.acme.com` + `login.acme.com`). Sign-in becomes a redirect to
62
+ * {@link FoirContextValue.hostedSignInUrl}; the password/OTP/provider actions
63
+ * do not apply and will throw.
64
+ */
65
+ hostedSession?: Omit<FoirSessionConfig, 'onSignOut'>;
66
+ children: ReactNode;
67
+ }
68
+ /**
69
+ * Provides Foir customer auth to a React tree. Hydrates the session on mount,
70
+ * then exposes the current customer and sign-in/out actions through
71
+ * {@link useFoirSession}, {@link useFoirUser}, and {@link useFoirAuth}.
72
+ *
73
+ * Two session models:
74
+ *
75
+ * default tokens in a `SessionAdapter` (localStorage unless you pass
76
+ * your own). Simple, but a refresh token sits in JS.
77
+ * `hostedSession` Foir-hosted first-party cookie; the bearer never leaves
78
+ * memory. Needs a custom login domain. Prefer this.
79
+ */
80
+ export declare function FoirProvider({ env, adapter, hostedSession, children, }: FoirProviderProps): import("react/jsx-runtime").JSX.Element;
81
+ /** The current session state. */
82
+ export interface FoirSession {
83
+ status: FoirStatus;
84
+ customer: FoirCustomer | null;
85
+ isLoading: boolean;
86
+ isAuthenticated: boolean;
87
+ }
88
+ /** Read the current session status and customer. */
89
+ export declare function useFoirSession(): FoirSession;
90
+ /** The signed-in customer (`{ id, email }`), or null. */
91
+ export declare function useFoirUser(): FoirCustomer | null;
92
+ /** The sign-in / sign-out actions. */
93
+ export declare function useFoirAuth(): {
94
+ signIn: (vars: {
95
+ email: string;
96
+ password: string;
97
+ }) => Promise<FoirCustomer>;
98
+ register: (vars: {
99
+ email: string;
100
+ password: string;
101
+ }) => Promise<FoirCustomer>;
102
+ requestOtp: (email: string) => Promise<boolean>;
103
+ signInWithOtp: (vars: {
104
+ email: string;
105
+ otp: string;
106
+ }) => Promise<FoirCustomer>;
107
+ signInWithProvider: (providerKey: string, opts?: Omit<ProviderLoginInput, "providerKey">) => Promise<void>;
108
+ completeProviderLogin: (input: Omit<ProviderCallbackInput, "providerKey"> & {
109
+ providerKey: string;
110
+ }) => Promise<FoirCustomer>;
111
+ signOut: () => Promise<void>;
112
+ refresh: () => Promise<boolean>;
113
+ getToken: () => Promise<string | null>;
114
+ getProviders: () => Promise<AuthProviderPublic[]>;
115
+ hostedSignInUrl: ((returnTo?: string) => string) | null;
116
+ };
@@ -0,0 +1,20 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { AuthProviderPublic } from '../auth';
3
+ export interface OAuthButtonsProps {
4
+ /** Passed to the provider login (where the callback returns the user). */
5
+ returnTo?: string;
6
+ /** Override the provider's default redirect URI (must be allow-listed). */
7
+ redirectUri?: string;
8
+ /** Filter which providers render (defaults to every redirect-capable one). */
9
+ filter?(provider: AuthProviderPublic): boolean;
10
+ /** Render your own button. Receives the provider and a click handler. */
11
+ children?(provider: AuthProviderPublic, signIn: () => void): ReactNode;
12
+ className?: string;
13
+ onError?(error: unknown): void;
14
+ }
15
+ /**
16
+ * Renders a sign-in button per configured OAuth/OIDC/SAML provider. Clicking a
17
+ * button starts the provider login and (for redirect flows) navigates away.
18
+ * Pass a `children` render prop to fully control the button markup.
19
+ */
20
+ export declare function OAuthButtons({ returnTo, redirectUri, filter, children, className, onError, }: OAuthButtonsProps): import("react/jsx-runtime").JSX.Element | null;
@@ -0,0 +1,22 @@
1
+ import type { FoirCustomer } from '../auth';
2
+ export interface SignInProps {
3
+ /** `password` (default) or `otp` (email one-time code). Ignored when `hostedUrl` is set. */
4
+ method?: 'password' | 'otp';
5
+ /** Render a single button that redirects to a hosted login page instead of an embedded form. */
6
+ hostedUrl?: string;
7
+ /** Show OAuth provider buttons below the form. Default true. */
8
+ oauth?: boolean;
9
+ /** Navigate here after a successful sign-in. */
10
+ afterSignInUrl?: string;
11
+ /** Called with the customer on a successful sign-in (before any redirect). */
12
+ onSignIn?(customer: FoirCustomer): void;
13
+ className?: string;
14
+ }
15
+ /**
16
+ * A drop-in customer sign-in. Renders an email/password (or OTP) form and,
17
+ * unless disabled, OAuth provider buttons. Set `hostedUrl` to instead render a
18
+ * button that redirects to a hosted login page. All state is handled through
19
+ * {@link FoirProvider}; on success it calls `onSignIn` and optionally navigates
20
+ * to `afterSignInUrl`.
21
+ */
22
+ export declare function SignIn({ method, hostedUrl, oauth, afterSignInUrl, onSignIn, className, }: SignInProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,13 @@
1
+ export interface UserButtonProps {
2
+ /** Called after a successful sign-out. */
3
+ afterSignOut?(): void;
4
+ /** Navigate here after sign-out. */
5
+ afterSignOutUrl?: string;
6
+ className?: string;
7
+ }
8
+ /**
9
+ * A minimal signed-in affordance: the customer's email plus a sign-out button.
10
+ * Renders nothing when signed out. Style it via `className` (the default is an
11
+ * unopinionated inline layout).
12
+ */
13
+ export declare function UserButton({ afterSignOut, afterSignOutUrl, className }: UserButtonProps): import("react/jsx-runtime").JSX.Element | null;
@@ -0,0 +1,19 @@
1
+ /**
2
+ * `@foir/sdk/react` — a drop-in React auth layer for Foir customers.
3
+ *
4
+ * Wrap your app in `<FoirProvider>`, then read the session with
5
+ * `useFoirSession` / `useFoirUser` and drive it with `useFoirAuth` — or drop in
6
+ * the `<SignIn>`, `<OAuthButtons>`, and `<UserButton>` components. Built entirely
7
+ * on `@foir/sdk/auth`; `react` is the only peer dependency.
8
+ */
9
+ export { FoirProvider, useFoirSession, useFoirUser, useFoirAuth, } from './FoirProvider';
10
+ export type { FoirProviderProps, FoirContextValue, FoirSession, FoirStatus, } from './FoirProvider';
11
+ export { SignIn } from './SignIn';
12
+ export type { SignInProps } from './SignIn';
13
+ export { OAuthButtons } from './OAuthButtons';
14
+ export type { OAuthButtonsProps } from './OAuthButtons';
15
+ export { UserButton } from './UserButton';
16
+ export type { UserButtonProps } from './UserButton';
17
+ export { createLocalStorageAdapter } from './localStorageAdapter';
18
+ export type { FoirCustomer, SessionAdapter, AuthProviderPublic } from '../auth';
19
+ export type { FoirEnv } from '../graphql/foir-request';
@@ -0,0 +1,10 @@
1
+ import type { SessionAdapter } from '../auth';
2
+ /**
3
+ * A {@link SessionAdapter} backed by the browser's `localStorage`.
4
+ *
5
+ * SSR-safe: with no `window` (server render) reads return `null` and writes are
6
+ * no-ops, so {@link FoirProvider} starts in `loading` and hydrates the real
7
+ * session on the client after mount. This is the default adapter when you don't
8
+ * pass one to `FoirProvider`.
9
+ */
10
+ export declare function createLocalStorageAdapter(): SessionAdapter;