@netlify/identity 0.1.1-alpha.0 → 0.1.1-alpha.10

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/index.d.cts CHANGED
@@ -30,6 +30,11 @@ interface User {
30
30
  /**
31
31
  * Returns the currently authenticated user, or `null` if not logged in.
32
32
  * Synchronous. Never throws.
33
+ *
34
+ * In the browser, checks gotrue-js localStorage first. If no localStorage
35
+ * session exists, falls back to decoding the `nf_jwt` cookie (set by
36
+ * server-side login). This gives immediate synchronous read access without
37
+ * waiting for async hydration via `hydrateSession()`.
33
38
  */
34
39
  declare const getUser: () => User | null;
35
40
  /**
@@ -59,11 +64,11 @@ type AuthCallback = (event: AuthEvent, user: User | null) => void;
59
64
  * Returns an unsubscribe function. No-op on the server.
60
65
  */
61
66
  declare const onAuthChange: (callback: AuthCallback) => (() => void);
62
- /** Logs in with email and password. Browser only. */
67
+ /** Logs in with email and password. Works in both browser and server contexts. */
63
68
  declare const login: (email: string, password: string) => Promise<User>;
64
- /** Creates a new account. Emits 'login' if autoconfirm is enabled. Browser only. */
69
+ /** Creates a new account. Emits 'login' if autoconfirm is enabled. Works in both browser and server contexts. */
65
70
  declare const signup: (email: string, password: string, data?: Record<string, unknown>) => Promise<User>;
66
- /** Logs out the current user and clears the session. Browser only. */
71
+ /** Logs out the current user and clears the session. Works in both browser and server contexts. */
67
72
  declare const logout: () => Promise<void>;
68
73
  /** Redirects to an OAuth provider. Always throws (the page navigates away). Browser only. */
69
74
  declare const oauthLogin: (provider: string) => never;
@@ -100,9 +105,15 @@ declare const recoverPassword: (token: string, newPassword: string) => Promise<U
100
105
  declare const confirmEmail: (token: string) => Promise<User>;
101
106
  /** Accepts an invite token and sets a password for the new account. Logs the user in on success. */
102
107
  declare const acceptInvite: (token: string, password: string) => Promise<User>;
103
- /** Verifies an email change using the token from a verification email. */
108
+ /**
109
+ * Verifies an email change using the token from a verification email.
110
+ * Auto-hydrates from auth cookies if no browser session exists.
111
+ */
104
112
  declare const verifyEmailChange: (token: string) => Promise<User>;
105
- /** Updates the current user's metadata or credentials. Requires an active session. */
113
+ /**
114
+ * Updates the current user's metadata or credentials.
115
+ * Auto-hydrates from auth cookies if no browser session exists.
116
+ */
106
117
  declare const updateUser: (updates: Record<string, unknown>) => Promise<User>;
107
118
 
108
119
  export { type AppMetadata, type AuthCallback, AuthError, type AuthEvent, type AuthProvider, type CallbackResult, type IdentityConfig, MissingIdentityError, type Settings, type User, acceptInvite, confirmEmail, getIdentityConfig, getSettings, getUser, handleAuthCallback, isAuthenticated, login, logout, oauthLogin, onAuthChange, recoverPassword, requestPasswordRecovery, signup, updateUser, verifyEmailChange };
package/dist/index.d.ts CHANGED
@@ -30,6 +30,11 @@ interface User {
30
30
  /**
31
31
  * Returns the currently authenticated user, or `null` if not logged in.
32
32
  * Synchronous. Never throws.
33
+ *
34
+ * In the browser, checks gotrue-js localStorage first. If no localStorage
35
+ * session exists, falls back to decoding the `nf_jwt` cookie (set by
36
+ * server-side login). This gives immediate synchronous read access without
37
+ * waiting for async hydration via `hydrateSession()`.
33
38
  */
34
39
  declare const getUser: () => User | null;
35
40
  /**
@@ -59,11 +64,11 @@ type AuthCallback = (event: AuthEvent, user: User | null) => void;
59
64
  * Returns an unsubscribe function. No-op on the server.
60
65
  */
61
66
  declare const onAuthChange: (callback: AuthCallback) => (() => void);
62
- /** Logs in with email and password. Browser only. */
67
+ /** Logs in with email and password. Works in both browser and server contexts. */
63
68
  declare const login: (email: string, password: string) => Promise<User>;
64
- /** Creates a new account. Emits 'login' if autoconfirm is enabled. Browser only. */
69
+ /** Creates a new account. Emits 'login' if autoconfirm is enabled. Works in both browser and server contexts. */
65
70
  declare const signup: (email: string, password: string, data?: Record<string, unknown>) => Promise<User>;
66
- /** Logs out the current user and clears the session. Browser only. */
71
+ /** Logs out the current user and clears the session. Works in both browser and server contexts. */
67
72
  declare const logout: () => Promise<void>;
68
73
  /** Redirects to an OAuth provider. Always throws (the page navigates away). Browser only. */
69
74
  declare const oauthLogin: (provider: string) => never;
@@ -100,9 +105,15 @@ declare const recoverPassword: (token: string, newPassword: string) => Promise<U
100
105
  declare const confirmEmail: (token: string) => Promise<User>;
101
106
  /** Accepts an invite token and sets a password for the new account. Logs the user in on success. */
102
107
  declare const acceptInvite: (token: string, password: string) => Promise<User>;
103
- /** Verifies an email change using the token from a verification email. */
108
+ /**
109
+ * Verifies an email change using the token from a verification email.
110
+ * Auto-hydrates from auth cookies if no browser session exists.
111
+ */
104
112
  declare const verifyEmailChange: (token: string) => Promise<User>;
105
- /** Updates the current user's metadata or credentials. Requires an active session. */
113
+ /**
114
+ * Updates the current user's metadata or credentials.
115
+ * Auto-hydrates from auth cookies if no browser session exists.
116
+ */
106
117
  declare const updateUser: (updates: Record<string, unknown>) => Promise<User>;
107
118
 
108
119
  export { type AppMetadata, type AuthCallback, AuthError, type AuthEvent, type AuthProvider, type CallbackResult, type IdentityConfig, MissingIdentityError, type Settings, type User, acceptInvite, confirmEmail, getIdentityConfig, getSettings, getUser, handleAuthCallback, isAuthenticated, login, logout, oauthLogin, onAuthChange, recoverPassword, requestPasswordRecovery, signup, updateUser, verifyEmailChange };
package/dist/index.js CHANGED
@@ -23,6 +23,7 @@ var MissingIdentityError = class extends Error {
23
23
  };
24
24
 
25
25
  // src/environment.ts
26
+ var IDENTITY_PATH = "/.netlify/identity";
26
27
  var goTrueClient = null;
27
28
  var cachedApiUrl;
28
29
  var warnedMissingUrl = false;
@@ -30,13 +31,15 @@ var isBrowser = () => typeof window !== "undefined" && typeof window.location !=
30
31
  var discoverApiUrl = () => {
31
32
  if (cachedApiUrl !== void 0) return cachedApiUrl;
32
33
  if (isBrowser()) {
33
- cachedApiUrl = `${window.location.origin}/.netlify/identity`;
34
+ cachedApiUrl = `${window.location.origin}${IDENTITY_PATH}`;
34
35
  } else {
35
36
  const identityContext = getIdentityContext();
36
37
  if (identityContext?.url) {
37
38
  cachedApiUrl = identityContext.url;
38
39
  } else if (globalThis.Netlify?.context?.url) {
39
- cachedApiUrl = new URL("/.netlify/identity", globalThis.Netlify.context.url).href;
40
+ cachedApiUrl = new URL(IDENTITY_PATH, globalThis.Netlify.context.url).href;
41
+ } else if (process.env.URL) {
42
+ cachedApiUrl = new URL(IDENTITY_PATH, process.env.URL).href;
40
43
  }
41
44
  }
42
45
  return cachedApiUrl ?? null;
@@ -63,18 +66,64 @@ var getClient = () => {
63
66
  };
64
67
  var getIdentityContext = () => {
65
68
  const identityContext = globalThis.netlifyIdentityContext;
66
- if (identityContext?.url && typeof identityContext.url === "string") {
69
+ if (identityContext?.url) {
67
70
  return {
68
71
  url: identityContext.url,
69
- token: typeof identityContext.token === "string" ? identityContext.token : void 0
72
+ token: identityContext.token
70
73
  };
71
74
  }
72
75
  if (globalThis.Netlify?.context?.url) {
73
- return { url: new URL("/.netlify/identity", globalThis.Netlify.context.url).href };
76
+ return { url: new URL(IDENTITY_PATH, globalThis.Netlify.context.url).href };
77
+ }
78
+ const siteUrl = process.env.URL;
79
+ if (siteUrl) {
80
+ return { url: new URL(IDENTITY_PATH, siteUrl).href };
74
81
  }
75
82
  return null;
76
83
  };
77
84
 
85
+ // src/cookies.ts
86
+ var NF_JWT_COOKIE = "nf_jwt";
87
+ var NF_REFRESH_COOKIE = "nf_refresh";
88
+ var getCookie = (name) => {
89
+ const match = document.cookie.match(new RegExp(`(?:^|; )${name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}=([^;]*)`));
90
+ return match ? decodeURIComponent(match[1]) : null;
91
+ };
92
+ var setAuthCookies = (cookies, accessToken, refreshToken) => {
93
+ cookies.set({
94
+ name: NF_JWT_COOKIE,
95
+ value: accessToken,
96
+ httpOnly: false,
97
+ secure: true,
98
+ path: "/",
99
+ sameSite: "Lax"
100
+ });
101
+ if (refreshToken) {
102
+ cookies.set({
103
+ name: NF_REFRESH_COOKIE,
104
+ value: refreshToken,
105
+ httpOnly: false,
106
+ secure: true,
107
+ path: "/",
108
+ sameSite: "Lax"
109
+ });
110
+ }
111
+ };
112
+ var deleteAuthCookies = (cookies) => {
113
+ cookies.delete(NF_JWT_COOKIE);
114
+ cookies.delete(NF_REFRESH_COOKIE);
115
+ };
116
+ var setBrowserAuthCookies = (accessToken, refreshToken) => {
117
+ document.cookie = `${NF_JWT_COOKIE}=${encodeURIComponent(accessToken)}; path=/; secure; samesite=lax`;
118
+ if (refreshToken) {
119
+ document.cookie = `${NF_REFRESH_COOKIE}=${encodeURIComponent(refreshToken)}; path=/; secure; samesite=lax`;
120
+ }
121
+ };
122
+ var deleteBrowserAuthCookies = () => {
123
+ document.cookie = `${NF_JWT_COOKIE}=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT`;
124
+ document.cookie = `${NF_REFRESH_COOKIE}=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT`;
125
+ };
126
+
78
127
  // src/user.ts
79
128
  var toAuthProvider = (value) => typeof value === "string" && AUTH_PROVIDERS.includes(value) ? value : void 0;
80
129
  var toUser = (userData) => {
@@ -100,32 +149,56 @@ var claimsToUser = (claims) => {
100
149
  const userMeta = claims.user_metadata ?? {};
101
150
  const name = userMeta.full_name || userMeta.name;
102
151
  return {
103
- id: typeof claims.sub === "string" ? claims.sub : "",
104
- email: typeof claims.email === "string" ? claims.email : void 0,
152
+ id: claims.sub ?? "",
153
+ email: claims.email,
105
154
  provider: toAuthProvider(appMeta.provider),
106
155
  name: typeof name === "string" ? name : void 0,
107
156
  metadata: userMeta
108
157
  };
109
158
  };
159
+ var decodeJwtPayload = (token) => {
160
+ try {
161
+ const parts = token.split(".");
162
+ if (parts.length !== 3) return null;
163
+ const payload = atob(parts[1].replace(/-/g, "+").replace(/_/g, "/"));
164
+ return JSON.parse(payload);
165
+ } catch {
166
+ return null;
167
+ }
168
+ };
110
169
  var getUser = () => {
111
170
  if (isBrowser()) {
112
171
  const client = getGoTrueClient();
113
172
  const currentUser = client?.currentUser() ?? null;
114
- if (!currentUser) return null;
115
- return toUser(currentUser);
173
+ if (currentUser) {
174
+ const jwt2 = getCookie(NF_JWT_COOKIE);
175
+ if (!jwt2) {
176
+ try {
177
+ currentUser.logout();
178
+ } catch {
179
+ }
180
+ return null;
181
+ }
182
+ return toUser(currentUser);
183
+ }
184
+ const jwt = getCookie(NF_JWT_COOKIE);
185
+ if (!jwt) return null;
186
+ const claims = decodeJwtPayload(jwt);
187
+ if (!claims) return null;
188
+ return claimsToUser(claims);
116
189
  }
117
190
  const identityContext = globalThis.netlifyIdentityContext;
118
- if (!identityContext) return null;
119
- const claims = identityContext.user ?? (identityContext.sub ? identityContext : null);
120
- if (!claims) return null;
121
- return claimsToUser(claims);
191
+ if (identityContext?.user) {
192
+ return claimsToUser(identityContext.user);
193
+ }
194
+ return null;
122
195
  };
123
196
  var isAuthenticated = () => getUser() !== null;
124
197
 
125
198
  // src/config.ts
126
199
  var getIdentityConfig = () => {
127
200
  if (isBrowser()) {
128
- return { url: `${window.location.origin}/.netlify/identity` };
201
+ return { url: `${window.location.origin}${IDENTITY_PATH}` };
129
202
  }
130
203
  return getIdentityContext();
131
204
  };
@@ -153,6 +226,20 @@ var getSettings = async () => {
153
226
  };
154
227
 
155
228
  // src/auth.ts
229
+ var getCookies = () => {
230
+ const cookies = globalThis.Netlify?.context?.cookies;
231
+ if (!cookies) {
232
+ throw new AuthError("Server-side auth requires Netlify Functions runtime");
233
+ }
234
+ return cookies;
235
+ };
236
+ var getServerIdentityUrl = () => {
237
+ const ctx = getIdentityContext();
238
+ if (!ctx?.url) {
239
+ throw new AuthError("Could not determine the Identity endpoint URL on the server");
240
+ }
241
+ return ctx.url;
242
+ };
156
243
  var persistSession = true;
157
244
  var listeners = /* @__PURE__ */ new Set();
158
245
  var emitAuthEvent = (event, user) => {
@@ -187,9 +274,58 @@ var onAuthChange = (callback) => {
187
274
  };
188
275
  };
189
276
  var login = async (email, password) => {
277
+ if (!isBrowser()) {
278
+ const identityUrl = getServerIdentityUrl();
279
+ const cookies = getCookies();
280
+ const body = new URLSearchParams({
281
+ grant_type: "password",
282
+ username: email,
283
+ password
284
+ });
285
+ let res;
286
+ try {
287
+ res = await fetch(`${identityUrl}/token`, {
288
+ method: "POST",
289
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
290
+ body: body.toString()
291
+ });
292
+ } catch (error) {
293
+ throw new AuthError(error.message, void 0, { cause: error });
294
+ }
295
+ if (!res.ok) {
296
+ const errorBody = await res.json().catch(() => ({}));
297
+ throw new AuthError(
298
+ errorBody.msg || errorBody.error_description || `Login failed (${res.status})`,
299
+ res.status
300
+ );
301
+ }
302
+ const data = await res.json();
303
+ const accessToken = data.access_token;
304
+ let userRes;
305
+ try {
306
+ userRes = await fetch(`${identityUrl}/user`, {
307
+ headers: { Authorization: `Bearer ${accessToken}` }
308
+ });
309
+ } catch (error) {
310
+ throw new AuthError(error.message, void 0, { cause: error });
311
+ }
312
+ if (!userRes.ok) {
313
+ const errorBody = await userRes.json().catch(() => ({}));
314
+ throw new AuthError(
315
+ errorBody.msg || `Failed to fetch user data (${userRes.status})`,
316
+ userRes.status
317
+ );
318
+ }
319
+ const userData = await userRes.json();
320
+ const user = toUser(userData);
321
+ setAuthCookies(cookies, accessToken, data.refresh_token);
322
+ return user;
323
+ }
190
324
  const client = getClient();
191
325
  try {
192
326
  const gotrueUser = await client.login(email, password, persistSession);
327
+ const jwt = await gotrueUser.jwt();
328
+ setBrowserAuthCookies(jwt);
193
329
  const user = toUser(gotrueUser);
194
330
  emitAuthEvent("login", user);
195
331
  return user;
@@ -198,6 +334,34 @@ var login = async (email, password) => {
198
334
  }
199
335
  };
200
336
  var signup = async (email, password, data) => {
337
+ if (!isBrowser()) {
338
+ const identityUrl = getServerIdentityUrl();
339
+ const cookies = getCookies();
340
+ let res;
341
+ try {
342
+ res = await fetch(`${identityUrl}/signup`, {
343
+ method: "POST",
344
+ headers: { "Content-Type": "application/json" },
345
+ body: JSON.stringify({ email, password, data })
346
+ });
347
+ } catch (error) {
348
+ throw new AuthError(error.message, void 0, { cause: error });
349
+ }
350
+ if (!res.ok) {
351
+ const errorBody = await res.json().catch(() => ({}));
352
+ throw new AuthError(errorBody.msg || `Signup failed (${res.status})`, res.status);
353
+ }
354
+ const responseData = await res.json();
355
+ const user = toUser(responseData);
356
+ if (responseData.confirmed_at) {
357
+ const responseRecord = responseData;
358
+ const accessToken = responseRecord.access_token;
359
+ if (accessToken) {
360
+ setAuthCookies(cookies, accessToken, responseRecord.refresh_token);
361
+ }
362
+ }
363
+ return user;
364
+ }
201
365
  const client = getClient();
202
366
  try {
203
367
  const response = await client.signup(email, password, data);
@@ -211,12 +375,30 @@ var signup = async (email, password, data) => {
211
375
  }
212
376
  };
213
377
  var logout = async () => {
378
+ if (!isBrowser()) {
379
+ const identityUrl = getServerIdentityUrl();
380
+ const cookies = getCookies();
381
+ const jwt = cookies.get(NF_JWT_COOKIE);
382
+ if (jwt) {
383
+ try {
384
+ await fetch(`${identityUrl}/logout`, {
385
+ method: "POST",
386
+ headers: { Authorization: `Bearer ${jwt}` }
387
+ });
388
+ } catch (error) {
389
+ throw new AuthError(error.message, void 0, { cause: error });
390
+ }
391
+ }
392
+ deleteAuthCookies(cookies);
393
+ return;
394
+ }
214
395
  const client = getClient();
215
396
  try {
216
397
  const currentUser = client.currentUser();
217
398
  if (currentUser) {
218
399
  await currentUser.logout();
219
400
  }
401
+ deleteBrowserAuthCookies();
220
402
  emitAuthEvent("logout", null);
221
403
  } catch (error) {
222
404
  throw new AuthError(error.message, void 0, { cause: error });
@@ -239,16 +421,18 @@ var handleAuthCallback = async () => {
239
421
  const params = new URLSearchParams(hash);
240
422
  const accessToken = params.get("access_token");
241
423
  if (accessToken) {
424
+ const refreshToken = params.get("refresh_token") ?? "";
242
425
  const gotrueUser = await client.createUser(
243
426
  {
244
427
  access_token: accessToken,
245
428
  token_type: params.get("token_type") ?? "bearer",
246
429
  expires_in: Number(params.get("expires_in")),
247
430
  expires_at: Number(params.get("expires_at")),
248
- refresh_token: params.get("refresh_token") ?? ""
431
+ refresh_token: refreshToken
249
432
  },
250
433
  persistSession
251
434
  );
435
+ setBrowserAuthCookies(accessToken, refreshToken || void 0);
252
436
  const user = toUser(gotrueUser);
253
437
  clearHash();
254
438
  emitAuthEvent("login", user);
@@ -257,6 +441,8 @@ var handleAuthCallback = async () => {
257
441
  const confirmationToken = params.get("confirmation_token");
258
442
  if (confirmationToken) {
259
443
  const gotrueUser = await client.confirm(confirmationToken, persistSession);
444
+ const jwt = await gotrueUser.jwt();
445
+ setBrowserAuthCookies(jwt);
260
446
  const user = toUser(gotrueUser);
261
447
  clearHash();
262
448
  emitAuthEvent("login", user);
@@ -265,6 +451,8 @@ var handleAuthCallback = async () => {
265
451
  const recoveryToken = params.get("recovery_token");
266
452
  if (recoveryToken) {
267
453
  const gotrueUser = await client.recover(recoveryToken, persistSession);
454
+ const jwt = await gotrueUser.jwt();
455
+ setBrowserAuthCookies(jwt);
268
456
  const user = toUser(gotrueUser);
269
457
  clearHash();
270
458
  emitAuthEvent("login", user);
@@ -277,8 +465,29 @@ var handleAuthCallback = async () => {
277
465
  }
278
466
  const emailChangeToken = params.get("email_change_token");
279
467
  if (emailChangeToken) {
280
- const gotrueUser = await client.verify("email_change", emailChangeToken, persistSession);
281
- const user = toUser(gotrueUser);
468
+ const currentUser = client.currentUser();
469
+ if (!currentUser) {
470
+ throw new AuthError("Email change verification requires an active browser session");
471
+ }
472
+ const jwt = await currentUser.jwt();
473
+ const identityUrl = `${window.location.origin}${IDENTITY_PATH}`;
474
+ const emailChangeRes = await fetch(`${identityUrl}/user`, {
475
+ method: "PUT",
476
+ headers: {
477
+ "Content-Type": "application/json",
478
+ Authorization: `Bearer ${jwt}`
479
+ },
480
+ body: JSON.stringify({ email_change_token: emailChangeToken })
481
+ });
482
+ if (!emailChangeRes.ok) {
483
+ const errorBody = await emailChangeRes.json().catch(() => ({}));
484
+ throw new AuthError(
485
+ errorBody.msg || `Email change verification failed (${emailChangeRes.status})`,
486
+ emailChangeRes.status
487
+ );
488
+ }
489
+ const emailChangeData = await emailChangeRes.json();
490
+ const user = toUser(emailChangeData);
282
491
  clearHash();
283
492
  emitAuthEvent("user_updated", user);
284
493
  return { type: "email_change", user };
@@ -291,8 +500,43 @@ var handleAuthCallback = async () => {
291
500
  var clearHash = () => {
292
501
  history.replaceState(null, "", window.location.pathname + window.location.search);
293
502
  };
503
+ var hydrateSession = async () => {
504
+ if (!isBrowser()) return null;
505
+ const client = getClient();
506
+ const currentUser = client.currentUser();
507
+ if (currentUser) return toUser(currentUser);
508
+ const accessToken = getCookie(NF_JWT_COOKIE);
509
+ if (!accessToken) return null;
510
+ const refreshToken = getCookie(NF_REFRESH_COOKIE) ?? "";
511
+ const gotrueUser = await client.createUser(
512
+ {
513
+ access_token: accessToken,
514
+ token_type: "bearer",
515
+ expires_in: 3600,
516
+ expires_at: Math.floor(Date.now() / 1e3) + 3600,
517
+ refresh_token: refreshToken
518
+ },
519
+ persistSession
520
+ );
521
+ const user = toUser(gotrueUser);
522
+ emitAuthEvent("login", user);
523
+ return user;
524
+ };
294
525
 
295
526
  // src/account.ts
527
+ var ensureCurrentUser = async () => {
528
+ const client = getClient();
529
+ let currentUser = client.currentUser();
530
+ if (!currentUser && isBrowser()) {
531
+ try {
532
+ await hydrateSession();
533
+ } catch {
534
+ }
535
+ currentUser = client.currentUser();
536
+ }
537
+ if (!currentUser) throw new AuthError("No user is currently logged in");
538
+ return currentUser;
539
+ };
296
540
  var requestPasswordRecovery = async (email) => {
297
541
  const client = getClient();
298
542
  try {
@@ -336,20 +580,37 @@ var acceptInvite = async (token, password) => {
336
580
  }
337
581
  };
338
582
  var verifyEmailChange = async (token) => {
339
- const client = getClient();
583
+ if (!isBrowser()) throw new AuthError("verifyEmailChange() is only available in the browser");
584
+ const currentUser = await ensureCurrentUser();
585
+ const jwt = await currentUser.jwt();
586
+ const identityUrl = `${window.location.origin}${IDENTITY_PATH}`;
340
587
  try {
341
- const gotrueUser = await client.verify("email_change", token, persistSession);
342
- const user = toUser(gotrueUser);
588
+ const res = await fetch(`${identityUrl}/user`, {
589
+ method: "PUT",
590
+ headers: {
591
+ "Content-Type": "application/json",
592
+ Authorization: `Bearer ${jwt}`
593
+ },
594
+ body: JSON.stringify({ email_change_token: token })
595
+ });
596
+ if (!res.ok) {
597
+ const errorBody = await res.json().catch(() => ({}));
598
+ throw new AuthError(
599
+ errorBody.msg || `Email change verification failed (${res.status})`,
600
+ res.status
601
+ );
602
+ }
603
+ const userData = await res.json();
604
+ const user = toUser(userData);
343
605
  emitAuthEvent("user_updated", user);
344
606
  return user;
345
607
  } catch (error) {
608
+ if (error instanceof AuthError) throw error;
346
609
  throw new AuthError(error.message, void 0, { cause: error });
347
610
  }
348
611
  };
349
612
  var updateUser = async (updates) => {
350
- const client = getClient();
351
- const currentUser = client.currentUser();
352
- if (!currentUser) throw new AuthError("No user is currently logged in");
613
+ const currentUser = await ensureCurrentUser();
353
614
  try {
354
615
  const updatedUser = await currentUser.update(updates);
355
616
  const user = toUser(updatedUser);