@netlify/identity 0.3.0-alpha.7 → 0.3.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/README.md CHANGED
@@ -1,6 +1,7 @@
1
1
  # @netlify/identity
2
2
 
3
- A lightweight, no-config headless authentication library for projects using Netlify Identity for browser and server. Uses [gotrue-js](https://github.com/netlify/gotrue-js) under the hood.
3
+ A lightweight, no-config headless authentication library for projects using Netlify Identity. Works in both browser and server contexts.
4
+ This is NOT the Netlify Identity Widget. This library exports standalone async functions (e.g., import { login, getUser } from '@netlify/identity'). There is no class to instantiate and no .init() call. Just import the functions you need and call them.
4
5
 
5
6
  > **Status:** Beta. The API may change before 1.0.
6
7
 
@@ -17,14 +18,14 @@ A lightweight, no-config headless authentication library for projects using Netl
17
18
  | [`netlify-identity-widget`](https://github.com/netlify/netlify-identity-widget) | Pre-built login/signup modal (HTML + CSS) | You want a drop-in UI component with no custom design |
18
19
  | [`gotrue-js`](https://github.com/netlify/gotrue-js) | Low-level GoTrue HTTP client (browser only) | You're building your own auth wrapper and need direct API access |
19
20
 
20
- This library wraps `gotrue-js` in the browser and calls the GoTrue HTTP API directly on the server. It provides a unified API that works in both contexts, handles cookie management, and normalizes the user object. You do not need to install `gotrue-js` or the widget separately.
21
+ This library provides a unified API that works in both browser and server contexts, handles cookie management, and normalizes the user object. You do not need to install `gotrue-js` or the widget separately.
21
22
 
22
23
  ## Table of contents
23
24
 
24
25
  - [Installation](#installation)
25
26
  - [Quick start](#quick-start)
26
27
  - [API](#api)
27
- - [Functions](#functions) -- `getUser`, `login`, `signup`, `logout`, `oauthLogin`, `handleAuthCallback`, `onAuthChange`, `hydrateSession`, and more
28
+ - [Functions](#functions) -- `getUser`, `login`, `signup`, `logout`, `oauthLogin`, `handleAuthCallback`, `onAuthChange`, `hydrateSession`, `refreshSession`, and more
28
29
  - [Admin Operations](#admin-operations) -- `admin.listUsers`, `admin.getUser`, `admin.createUser`, `admin.updateUser`, `admin.deleteUser`
29
30
  - [Types](#types) -- `User`, `AuthEvent`, `CallbackResult`, `Settings`, `Admin`, `ListUsersOptions`, `CreateUserParams`, etc.
30
31
  - [Errors](#errors) -- `AuthError`, `MissingIdentityError`
@@ -94,7 +95,7 @@ export default async (req: Request, context: Context) => {
94
95
  getUser(): Promise<User | null>
95
96
  ```
96
97
 
97
- Returns the current authenticated user, or `null` if not logged in. Always returns a full `User` object with all available fields (email, roles, timestamps, metadata) regardless of whether the call happens in the browser or on the server. Never throws.
98
+ Returns the current authenticated user, or `null` if not logged in. Returns the best available normalized `User` from the current context. In the browser or when the server can reach the Identity API, all fields are populated. When falling back to JWT claims (e.g., Identity API unreachable), fields like `createdAt`, `updatedAt`, `emailVerified`, and `rawGoTrueData` may be missing. Never throws.
98
99
 
99
100
  > **Next.js note:** Calling `getUser()` in a Server Component opts the page into [dynamic rendering](https://nextjs.org/docs/app/building-your-application/rendering/server-components#dynamic-rendering) because it reads cookies. This is expected and correct for authenticated pages. Next.js handles the internal dynamic rendering signal automatically.
100
101
 
@@ -132,7 +133,7 @@ login(email: string, password: string): Promise<User>
132
133
 
133
134
  Logs in with email and password. Works in both browser and server contexts.
134
135
 
135
- In the browser, uses gotrue-js and emits a `'login'` event. On the server (Netlify Functions, Edge Functions), calls the GoTrue HTTP API directly and sets the `nf_jwt` cookie via the Netlify runtime.
136
+ In the browser, emits a `'login'` event. On the server (Netlify Functions, Edge Functions), calls the Identity API directly and sets the `nf_jwt` cookie via the Netlify runtime.
136
137
 
137
138
  **Throws:** `AuthError` on invalid credentials or network failure. In the browser, `MissingIdentityError` if Identity is not configured. On the server, `AuthError` if the Netlify Functions runtime is not available.
138
139
 
@@ -158,7 +159,7 @@ logout(): Promise<void>
158
159
 
159
160
  Logs out the current user and clears the session. Works in both browser and server contexts.
160
161
 
161
- In the browser, uses gotrue-js and emits a `'logout'` event. On the server, calls GoTrue's `/logout` endpoint with the JWT from the `nf_jwt` cookie, then deletes the cookie. Auth cookies are always cleared, even if the GoTrue call fails.
162
+ In the browser, emits a `'logout'` event. On the server, calls the Identity `/logout` endpoint with the JWT from the `nf_jwt` cookie, then deletes the cookie. Auth cookies are always cleared, even if the server call fails.
162
163
 
163
164
  **Throws:** In the browser, `MissingIdentityError` if Identity is not configured. On the server, `AuthError` if the Netlify Functions runtime is not available.
164
165
 
@@ -198,11 +199,11 @@ Subscribes to auth state changes (login, logout, token refresh, user updates, an
198
199
  hydrateSession(): Promise<User | null>
199
200
  ```
200
201
 
201
- Bootstraps the browser-side gotrue-js session from server-set auth cookies (`nf_jwt`, `nf_refresh`). Returns the hydrated `User`, or `null` if no auth cookies are present. No-op on the server.
202
+ Bootstraps the browser-side session from server-set auth cookies (`nf_jwt`, `nf_refresh`). Returns the hydrated `User`, or `null` if no auth cookies are present. No-op on the server.
202
203
 
203
- **When to use:** After a server-side login (e.g., via a Netlify Function or Server Action), the `nf_jwt` cookie is set but gotrue-js has no browser session yet. `getUser()` calls `hydrateSession()` automatically, but account operations like `updateUser()` or `verifyEmailChange()` require a live gotrue-js session. Call `hydrateSession()` explicitly if you need the session ready before calling those operations.
204
+ **When to use:** After a server-side login (e.g., via a Netlify Function or Server Action), the `nf_jwt` cookie is set but no browser session exists yet. `getUser()` calls `hydrateSession()` automatically, but account operations like `updateUser()` or `verifyEmailChange()` require a live browser session. Call `hydrateSession()` explicitly if you need the session ready before calling those operations.
204
205
 
205
- If a gotrue-js session already exists (e.g., from a browser-side login), this is a no-op and returns the existing user.
206
+ If a browser session already exists (e.g., from a browser-side login), this is a no-op and returns the existing user.
206
207
 
207
208
  ```ts
208
209
  import { hydrateSession, updateUser } from '@netlify/identity'
@@ -214,6 +215,30 @@ await hydrateSession()
214
215
  await updateUser({ data: { full_name: 'Jane Doe' } })
215
216
  ```
216
217
 
218
+ #### `refreshSession`
219
+
220
+ ```ts
221
+ refreshSession(): Promise<string | null>
222
+ ```
223
+
224
+ Refreshes an expired or near-expired session. Returns the new access token on success, or `null` if no refresh is needed or the refresh token is invalid/missing.
225
+
226
+ **Browser:** Checks if the current access token is near expiry and refreshes it if needed, syncing the new token to the `nf_jwt` cookie. Note: the library automatically refreshes tokens in the background after any browser flow that establishes a session (`login()`, `signup()`, `hydrateSession()`, `handleAuthCallback()`, `confirmEmail()`, `recoverPassword()`, `acceptInvite()`), so you typically don't need to call this manually. `getUser()` also restarts the refresh timer when it finds an existing session. Browser-side errors return `null`, not an `AuthError`.
227
+
228
+ **Server:** Reads the `nf_jwt` and `nf_refresh` cookies. If the access token is expired or within 60 seconds of expiry, exchanges the refresh token for a new access token via the Identity `/token` endpoint and updates both cookies on the response. Call this in framework middleware or at the start of server-side request handlers to ensure the JWT is valid for downstream processing.
229
+
230
+ **Throws:** `AuthError` on network failure or if the Identity endpoint URL cannot be determined. Does **not** throw for invalid/expired refresh tokens (returns `null` instead).
231
+
232
+ ```ts
233
+ // Example: Astro middleware
234
+ import { refreshSession } from '@netlify/identity'
235
+
236
+ export async function onRequest(context, next) {
237
+ await refreshSession()
238
+ return next()
239
+ }
240
+ ```
241
+
217
242
  #### `requestPasswordRecovery`
218
243
 
219
244
  ```ts
@@ -279,7 +304,7 @@ Updates the current user's metadata or credentials. Requires an active session.
279
304
  The `admin` namespace provides user management functions for administrators. These work in two contexts:
280
305
 
281
306
  - **Server:** Uses the operator token from the Netlify runtime for full admin access. No logged-in user required.
282
- - **Browser:** Uses the logged-in user's JWT via gotrue-js. The user must have an admin role.
307
+ - **Browser:** Uses the logged-in user's JWT. The user must have an admin role.
283
308
 
284
309
  ```ts
285
310
  import { admin } from '@netlify/identity'
@@ -315,7 +340,7 @@ export default async (req: Request, context: Context) => {
315
340
  admin.listUsers(options?: ListUsersOptions): Promise<User[]>
316
341
  ```
317
342
 
318
- Lists all users. Pagination options are supported on the server; they are ignored in the browser (gotrue-js does not support pagination for this method).
343
+ Lists all users. Pagination options are supported on the server; they are ignored in the browser.
319
344
 
320
345
  **Throws:** `AuthError` if the operator token is missing (server) or no user is logged in (browser).
321
346
 
@@ -345,7 +370,7 @@ Creates a new user. The user is auto-confirmed. Optional `data` is spread into t
345
370
  admin.updateUser(userId: string, attributes: AdminUserUpdates): Promise<User>
346
371
  ```
347
372
 
348
- Updates an existing user by ID. Pass any attributes to change (e.g., `{ email: 'new@example.com' }`). See {@link AdminUserUpdates} for typed fields.
373
+ Updates an existing user by ID. Pass any attributes to change (e.g., `{ email: 'new@example.com' }`). See `AdminUserUpdates` for typed fields.
349
374
 
350
375
  **Throws:** `AuthError` if the user is not found or the update fails.
351
376
 
@@ -460,7 +485,7 @@ interface ListUsersOptions {
460
485
  }
461
486
  ```
462
487
 
463
- Pagination options for `admin.listUsers()`. Only used on the server; pagination is ignored in the browser (gotrue-js limitation).
488
+ Pagination options for `admin.listUsers()`. Only used on the server; pagination is ignored in the browser.
464
489
 
465
490
  #### `CreateUserParams`
466
491
 
@@ -472,7 +497,7 @@ interface CreateUserParams {
472
497
  }
473
498
  ```
474
499
 
475
- Parameters for `admin.createUser()`. Optional `data` is spread into the GoTrue request body as top-level attributes (use it to set `app_metadata`, `user_metadata`, `role`, etc.).
500
+ Parameters for `admin.createUser()`. Optional `data` is spread into the request body as top-level attributes (use it to set `app_metadata`, `user_metadata`, `role`, etc.).
476
501
 
477
502
  #### `Admin`
478
503
 
@@ -502,13 +527,13 @@ const AUTH_EVENTS: {
502
527
 
503
528
  Constants for auth event names. Use these instead of string literals for type safety and autocomplete.
504
529
 
505
- | Event | When it fires |
506
- | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
507
- | `LOGIN` | `login()`, `signup()` (with autoconfirm), `recoverPassword()`, `handleAuthCallback()` (OAuth/confirmation), `hydrateSession()` |
508
- | `LOGOUT` | `logout()` |
509
- | `TOKEN_REFRESH` | gotrue-js refreshes an expiring access token in the background |
510
- | `USER_UPDATED` | `updateUser()`, `verifyEmailChange()`, `handleAuthCallback()` (email change) |
511
- | `RECOVERY` | `handleAuthCallback()` (recovery token only). The user is authenticated but has **not** set a new password yet. Listen for this to redirect to a password reset form. `recoverPassword()` emits `LOGIN` instead because it completes both steps (token redemption + password change). |
530
+ | Event | When it fires |
531
+ | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
532
+ | `LOGIN` | `login()`, `signup()` (with autoconfirm), `recoverPassword()`, `confirmEmail()`, `acceptInvite()`, `handleAuthCallback()` (OAuth/confirmation), `hydrateSession()` |
533
+ | `LOGOUT` | `logout()` |
534
+ | `TOKEN_REFRESH` | The library's auto-refresh timer refreshes an expiring access token and syncs the new token to the `nf_jwt` cookie. Fires automatically after any session-establishing flow: `login()`, `signup()`, `hydrateSession()`, `handleAuthCallback()`, `confirmEmail()`, `recoverPassword()`, `acceptInvite()`. `getUser()` also restarts the timer when it finds an existing session. |
535
+ | `USER_UPDATED` | `updateUser()`, `verifyEmailChange()`, `handleAuthCallback()` (email change) |
536
+ | `RECOVERY` | `handleAuthCallback()` (recovery token only). The user is authenticated but has **not** set a new password yet. Listen for this to redirect to a password reset form. `recoverPassword()` emits `LOGIN` instead because it completes both steps (token redemption + password change). |
512
537
 
513
538
  #### `AuthEvent`
514
539
 
@@ -564,7 +589,7 @@ For SSR frameworks (Next.js, Remix, Astro, TanStack Start), the recommended patt
564
589
  - **Browser-side** for auth mutations: `login()`, `signup()`, `logout()`, `oauthLogin()`
565
590
  - **Server-side** for reading auth state: `getUser()`, `getSettings()`, `getIdentityConfig()`
566
591
 
567
- Browser-side auth mutations call the GoTrue API directly from the browser, set the `nf_jwt` cookie and gotrue-js localStorage, and emit `onAuthChange` events. This keeps the client UI in sync immediately. Server-side reads work because the cookie is sent with every request.
592
+ Browser-side auth mutations call the Identity API directly from the browser, set the `nf_jwt` cookie, and emit `onAuthChange` events. This keeps the client UI in sync immediately. Server-side reads work because the cookie is sent with every request.
568
593
 
569
594
  The library also supports server-side mutations (`login()`, `signup()`, `logout()` inside Netlify Functions), but these require the Netlify Functions runtime to set cookies. After a server-side mutation, you need a full page navigation so the browser sends the new cookie.
570
595
 
@@ -1010,14 +1035,16 @@ if (result?.type === 'invite' && result.token) {
1010
1035
 
1011
1036
  ### Session lifetime
1012
1037
 
1013
- Sessions are managed by Netlify Identity (GoTrue) on the server side. The library stores two cookies:
1038
+ Sessions are managed by Netlify Identity on the server side. The library stores two cookies:
1014
1039
 
1015
- - **`nf_jwt`**: A short-lived JWT access token (default: 1 hour). Automatically refreshed by gotrue-js in the browser using the refresh token.
1040
+ - **`nf_jwt`**: A short-lived JWT access token (default: 1 hour).
1016
1041
  - **`nf_refresh`**: A long-lived refresh token used to obtain new access tokens without re-authenticating.
1017
1042
 
1018
- In the browser, gotrue-js handles token refresh automatically in the background. On the server, the access token in the `nf_jwt` cookie is validated as-is; if it has expired, `getUser()` returns `null`. The user will need to refresh the page (which triggers a browser-side token refresh) or log in again.
1043
+ **Browser auto-refresh:** After any session-establishing flow (`login()`, `signup()`, `hydrateSession()`, `handleAuthCallback()`, `confirmEmail()`, `recoverPassword()`, `acceptInvite()`), the library automatically schedules a background refresh 60 seconds before the access token expires. `getUser()` also restarts the refresh timer when it finds an existing session (e.g., after a page reload). When the refresh fires, it obtains a new access token, syncs it to the `nf_jwt` cookie, and emits a `TOKEN_REFRESH` event. This keeps the cookie fresh as long as the user has the tab open. If the refresh fails (e.g., the refresh token was revoked), the timer stops and the user will need to log in again.
1044
+
1045
+ **Server-side refresh:** On the server, the access token in the `nf_jwt` cookie is validated as-is. If it has expired and no refresh happens, `getUser()` returns `null`. To handle this, call `refreshSession()` in your framework middleware or request handler. This checks if the token is near expiry, exchanges the refresh token for a new one, and updates the cookies on the response.
1019
1046
 
1020
- Session lifetime is configured in your GoTrue/Identity server settings, not in this library.
1047
+ Session lifetime is configured in your Netlify Identity settings, not in this library.
1021
1048
 
1022
1049
  ## License
1023
1050
 
package/dist/index.cjs CHANGED
@@ -47,6 +47,7 @@ __export(index_exports, {
47
47
  oauthLogin: () => oauthLogin,
48
48
  onAuthChange: () => onAuthChange,
49
49
  recoverPassword: () => recoverPassword,
50
+ refreshSession: () => refreshSession,
50
51
  requestPasswordRecovery: () => requestPasswordRecovery,
51
52
  signup: () => signup,
52
53
  updateUser: () => updateUser,
@@ -271,6 +272,105 @@ var onAuthChange = (callback) => {
271
272
  };
272
273
  };
273
274
 
275
+ // src/refresh.ts
276
+ var REFRESH_MARGIN_S = 60;
277
+ var refreshTimer = null;
278
+ var startTokenRefresh = () => {
279
+ if (!isBrowser()) return;
280
+ stopTokenRefresh();
281
+ const client = getGoTrueClient();
282
+ const user = client?.currentUser();
283
+ if (!user) return;
284
+ const token = user.tokenDetails();
285
+ if (!token?.expires_at) return;
286
+ const nowS = Math.floor(Date.now() / 1e3);
287
+ const expiresAtS = typeof token.expires_at === "number" && token.expires_at > 1e12 ? Math.floor(token.expires_at / 1e3) : token.expires_at;
288
+ const delayMs = Math.max(0, expiresAtS - nowS - REFRESH_MARGIN_S) * 1e3;
289
+ refreshTimer = setTimeout(async () => {
290
+ try {
291
+ const freshJwt = await user.jwt(true);
292
+ const freshDetails = user.tokenDetails();
293
+ setBrowserAuthCookies(freshJwt, freshDetails?.refresh_token);
294
+ emitAuthEvent(AUTH_EVENTS.TOKEN_REFRESH, toUser(user));
295
+ startTokenRefresh();
296
+ } catch {
297
+ stopTokenRefresh();
298
+ }
299
+ }, delayMs);
300
+ };
301
+ var stopTokenRefresh = () => {
302
+ if (refreshTimer !== null) {
303
+ clearTimeout(refreshTimer);
304
+ refreshTimer = null;
305
+ }
306
+ };
307
+ var refreshSession = async () => {
308
+ if (isBrowser()) {
309
+ const client = getGoTrueClient();
310
+ const user = client?.currentUser();
311
+ if (!user) return null;
312
+ const details = user.tokenDetails();
313
+ if (details?.expires_at) {
314
+ const nowS2 = Math.floor(Date.now() / 1e3);
315
+ const expiresAtS = typeof details.expires_at === "number" && details.expires_at > 1e12 ? Math.floor(details.expires_at / 1e3) : details.expires_at;
316
+ if (expiresAtS - nowS2 > REFRESH_MARGIN_S) {
317
+ return null;
318
+ }
319
+ }
320
+ try {
321
+ const jwt = await user.jwt(true);
322
+ setBrowserAuthCookies(jwt, user.tokenDetails()?.refresh_token);
323
+ emitAuthEvent(AUTH_EVENTS.TOKEN_REFRESH, toUser(user));
324
+ startTokenRefresh();
325
+ return jwt;
326
+ } catch {
327
+ stopTokenRefresh();
328
+ return null;
329
+ }
330
+ }
331
+ const accessToken = getServerCookie(NF_JWT_COOKIE);
332
+ const refreshToken = getServerCookie(NF_REFRESH_COOKIE);
333
+ if (!accessToken || !refreshToken) return null;
334
+ const decoded = decodeJwtPayload(accessToken);
335
+ if (!decoded?.exp) return null;
336
+ const nowS = Math.floor(Date.now() / 1e3);
337
+ if (decoded.exp - nowS > REFRESH_MARGIN_S) {
338
+ return null;
339
+ }
340
+ const ctx = getIdentityContext();
341
+ const identityUrl = ctx?.url ?? (globalThis.Netlify?.context?.url ? new URL(IDENTITY_PATH, globalThis.Netlify.context.url).href : null);
342
+ if (!identityUrl) {
343
+ throw new AuthError("Could not determine the Identity endpoint URL for token refresh");
344
+ }
345
+ let res;
346
+ try {
347
+ res = await fetch(`${identityUrl}/token`, {
348
+ method: "POST",
349
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
350
+ body: new URLSearchParams({ grant_type: "refresh_token", refresh_token: refreshToken }).toString()
351
+ });
352
+ } catch (error) {
353
+ throw AuthError.from(error);
354
+ }
355
+ if (!res.ok) {
356
+ const errorBody = await res.json().catch(() => ({}));
357
+ if (res.status === 401 || res.status === 400) {
358
+ const cookies2 = globalThis.Netlify?.context?.cookies;
359
+ if (cookies2) {
360
+ deleteAuthCookies(cookies2);
361
+ }
362
+ return null;
363
+ }
364
+ throw new AuthError(errorBody.msg || `Token refresh failed (${res.status})`, res.status);
365
+ }
366
+ const data = await res.json();
367
+ const cookies = globalThis.Netlify?.context?.cookies;
368
+ if (cookies) {
369
+ setAuthCookies(cookies, data.access_token, data.refresh_token);
370
+ }
371
+ return data.access_token;
372
+ };
373
+
274
374
  // src/auth.ts
275
375
  var getCookies = () => {
276
376
  const cookies = globalThis.Netlify?.context?.cookies;
@@ -335,6 +435,7 @@ var login = async (email, password) => {
335
435
  const jwt = await gotrueUser.jwt();
336
436
  setBrowserAuthCookies(jwt, gotrueUser.tokenDetails()?.refresh_token);
337
437
  const user = toUser(gotrueUser);
438
+ startTokenRefresh();
338
439
  emitAuthEvent(AUTH_EVENTS.LOGIN, user);
339
440
  return user;
340
441
  } catch (error) {
@@ -379,6 +480,7 @@ var signup = async (email, password, data) => {
379
480
  const refreshToken = response.tokenDetails?.()?.refresh_token;
380
481
  setBrowserAuthCookies(jwt, refreshToken);
381
482
  }
483
+ startTokenRefresh();
382
484
  emitAuthEvent(AUTH_EVENTS.LOGIN, user);
383
485
  }
384
486
  return user;
@@ -410,6 +512,7 @@ var logout = async () => {
410
512
  await currentUser.logout();
411
513
  }
412
514
  deleteBrowserAuthCookies();
515
+ stopTokenRefresh();
413
516
  emitAuthEvent(AUTH_EVENTS.LOGOUT, null);
414
517
  } catch (error) {
415
518
  throw AuthError.from(error);
@@ -462,6 +565,7 @@ var handleOAuthCallback = async (client, params, accessToken) => {
462
565
  );
463
566
  setBrowserAuthCookies(accessToken, refreshToken || void 0);
464
567
  const user = toUser(gotrueUser);
568
+ startTokenRefresh();
465
569
  clearHash();
466
570
  emitAuthEvent(AUTH_EVENTS.LOGIN, user);
467
571
  return { type: "oauth", user };
@@ -471,6 +575,7 @@ var handleConfirmationCallback = async (client, token) => {
471
575
  const jwt = await gotrueUser.jwt();
472
576
  setBrowserAuthCookies(jwt, gotrueUser.tokenDetails()?.refresh_token);
473
577
  const user = toUser(gotrueUser);
578
+ startTokenRefresh();
474
579
  clearHash();
475
580
  emitAuthEvent(AUTH_EVENTS.LOGIN, user);
476
581
  return { type: "confirmation", user };
@@ -480,6 +585,7 @@ var handleRecoveryCallback = async (client, token) => {
480
585
  const jwt = await gotrueUser.jwt();
481
586
  setBrowserAuthCookies(jwt, gotrueUser.tokenDetails()?.refresh_token);
482
587
  const user = toUser(gotrueUser);
588
+ startTokenRefresh();
483
589
  clearHash();
484
590
  emitAuthEvent(AUTH_EVENTS.RECOVERY, user);
485
591
  return { type: "recovery", user };
@@ -523,7 +629,10 @@ var hydrateSession = async () => {
523
629
  if (!isBrowser()) return null;
524
630
  const client = getClient();
525
631
  const currentUser = client.currentUser();
526
- if (currentUser) return toUser(currentUser);
632
+ if (currentUser) {
633
+ startTokenRefresh();
634
+ return toUser(currentUser);
635
+ }
527
636
  const accessToken = getCookie(NF_JWT_COOKIE);
528
637
  if (!accessToken) return null;
529
638
  const refreshToken = getCookie(NF_REFRESH_COOKIE) ?? "";
@@ -547,6 +656,7 @@ var hydrateSession = async () => {
547
656
  return null;
548
657
  }
549
658
  const user = toUser(gotrueUser);
659
+ startTokenRefresh();
550
660
  emitAuthEvent(AUTH_EVENTS.LOGIN, user);
551
661
  return user;
552
662
  };
@@ -641,6 +751,7 @@ var getUser = async () => {
641
751
  }
642
752
  return null;
643
753
  }
754
+ startTokenRefresh();
644
755
  return toUser(currentUser);
645
756
  }
646
757
  const jwt = getCookie(NF_JWT_COOKIE);
@@ -648,8 +759,7 @@ var getUser = async () => {
648
759
  const claims2 = decodeJwtPayload(jwt);
649
760
  if (!claims2) return null;
650
761
  const hydrated = await hydrateSession();
651
- if (hydrated) return hydrated;
652
- return claimsToUser(claims2);
762
+ return hydrated ?? null;
653
763
  }
654
764
  triggerNextjsDynamic();
655
765
  const identityContext = globalThis.netlifyIdentityContext;
@@ -661,7 +771,7 @@ var getUser = async () => {
661
771
  if (fullUser) return fullUser;
662
772
  }
663
773
  }
664
- const claims = identityContext?.user ?? (serverJwt ? decodeJwtPayload(serverJwt) : null);
774
+ const claims = identityContext?.user ?? null;
665
775
  return claims ? claimsToUser(claims) : null;
666
776
  };
667
777
  var isAuthenticated = async () => await getUser() !== null;
@@ -724,6 +834,7 @@ var recoverPassword = async (token, newPassword) => {
724
834
  const gotrueUser = await client.recover(token, persistSession);
725
835
  const updatedUser = await gotrueUser.update({ password: newPassword });
726
836
  const user = toUser(updatedUser);
837
+ startTokenRefresh();
727
838
  emitAuthEvent(AUTH_EVENTS.LOGIN, user);
728
839
  return user;
729
840
  } catch (error) {
@@ -735,6 +846,7 @@ var confirmEmail = async (token) => {
735
846
  try {
736
847
  const gotrueUser = await client.confirm(token, persistSession);
737
848
  const user = toUser(gotrueUser);
849
+ startTokenRefresh();
738
850
  emitAuthEvent(AUTH_EVENTS.LOGIN, user);
739
851
  return user;
740
852
  } catch (error) {
@@ -746,6 +858,7 @@ var acceptInvite = async (token, password) => {
746
858
  try {
747
859
  const gotrueUser = await client.acceptInvite(token, password, persistSession);
748
860
  const user = toUser(gotrueUser);
861
+ startTokenRefresh();
749
862
  emitAuthEvent(AUTH_EVENTS.LOGIN, user);
750
863
  return user;
751
864
  } catch (error) {
@@ -943,6 +1056,7 @@ var admin = { listUsers, getUser: getUser2, createUser, updateUser: updateUser2,
943
1056
  oauthLogin,
944
1057
  onAuthChange,
945
1058
  recoverPassword,
1059
+ refreshSession,
946
1060
  requestPasswordRecovery,
947
1061
  signup,
948
1062
  updateUser,