@imtbl/auth-next-client 2.12.7-alpha.1 → 2.12.7-alpha.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -10,7 +10,7 @@ This package provides minimal client-side utilities for Next.js applications usi
10
10
 
11
11
  - `useLogin` - Hook for login flows with state management (loading, error)
12
12
  - `useLogout` - Hook for logout with federated logout support (clears both local and upstream sessions)
13
- - `useImmutableSession` - Hook that provides session state and a `getUser` function for wallet integration
13
+ - `useImmutableSession` - Hook that provides session state, `getAccessToken()` for guaranteed-fresh tokens, and `getUser` for wallet integration
14
14
  - `CallbackPage` - OAuth callback handler component
15
15
 
16
16
  For server-side utilities, use [`@imtbl/auth-next-server`](../auth-next-server).
@@ -27,6 +27,10 @@ npm install @imtbl/auth-next-client @imtbl/auth-next-server next-auth@5
27
27
  - `next` >= 14.0.0
28
28
  - `next-auth` >= 5.0.0-beta.25
29
29
 
30
+ ### Next.js 14 Compatibility
31
+
32
+ This package is compatible with both Next.js 14 and 15. It uses only standard APIs available in both versions (`next/navigation` for `useRouter`, `next-auth/react`). No Next.js 15-only APIs are used.
33
+
30
34
  ## Quick Start
31
35
 
32
36
  ### 1. Set Up Server-Side Auth
@@ -100,6 +104,42 @@ export default function Callback() {
100
104
  }
101
105
  ```
102
106
 
107
+ ### Default Auth (Zero Config)
108
+
109
+ When using `createAuthConfig()` with no args on the server, you can call login/logout with no config—sandbox clientId and redirectUri are used:
110
+
111
+ ```tsx
112
+ // With default auth - no config needed
113
+ function LoginButton() {
114
+ const { isAuthenticated } = useImmutableSession();
115
+ const { loginWithPopup, isLoggingIn, error } = useLogin();
116
+
117
+ if (isAuthenticated) return <p>You are logged in!</p>;
118
+
119
+ return (
120
+ <button onClick={() => loginWithPopup()} disabled={isLoggingIn}>
121
+ {isLoggingIn ? "Signing in..." : "Sign In"}
122
+ </button>
123
+ );
124
+ }
125
+ ```
126
+
127
+ Or with custom config (pass full LoginConfig/LogoutConfig when overriding):
128
+
129
+ ```tsx
130
+ // With custom config - pass complete config
131
+ loginWithPopup({
132
+ clientId: process.env.NEXT_PUBLIC_IMMUTABLE_CLIENT_ID!,
133
+ redirectUri: `${window.location.origin}/callback`,
134
+ });
135
+ logout({
136
+ clientId: process.env.NEXT_PUBLIC_IMMUTABLE_CLIENT_ID!,
137
+ logoutRedirectUri: process.env.NEXT_PUBLIC_BASE_URL!,
138
+ });
139
+ ```
140
+
141
+ See the [wallets-connect-with-nextjs](../../examples/passport/wallets-connect-with-nextjs) example for a full integration with `@imtbl/wallet`.
142
+
103
143
  ### 5. Add Login Button
104
144
 
105
145
  Use the `useLogin` hook for login flows with built-in state management:
@@ -395,7 +435,11 @@ With federated logout, the auth server's session is also cleared, so users can s
395
435
 
396
436
  ### `useImmutableSession()`
397
437
 
398
- A convenience hook that wraps `next-auth/react`'s `useSession` with a `getUser` function for wallet integration.
438
+ A convenience hook that wraps `next-auth/react`'s `useSession` with:
439
+
440
+ - `getAccessToken()` -- async function that returns a **guaranteed-fresh** access token
441
+ - `getUser()` -- function for wallet integration
442
+ - Automatic token refresh -- detects expired tokens and refreshes on demand
399
443
 
400
444
  ```tsx
401
445
  "use client";
@@ -404,10 +448,12 @@ import { useImmutableSession } from "@imtbl/auth-next-client";
404
448
 
405
449
  function MyComponent() {
406
450
  const {
407
- session, // Session with tokens
451
+ session, // Session metadata (user info, zkEvm, error) -- does NOT include accessToken
408
452
  status, // 'loading' | 'authenticated' | 'unauthenticated'
409
453
  isLoading, // True during initial load
410
454
  isAuthenticated, // True when logged in
455
+ isRefreshing, // True during token refresh
456
+ getAccessToken, // Async function: returns a guaranteed-fresh access token
411
457
  getUser, // Function for wallet integration
412
458
  } = useImmutableSession();
413
459
 
@@ -420,13 +466,131 @@ function MyComponent() {
420
466
 
421
467
  #### Return Value
422
468
 
423
- | Property | Type | Description |
424
- | ----------------- | --------------------------------------------------- | ---------------------------------------------------------------- |
425
- | `session` | `ImmutableSession \| null` | Session with access/refresh tokens |
426
- | `status` | `string` | Auth status: `'loading'`, `'authenticated'`, `'unauthenticated'` |
427
- | `isLoading` | `boolean` | Whether initial auth state is loading |
428
- | `isAuthenticated` | `boolean` | Whether user is authenticated |
429
- | `getUser` | `(forceRefresh?: boolean) => Promise<User \| null>` | Get user function for wallet integration |
469
+ | Property | Type | Description |
470
+ | ----------------- | --------------------------------------------------- | --------------------------------------------------------------------------------------- |
471
+ | `session` | `ImmutableSession \| null` | Session metadata (user, zkEvm, error). Does **not** include `accessToken` -- see below. |
472
+ | `status` | `string` | Auth status: `'loading'`, `'authenticated'`, `'unauthenticated'` |
473
+ | `isLoading` | `boolean` | Whether initial auth state is loading |
474
+ | `isAuthenticated` | `boolean` | Whether user is authenticated |
475
+ | `isRefreshing` | `boolean` | Whether a token refresh is in progress |
476
+ | `getAccessToken` | `() => Promise<string>` | Get a guaranteed-fresh access token. Throws if not authenticated or refresh fails. |
477
+ | `getUser` | `(forceRefresh?: boolean) => Promise<User \| null>` | Get user function for wallet integration |
478
+
479
+ #### Why no `accessToken` on `session`?
480
+
481
+ The `session` object intentionally does **not** expose `accessToken`. This is a deliberate design choice to prevent consumers from accidentally using a stale/expired token.
482
+
483
+ **Always use `getAccessToken()`** to obtain a token for authenticated requests:
484
+
485
+ ```tsx
486
+ // ✅ Correct - always fresh
487
+ const token = await getAccessToken();
488
+ await authenticatedGet("/api/data", token);
489
+
490
+ // ❌ Incorrect - session.accessToken does not exist on the type
491
+ const token = session?.accessToken; // TypeScript error
492
+ ```
493
+
494
+ `getAccessToken()` guarantees freshness:
495
+
496
+ - **Fast path**: If the current token is valid, returns immediately (no network call).
497
+ - **Slow path**: If the token is expired, triggers a server-side refresh and **blocks** (awaits) until the fresh token is available.
498
+ - **Deduplication**: Multiple concurrent calls share a single refresh request.
499
+
500
+ #### Checking Authentication Status
501
+
502
+ **Always use `isAuthenticated` to determine if a user is logged in.** Do not use the `session` object or `status` field directly for this purpose.
503
+
504
+ **Why not `!!session`?**
505
+
506
+ A `session` object can exist but be **unusable**. For example, the session may be present but the access token is missing, or a token refresh may have failed (indicated by `session.error === "RefreshTokenError"`). Checking `!!session` would incorrectly treat these broken sessions as authenticated.
507
+
508
+ **Why not `status === 'authenticated'`?**
509
+
510
+ The `status` field comes directly from NextAuth's `useSession` and only reflects whether NextAuth considers the session valid at the cookie/JWT level. It does **not** account for whether the access token is actually present or whether a token refresh has failed. A session can have `status === 'authenticated'` while `session.error` is set to `"RefreshTokenError"`, meaning the tokens are no longer usable.
511
+
512
+ **What `isAuthenticated` checks:**
513
+
514
+ The `isAuthenticated` boolean validates all of the following:
515
+
516
+ 1. NextAuth reports `'authenticated'` status
517
+ 2. The session object exists
518
+ 3. A valid access token is present in the session
519
+ 4. No session-level error exists (e.g., `RefreshTokenError`)
520
+
521
+ It also handles transient states gracefully — during session refetches (e.g., window focus) or manual refreshes (e.g., after wallet registration via `getUser(true)`), `isAuthenticated` remains `true` if the user was previously authenticated, preventing UI flicker.
522
+
523
+ ```tsx
524
+ // ✅ Correct - uses isAuthenticated
525
+ const { isAuthenticated } = useImmutableSession();
526
+ if (!isAuthenticated) return <div>Please log in</div>;
527
+
528
+ // ❌ Incorrect - session can exist with expired/invalid tokens
529
+ const { session } = useImmutableSession();
530
+ if (!session) return <div>Please log in</div>;
531
+
532
+ // ❌ Incorrect - status doesn't account for token errors
533
+ const { status } = useImmutableSession();
534
+ if (status !== "authenticated") return <div>Please log in</div>;
535
+ ```
536
+
537
+ #### Using `getAccessToken()` in Practice
538
+
539
+ **SWR fetcher:**
540
+
541
+ ```tsx
542
+ import useSWR from "swr";
543
+ import { useImmutableSession } from "@imtbl/auth-next-client";
544
+
545
+ function useProfile() {
546
+ const { getAccessToken, isAuthenticated } = useImmutableSession();
547
+
548
+ return useSWR(
549
+ isAuthenticated ? "/passport-profile/v1/profile" : null,
550
+ async (path) => {
551
+ const token = await getAccessToken(); // blocks until fresh
552
+ return authenticatedGet(path, token);
553
+ },
554
+ );
555
+ }
556
+ ```
557
+
558
+ **Event handler:**
559
+
560
+ ```tsx
561
+ import { useImmutableSession } from "@imtbl/auth-next-client";
562
+
563
+ function ClaimRewardButton({ questId }: { questId: string }) {
564
+ const { getAccessToken } = useImmutableSession();
565
+
566
+ const handleClaim = async () => {
567
+ const token = await getAccessToken(); // blocks until fresh
568
+ await authenticatedPost("/v1/quests/claim", token, { questId });
569
+ };
570
+
571
+ return <button onClick={handleClaim}>Claim</button>;
572
+ }
573
+ ```
574
+
575
+ **Periodic polling:**
576
+
577
+ ```tsx
578
+ import useSWR from "swr";
579
+ import { useImmutableSession } from "@imtbl/auth-next-client";
580
+
581
+ function ActivityFeed() {
582
+ const { getAccessToken, isAuthenticated } = useImmutableSession();
583
+
584
+ return useSWR(
585
+ isAuthenticated ? "/v1/activities" : null,
586
+ async (path) => {
587
+ const token = await getAccessToken();
588
+ return authenticatedGet(path, token);
589
+ },
590
+ { refreshInterval: 10000 }, // polls every 10s, always gets a fresh token
591
+ );
592
+ }
593
+ ```
430
594
 
431
595
  #### The `getUser` Function
432
596
 
@@ -452,13 +616,13 @@ When `forceRefresh` is `true`:
452
616
 
453
617
  ### ImmutableSession
454
618
 
455
- The session type returned by `useImmutableSession`:
619
+ The session type returned by `useImmutableSession`. Note that `accessToken` is intentionally **not** included -- use `getAccessToken()` instead to obtain a guaranteed-fresh token.
456
620
 
457
621
  ```typescript
458
622
  interface ImmutableSession {
459
- accessToken: string;
623
+ // accessToken is NOT exposed -- use getAccessToken() instead
460
624
  refreshToken?: string;
461
- idToken?: string;
625
+ idToken?: string; // Only present transiently after sign-in or token refresh (not stored in cookie)
462
626
  accessTokenExpires: number;
463
627
  zkEvm?: {
464
628
  ethAddress: string;
@@ -473,6 +637,8 @@ interface ImmutableSession {
473
637
  }
474
638
  ```
475
639
 
640
+ > **Note:** The `idToken` is **not** stored in the session cookie (to avoid CloudFront 413 errors from oversized headers). It is only present in the session response transiently after sign-in or token refresh. `@imtbl/auth-next-client` automatically persists it in `localStorage` so that `getUser()` always returns a valid `idToken` for wallet operations. All data extracted from the idToken (`email`, `nickname`, `zkEvm`) remains in the cookie as separate fields and is always available in the session.
641
+
476
642
  ### LoginConfig
477
643
 
478
644
  Configuration for the `useLogin` hook's login functions:
@@ -531,17 +697,19 @@ interface LogoutConfig {
531
697
 
532
698
  The session may contain an `error` field indicating authentication issues:
533
699
 
534
- | Error | Description | Handling |
535
- | --------------------- | --------------------- | --------------------------------------------- |
536
- | `"TokenExpired"` | Access token expired | Server-side refresh will happen automatically |
537
- | `"RefreshTokenError"` | Refresh token invalid | Prompt user to sign in again |
700
+ | Error | Description | Handling |
701
+ | --------------------- | --------------------- | -------------------------------------------- |
702
+ | `"TokenExpired"` | Access token expired | Proactive refresh handles this automatically |
703
+ | `"RefreshTokenError"` | Refresh token invalid | Prompt user to sign in again |
704
+
705
+ `getAccessToken()` throws an error if the token cannot be obtained (e.g., refresh failure). Handle it with try/catch:
538
706
 
539
707
  ```tsx
540
708
  import { useImmutableSession } from "@imtbl/auth-next-client";
541
- import { signIn, signOut } from "next-auth/react";
709
+ import { signOut } from "next-auth/react";
542
710
 
543
711
  function ProtectedContent() {
544
- const { session, isAuthenticated } = useImmutableSession();
712
+ const { session, isAuthenticated, getAccessToken } = useImmutableSession();
545
713
 
546
714
  if (session?.error === "RefreshTokenError") {
547
715
  return (
@@ -556,11 +724,20 @@ function ProtectedContent() {
556
724
  return (
557
725
  <div>
558
726
  <p>Please sign in to continue.</p>
559
- <button onClick={() => signIn()}>Sign In</button>
560
727
  </div>
561
728
  );
562
729
  }
563
730
 
731
+ const handleFetch = async () => {
732
+ try {
733
+ const token = await getAccessToken();
734
+ // Use token for authenticated requests
735
+ } catch (error) {
736
+ // Token refresh failed -- session may be expired
737
+ console.error("Failed to get access token:", error);
738
+ }
739
+ };
740
+
564
741
  return <div>Protected content here</div>;
565
742
  }
566
743
  ```
@@ -22,7 +22,15 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
22
22
  var src_exports = {};
23
23
  __export(src_exports, {
24
24
  CallbackPage: () => CallbackPage,
25
+ DEFAULT_AUDIENCE: () => DEFAULT_AUDIENCE,
26
+ DEFAULT_AUTH_DOMAIN: () => DEFAULT_AUTH_DOMAIN,
27
+ DEFAULT_LOGOUT_REDIRECT_URI_PATH: () => DEFAULT_LOGOUT_REDIRECT_URI_PATH,
28
+ DEFAULT_REDIRECT_URI_PATH: () => DEFAULT_REDIRECT_URI_PATH,
29
+ DEFAULT_SANDBOX_CLIENT_ID: () => DEFAULT_SANDBOX_CLIENT_ID,
30
+ DEFAULT_SCOPE: () => DEFAULT_SCOPE,
31
+ IMMUTABLE_PROVIDER_ID: () => IMMUTABLE_PROVIDER_ID,
25
32
  MarketingConsentStatus: () => import_auth3.MarketingConsentStatus,
33
+ deriveDefaultRedirectUri: () => deriveDefaultRedirectUri,
26
34
  useImmutableSession: () => useImmutableSession,
27
35
  useLogin: () => useLogin,
28
36
  useLogout: () => useLogout
@@ -36,9 +44,42 @@ var import_react2 = require("next-auth/react");
36
44
  var import_auth = require("@imtbl/auth");
37
45
 
38
46
  // src/constants.ts
47
+ var DEFAULT_AUTH_DOMAIN = "https://auth.immutable.com";
48
+ var DEFAULT_AUDIENCE = "platform_api";
49
+ var DEFAULT_SCOPE = "openid profile email offline_access transact";
39
50
  var IMMUTABLE_PROVIDER_ID = "immutable";
40
- var DEFAULT_TOKEN_EXPIRY_SECONDS = 900;
41
- var DEFAULT_TOKEN_EXPIRY_MS = DEFAULT_TOKEN_EXPIRY_SECONDS * 1e3;
51
+ var DEFAULT_SANDBOX_CLIENT_ID = "mjtCL8mt06BtbxSkp2vbrYStKWnXVZfo";
52
+ var DEFAULT_REDIRECT_URI_PATH = "/callback";
53
+ var DEFAULT_LOGOUT_REDIRECT_URI_PATH = "/";
54
+ var TOKEN_EXPIRY_BUFFER_MS = 6e4;
55
+
56
+ // src/idTokenStorage.ts
57
+ var ID_TOKEN_STORAGE_KEY = "imtbl_id_token";
58
+ function storeIdToken(idToken) {
59
+ try {
60
+ if (typeof window !== "undefined" && window.localStorage) {
61
+ window.localStorage.setItem(ID_TOKEN_STORAGE_KEY, idToken);
62
+ }
63
+ } catch {
64
+ }
65
+ }
66
+ function getStoredIdToken() {
67
+ try {
68
+ if (typeof window !== "undefined" && window.localStorage) {
69
+ return window.localStorage.getItem(ID_TOKEN_STORAGE_KEY) ?? void 0;
70
+ }
71
+ } catch {
72
+ }
73
+ return void 0;
74
+ }
75
+ function clearStoredIdToken() {
76
+ try {
77
+ if (typeof window !== "undefined" && window.localStorage) {
78
+ window.localStorage.removeItem(ID_TOKEN_STORAGE_KEY);
79
+ }
80
+ } catch {
81
+ }
82
+ }
42
83
 
43
84
  // src/callback.tsx
44
85
  var import_jsx_runtime = require("react/jsx-runtime");
@@ -89,6 +130,9 @@ function CallbackPage({
89
130
  window.close();
90
131
  } else {
91
132
  const tokenData = mapTokensToSignInData(tokens);
133
+ if (tokens.idToken) {
134
+ storeIdToken(tokens.idToken);
135
+ }
92
136
  const result = await (0, import_react2.signIn)(IMMUTABLE_PROVIDER_ID, {
93
137
  tokens: JSON.stringify(tokenData),
94
138
  redirect: false
@@ -181,6 +225,51 @@ function CallbackPage({
181
225
  var import_react3 = require("react");
182
226
  var import_react4 = require("next-auth/react");
183
227
  var import_auth2 = require("@imtbl/auth");
228
+
229
+ // src/defaultConfig.ts
230
+ function deriveDefaultRedirectUri() {
231
+ if (typeof window === "undefined") {
232
+ throw new Error(
233
+ "[auth-next-client] deriveDefaultRedirectUri requires window. Login hooks run in the browser when the user triggers login."
234
+ );
235
+ }
236
+ return `${window.location.origin}${DEFAULT_REDIRECT_URI_PATH}`;
237
+ }
238
+
239
+ // src/hooks.tsx
240
+ var pendingRefresh = null;
241
+ function deduplicatedUpdate(update) {
242
+ if (!pendingRefresh) {
243
+ pendingRefresh = update().finally(() => {
244
+ pendingRefresh = null;
245
+ });
246
+ }
247
+ return pendingRefresh;
248
+ }
249
+ function getSandboxLoginConfig() {
250
+ const redirectUri = deriveDefaultRedirectUri();
251
+ return {
252
+ clientId: DEFAULT_SANDBOX_CLIENT_ID,
253
+ redirectUri,
254
+ popupRedirectUri: redirectUri,
255
+ scope: DEFAULT_SCOPE,
256
+ audience: DEFAULT_AUDIENCE,
257
+ authenticationDomain: DEFAULT_AUTH_DOMAIN
258
+ };
259
+ }
260
+ function getSandboxLogoutConfig() {
261
+ if (typeof window === "undefined") {
262
+ throw new Error(
263
+ "[auth-next-client] getSandboxLogoutConfig requires window. Logout runs in the browser when the user triggers it."
264
+ );
265
+ }
266
+ const logoutRedirectUri = window.location.origin + DEFAULT_LOGOUT_REDIRECT_URI_PATH;
267
+ return {
268
+ clientId: DEFAULT_SANDBOX_CLIENT_ID,
269
+ logoutRedirectUri,
270
+ authenticationDomain: DEFAULT_AUTH_DOMAIN
271
+ };
272
+ }
184
273
  function useImmutableSession() {
185
274
  const { data: sessionData, status, update } = (0, import_react4.useSession)();
186
275
  const [isRefreshing, setIsRefreshing] = (0, import_react3.useState)(false);
@@ -197,6 +286,18 @@ function useImmutableSession() {
197
286
  updateRef.current = update;
198
287
  const setIsRefreshingRef = (0, import_react3.useRef)(setIsRefreshing);
199
288
  setIsRefreshingRef.current = setIsRefreshing;
289
+ (0, import_react3.useEffect)(() => {
290
+ if (!session?.accessTokenExpires) return;
291
+ const timeUntilExpiry = session.accessTokenExpires - Date.now() - TOKEN_EXPIRY_BUFFER_MS;
292
+ if (timeUntilExpiry <= 0) {
293
+ deduplicatedUpdate(() => updateRef.current());
294
+ }
295
+ }, [session?.accessTokenExpires]);
296
+ (0, import_react3.useEffect)(() => {
297
+ if (session?.idToken) {
298
+ storeIdToken(session.idToken);
299
+ }
300
+ }, [session?.idToken]);
200
301
  const getUser = (0, import_react3.useCallback)(async (forceRefresh) => {
201
302
  let currentSession;
202
303
  if (forceRefresh) {
@@ -206,6 +307,9 @@ function useImmutableSession() {
206
307
  currentSession = updatedSession;
207
308
  if (currentSession) {
208
309
  sessionRef.current = currentSession;
310
+ if (currentSession.idToken) {
311
+ storeIdToken(currentSession.idToken);
312
+ }
209
313
  }
210
314
  } catch (error) {
211
315
  console.error("[auth-next-client] Force refresh failed:", error);
@@ -213,6 +317,17 @@ function useImmutableSession() {
213
317
  } finally {
214
318
  setIsRefreshingRef.current(false);
215
319
  }
320
+ } else if (pendingRefresh) {
321
+ const refreshed = await pendingRefresh;
322
+ if (refreshed) {
323
+ currentSession = refreshed;
324
+ sessionRef.current = currentSession;
325
+ if (currentSession.idToken) {
326
+ storeIdToken(currentSession.idToken);
327
+ }
328
+ } else {
329
+ currentSession = sessionRef.current;
330
+ }
216
331
  } else {
217
332
  currentSession = sessionRef.current;
218
333
  }
@@ -226,7 +341,9 @@ function useImmutableSession() {
226
341
  return {
227
342
  accessToken: currentSession.accessToken,
228
343
  refreshToken: currentSession.refreshToken,
229
- idToken: currentSession.idToken,
344
+ // Prefer session idToken (fresh after sign-in or refresh, before useEffect
345
+ // stores it), fall back to localStorage for normal reads (cookie has no idToken).
346
+ idToken: currentSession.idToken || getStoredIdToken(),
230
347
  profile: {
231
348
  sub: currentSession.user?.sub ?? "",
232
349
  email: currentSession.user?.email ?? void 0,
@@ -235,19 +352,40 @@ function useImmutableSession() {
235
352
  zkEvm: currentSession.zkEvm
236
353
  };
237
354
  }, []);
355
+ const getAccessToken = (0, import_react3.useCallback)(async () => {
356
+ const currentSession = sessionRef.current;
357
+ if (currentSession?.accessToken && currentSession.accessTokenExpires && Date.now() < currentSession.accessTokenExpires - TOKEN_EXPIRY_BUFFER_MS && !currentSession.error) {
358
+ return currentSession.accessToken;
359
+ }
360
+ const refreshed = await deduplicatedUpdate(
361
+ () => updateRef.current()
362
+ );
363
+ if (!refreshed?.accessToken || refreshed.error) {
364
+ throw new Error(
365
+ `[auth-next-client] Failed to get access token: ${refreshed?.error || "no session"}`
366
+ );
367
+ }
368
+ sessionRef.current = refreshed;
369
+ return refreshed.accessToken;
370
+ }, []);
371
+ const publicSession = session;
238
372
  return {
239
- session,
373
+ session: publicSession,
240
374
  status,
241
375
  isLoading,
242
376
  isAuthenticated,
243
377
  isRefreshing,
244
- getUser
378
+ getUser,
379
+ getAccessToken
245
380
  };
246
381
  }
247
382
  function useLogin() {
248
383
  const [isLoggingIn, setIsLoggingIn] = (0, import_react3.useState)(false);
249
384
  const [error, setError] = (0, import_react3.useState)(null);
250
385
  const signInWithTokens = (0, import_react3.useCallback)(async (tokens) => {
386
+ if (tokens.idToken) {
387
+ storeIdToken(tokens.idToken);
388
+ }
251
389
  const result = await (0, import_react4.signIn)(IMMUTABLE_PROVIDER_ID, {
252
390
  tokens: JSON.stringify(tokens),
253
391
  redirect: false
@@ -263,7 +401,8 @@ function useLogin() {
263
401
  setIsLoggingIn(true);
264
402
  setError(null);
265
403
  try {
266
- const tokens = await (0, import_auth2.loginWithPopup)(config, options);
404
+ const fullConfig = config ?? getSandboxLoginConfig();
405
+ const tokens = await (0, import_auth2.loginWithPopup)(fullConfig, options);
267
406
  await signInWithTokens(tokens);
268
407
  } catch (err) {
269
408
  const errorMessage = err instanceof Error ? err.message : "Login failed";
@@ -277,7 +416,8 @@ function useLogin() {
277
416
  setIsLoggingIn(true);
278
417
  setError(null);
279
418
  try {
280
- const tokens = await (0, import_auth2.loginWithEmbedded)(config);
419
+ const fullConfig = config ?? getSandboxLoginConfig();
420
+ const tokens = await (0, import_auth2.loginWithEmbedded)(fullConfig);
281
421
  await signInWithTokens(tokens);
282
422
  } catch (err) {
283
423
  const errorMessage = err instanceof Error ? err.message : "Login failed";
@@ -291,7 +431,8 @@ function useLogin() {
291
431
  setIsLoggingIn(true);
292
432
  setError(null);
293
433
  try {
294
- await (0, import_auth2.loginWithRedirect)(config, options);
434
+ const fullConfig = config ?? getSandboxLoginConfig();
435
+ await (0, import_auth2.loginWithRedirect)(fullConfig, options);
295
436
  } catch (err) {
296
437
  const errorMessage = err instanceof Error ? err.message : "Login failed";
297
438
  setError(errorMessage);
@@ -314,8 +455,10 @@ function useLogout() {
314
455
  setIsLoggingOut(true);
315
456
  setError(null);
316
457
  try {
458
+ clearStoredIdToken();
317
459
  await (0, import_react4.signOut)({ redirect: false });
318
- (0, import_auth2.logoutWithRedirect)(config);
460
+ const fullConfig = config ?? getSandboxLogoutConfig();
461
+ (0, import_auth2.logoutWithRedirect)(fullConfig);
319
462
  } catch (err) {
320
463
  const errorMessage = err instanceof Error ? err.message : "Logout failed";
321
464
  setError(errorMessage);
@@ -335,7 +478,15 @@ var import_auth3 = require("@imtbl/auth");
335
478
  // Annotate the CommonJS export names for ESM import in node:
336
479
  0 && (module.exports = {
337
480
  CallbackPage,
481
+ DEFAULT_AUDIENCE,
482
+ DEFAULT_AUTH_DOMAIN,
483
+ DEFAULT_LOGOUT_REDIRECT_URI_PATH,
484
+ DEFAULT_REDIRECT_URI_PATH,
485
+ DEFAULT_SANDBOX_CLIENT_ID,
486
+ DEFAULT_SCOPE,
487
+ IMMUTABLE_PROVIDER_ID,
338
488
  MarketingConsentStatus,
489
+ deriveDefaultRedirectUri,
339
490
  useImmutableSession,
340
491
  useLogin,
341
492
  useLogout