@neondatabase/auth 0.1.0-beta.8 → 0.2.0-beta.1

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 (45) hide show
  1. package/README.md +108 -18
  2. package/dist/{adapter-core-Bw9mn_AS.d.mts → adapter-core-CnrOXh1T.d.mts} +246 -280
  3. package/dist/{adapter-core-C_NEMs0b.mjs → adapter-core-CtmnMMJ7.mjs} +392 -67
  4. package/dist/better-auth-react-adapter-DNi5PC5D.d.mts +2170 -0
  5. package/dist/{better-auth-react-adapter-BbM3jLLv.mjs → better-auth-react-adapter-Dv-o6A6O.mjs} +10 -8
  6. package/dist/{chunk-5DLVHPZS-Bxj7snpZ-DoVNlsyk.mjs → chunk-VCZJYX65-CLnrj1o7-D6ZQkcc_.mjs} +13 -3
  7. package/dist/constants-Cupc_bln.mjs +28 -0
  8. package/dist/index.d.mts +4 -98
  9. package/dist/index.mjs +2 -1
  10. package/dist/neon-auth-BEGCfAe6.d.mts +107 -0
  11. package/dist/{neon-auth-DdlToh7_.mjs → neon-auth-Cs2cWh1B.mjs} +7 -4
  12. package/dist/next/index.d.mts +61 -170
  13. package/dist/next/index.mjs +4 -311
  14. package/dist/next/server/index.d.mts +538 -0
  15. package/dist/next/server/index.mjs +1373 -0
  16. package/dist/react/adapters/index.d.mts +4 -4
  17. package/dist/react/adapters/index.mjs +2 -1
  18. package/dist/react/index.d.mts +5 -5
  19. package/dist/react/index.mjs +4 -3
  20. package/dist/react/ui/index.d.mts +1 -1
  21. package/dist/react/ui/index.mjs +2 -2
  22. package/dist/react/ui/server.mjs +1 -1
  23. package/dist/{supabase-adapter-CAqbpOC7.mjs → supabase-adapter-BlcGPyOf.mjs} +28 -45
  24. package/dist/supabase-adapter-DUqw2fw8.d.mts +2258 -0
  25. package/dist/types/index.d.mts +2 -7
  26. package/dist/ui/.safelist.html +3 -0
  27. package/dist/ui/css.css +2 -2
  28. package/dist/ui/tailwind.css +4 -3
  29. package/dist/ui/theme-inline.css +44 -0
  30. package/dist/ui/theme.css +221 -118
  31. package/dist/{ui-aMoA-9nq.mjs → ui-COLWzDsu.mjs} +6024 -3004
  32. package/dist/vanilla/adapters/index.d.mts +3 -3
  33. package/dist/vanilla/adapters/index.mjs +2 -1
  34. package/dist/vanilla/index.d.mts +3 -3
  35. package/dist/vanilla/index.mjs +2 -1
  36. package/llms.txt +330 -0
  37. package/package.json +17 -10
  38. package/dist/better-auth-react-adapter-JoscqoDc.d.mts +0 -722
  39. package/dist/better-auth-types-CE4hLv9E.d.mts +0 -9
  40. package/dist/supabase-adapter-Clxlqg1x.d.mts +0 -127
  41. /package/dist/{adapters-D0mxG3F-.mjs → adapters-B7YKkjaL.mjs} +0 -0
  42. /package/dist/{adapters-Df6Dd3KK.mjs → adapters-CivF9wql.mjs} +0 -0
  43. /package/dist/{index-ClXLQ1fw.d.mts → index-CPnFzULh.d.mts} +0 -0
  44. /package/dist/{index-BXlAjlSt.d.mts → index-CzsGMS7C.d.mts} +0 -0
  45. /package/dist/{index-DCQ5Y2ED.d.mts → index-OEBbnNdr.d.mts} +0 -0
@@ -1,5 +1,7 @@
1
+ import { a as NEON_AUTH_POPUP_PARAM_NAME, c as SESSION_CACHE_TTL_MS, i as NEON_AUTH_POPUP_CALLBACK_ROUTE, o as NEON_AUTH_SESSION_VERIFIER_PARAM_NAME, r as NEON_AUTH_POPUP_CALLBACK_PARAM_NAME, s as OAUTH_POPUP_MESSAGE_TYPE, t as CLOCK_SKEW_BUFFER_MS } from "./constants-Cupc_bln.mjs";
1
2
  import { getGlobalBroadcastChannel } from "better-auth/client";
2
- import { adminClient, anonymousClient, emailOTPClient, jwtClient, organizationClient } from "better-auth/client/plugins";
3
+ import { adminClient, emailOTPClient, jwtClient, organizationClient } from "better-auth/client/plugins";
4
+ import z from "zod";
3
5
 
4
6
  //#region src/core/in-flight-request-manager.ts
5
7
  /**
@@ -99,25 +101,6 @@ var InFlightRequestManager = class {
99
101
  }
100
102
  };
101
103
 
102
- //#endregion
103
- //#region src/core/constants.ts
104
- /**
105
- * Session caching configuration constants
106
- *
107
- * Uses industry-standard 60s cache TTL (common across auth providers).
108
- *
109
- * Note: Token refresh detection is now automatic via Better Auth's
110
- * fetchOptions.onSuccess callback. No polling is needed.
111
- */
112
- /** Session cache TTL in milliseconds (60 seconds) */
113
- const SESSION_CACHE_TTL_MS = 6e4;
114
- /** Clock skew buffer for token expiration checks in milliseconds (10 seconds) */
115
- const CLOCK_SKEW_BUFFER_MS = 1e4;
116
- /** Default session expiry duration in milliseconds (1 hour) */
117
- const DEFAULT_SESSION_EXPIRY_MS = 36e5;
118
- /** Name of the session verifier parameter in the URL, used for the OAUTH flow */
119
- const NEON_AUTH_SESSION_VERIFIER_PARAM_NAME = "neon_auth_session_verifier";
120
-
121
104
  //#endregion
122
105
  //#region src/utils/jwt.ts
123
106
  /**
@@ -136,16 +119,68 @@ function getJwtExpiration(jwt) {
136
119
  }
137
120
  }
138
121
 
122
+ //#endregion
123
+ //#region src/core/token-cache.ts
124
+ var TokenCache = class {
125
+ cache = null;
126
+ /**
127
+ * Get cached data if not expired.
128
+ * Returns null if cache is empty or expired.
129
+ */
130
+ get() {
131
+ if (!this.cache) return null;
132
+ if (Date.now() > this.cache.expiresAt) {
133
+ this.cache = null;
134
+ return null;
135
+ }
136
+ return this.cache.data;
137
+ }
138
+ /**
139
+ * Set cached data with TTL.
140
+ * If jwt is provided, TTL is calculated from its expiration.
141
+ * Otherwise, uses default SESSION_CACHE_TTL_MS.
142
+ */
143
+ set(data, jwt) {
144
+ const ttl = this.calculateTTL(jwt);
145
+ this.cache = {
146
+ data,
147
+ expiresAt: Date.now() + ttl
148
+ };
149
+ }
150
+ /**
151
+ * Clear the cache.
152
+ */
153
+ clear() {
154
+ this.cache = null;
155
+ }
156
+ /**
157
+ * Check if cache has valid (non-expired) data.
158
+ */
159
+ has() {
160
+ return this.get() !== null;
161
+ }
162
+ /**
163
+ * Calculate cache TTL from JWT expiration.
164
+ * Falls back to default TTL if JWT is invalid or missing.
165
+ */
166
+ calculateTTL(jwt) {
167
+ if (!jwt) return SESSION_CACHE_TTL_MS;
168
+ const exp = getJwtExpiration(jwt);
169
+ if (!exp) return SESSION_CACHE_TTL_MS;
170
+ const now = Date.now();
171
+ const ttl = exp * 1e3 - now - CLOCK_SKEW_BUFFER_MS;
172
+ return Math.max(ttl, 1e3);
173
+ }
174
+ };
175
+
139
176
  //#endregion
140
177
  //#region src/core/session-cache-manager.ts
141
178
  /**
142
179
  * Manages in-memory session cache with TTL expiration.
143
180
  *
144
- * Features:
145
- * - Stores sessions in Better Auth native format
146
- * - Automatic expiration based on JWT token expiration
181
+ * Built on TokenCache, adding session-specific features:
147
182
  * - Invalidation flag for sign-out scenarios
148
- * - TTL calculation with clock skew buffer
183
+ * - Token refresh detection via lastSessionData comparison
149
184
  *
150
185
  * Example:
151
186
  * ```typescript
@@ -155,48 +190,40 @@ function getJwtExpiration(jwt) {
155
190
  * ```
156
191
  */
157
192
  var SessionCacheManager = class {
158
- cache = null;
193
+ cache = new TokenCache();
159
194
  lastSessionData = null;
195
+ invalidated = false;
160
196
  /**
161
197
  * Get cached session if valid and not expired.
162
198
  * Returns null if cache is invalid, expired, or doesn't exist.
163
199
  */
164
200
  getCachedSession() {
165
- if (!this.cache || this.cache.invalidated) return null;
166
- if (Date.now() > this.cache.expiresAt) {
167
- this.clearSessionCache();
168
- return null;
169
- }
170
- return this.cache.data;
201
+ if (this.invalidated) return null;
202
+ return this.cache.get();
171
203
  }
172
204
  /**
173
- * Set cached session with optional TTL.
174
- * If TTL not provided, calculates from JWT expiration.
205
+ * Set cached session with JWT-based TTL.
175
206
  * Skips caching if cache was invalidated (sign-out scenario).
176
207
  */
177
- setCachedSession(data, ttl) {
178
- if (this.cache?.invalidated) return;
179
- this.lastSessionData = this.cache?.data ?? null;
180
- const calculatedTtl = ttl ?? this.calculateCacheTTL(data.session.token);
181
- this.cache = {
182
- data,
183
- expiresAt: Date.now() + calculatedTtl,
184
- invalidated: false
185
- };
208
+ setCachedSession(data) {
209
+ if (this.invalidated) return;
210
+ this.lastSessionData = this.cache.get();
211
+ this.cache.set(data, data.session.token);
186
212
  }
187
213
  /**
188
214
  * Invalidate cache (marks as invalid but doesn't clear).
189
215
  * Useful for sign-out scenarios where in-flight requests should not cache.
190
216
  */
191
217
  invalidateSessionCache() {
192
- if (this.cache) this.cache.invalidated = true;
218
+ this.invalidated = true;
193
219
  }
194
220
  /**
195
221
  * Clear cache completely.
196
222
  */
197
223
  clearSessionCache() {
198
- this.cache = null;
224
+ this.cache.clear();
199
225
  this.lastSessionData = null;
226
+ this.invalidated = false;
200
227
  }
201
228
  /**
202
229
  * Check if token was refreshed by comparing tokens with previous session.
@@ -206,20 +233,101 @@ var SessionCacheManager = class {
206
233
  if (!this.lastSessionData?.session?.token || !data?.session?.token) return false;
207
234
  return this.lastSessionData.session.token !== data.session.token;
208
235
  }
236
+ };
237
+
238
+ //#endregion
239
+ //#region src/core/anonymous-token-cache-manager.ts
240
+ /**
241
+ * Manages in-memory anonymous token cache with TTL expiration.
242
+ *
243
+ * Stores the full anonymous token response (token + expires_at).
244
+ * Unlike SessionCacheManager, doesn't need:
245
+ * - Invalidation flag (no sign-out scenario for anonymous tokens)
246
+ * - Refresh detection (anonymous tokens are stateless)
247
+ */
248
+ var AnonymousTokenCacheManager = class {
249
+ cache = new TokenCache();
250
+ /**
251
+ * Get cached anonymous token response if not expired.
252
+ * Returns null if cache is empty or expired.
253
+ */
254
+ getCachedResponse() {
255
+ return this.cache.get();
256
+ }
209
257
  /**
210
- * Calculate cache TTL from JWT expiration.
211
- * Falls back to default TTL if JWT is invalid or missing.
258
+ * Set cached anonymous token response with JWT-based TTL.
259
+ * TTL is automatically calculated from the JWT expiration.
212
260
  */
213
- calculateCacheTTL(jwt) {
214
- if (!jwt) return SESSION_CACHE_TTL_MS;
215
- const exp = getJwtExpiration(jwt);
216
- if (!exp) return SESSION_CACHE_TTL_MS;
217
- const now = Date.now();
218
- const ttl = exp * 1e3 - now - CLOCK_SKEW_BUFFER_MS;
219
- return Math.max(ttl, 1e3);
261
+ setCachedResponse(data) {
262
+ this.cache.set(data, data.token);
263
+ }
264
+ /**
265
+ * Clear the cache.
266
+ */
267
+ clearCache() {
268
+ this.cache.clear();
269
+ }
270
+ /**
271
+ * Check if cache has a valid (non-expired) response.
272
+ */
273
+ hasCachedResponse() {
274
+ return this.cache.has();
220
275
  }
221
276
  };
222
277
 
278
+ //#endregion
279
+ //#region src/core/oauth-popup.ts
280
+ /**
281
+ * Opens an OAuth popup window and waits for completion.
282
+ *
283
+ * This is used when the app is running inside an iframe, where OAuth
284
+ * redirect flows don't work due to X-Frame-Options/CSP restrictions.
285
+ * The popup completes the OAuth flow and sends a postMessage back
286
+ * with the session verifier needed to fetch the session.
287
+ *
288
+ * @param url - The OAuth authorization URL to open in the popup
289
+ * @returns Promise that resolves with the session verifier when OAuth completes
290
+ * @throws Error if popup is blocked, closed by user, or times out
291
+ */
292
+ async function openOAuthPopup(url) {
293
+ const timeout = 12e4;
294
+ const pollInterval = 500;
295
+ return new Promise((resolve, reject) => {
296
+ const popup = globalThis.open(url, "neon_oauth_popup", "width=500,height=700,popup=yes");
297
+ if (!popup || popup.closed) {
298
+ reject(/* @__PURE__ */ new Error("Popup blocked. Please allow popups for this site."));
299
+ return;
300
+ }
301
+ const timeoutId = setTimeout(() => {
302
+ cleanup();
303
+ try {
304
+ popup.close();
305
+ } catch {}
306
+ reject(/* @__PURE__ */ new Error("OAuth popup timed out. Please try again."));
307
+ }, timeout);
308
+ const pollId = setInterval(() => {
309
+ try {
310
+ if (popup.closed) {
311
+ cleanup();
312
+ reject(/* @__PURE__ */ new Error("OAuth popup was closed. Please try again."));
313
+ }
314
+ } catch {}
315
+ }, pollInterval);
316
+ function cleanup() {
317
+ clearTimeout(timeoutId);
318
+ clearInterval(pollId);
319
+ globalThis.removeEventListener("message", handleMessage);
320
+ }
321
+ function handleMessage(event) {
322
+ if (event.origin !== globalThis.location.origin) return;
323
+ if (event.data?.type !== OAUTH_POPUP_MESSAGE_TYPE) return;
324
+ cleanup();
325
+ resolve({ verifier: event.data.verifier || null });
326
+ }
327
+ globalThis.addEventListener("message", handleMessage);
328
+ });
329
+ }
330
+
223
331
  //#endregion
224
332
  //#region src/utils/browser.ts
225
333
  /**
@@ -229,20 +337,91 @@ var SessionCacheManager = class {
229
337
  const isBrowser = () => {
230
338
  return globalThis.window !== void 0 && typeof document !== "undefined";
231
339
  };
340
+ /**
341
+ * Checks if the code is running inside an iframe
342
+ * Used to detect embedded contexts where OAuth redirect won't work
343
+ * @returns true if in iframe, false otherwise
344
+ */
345
+ const isIframe = () => {
346
+ if (!isBrowser()) return false;
347
+ try {
348
+ return globalThis.self !== globalThis.top;
349
+ } catch {
350
+ return true;
351
+ }
352
+ };
353
+
354
+ //#endregion
355
+ //#region src/plugins/anonymous-token.ts
356
+ const ANONYMOUS_TOKEN_ENDPOINT = "/token/anonymous";
357
+ const anonymousTokenResponseSchema = z.object({
358
+ token: z.string(),
359
+ expires_at: z.number()
360
+ });
361
+ const anonymousTokenClient = () => {
362
+ return {
363
+ id: "anonymous-token",
364
+ pathMethods: { [ANONYMOUS_TOKEN_ENDPOINT]: "GET" },
365
+ getActions: ($fetch) => {
366
+ return { getAnonymousToken: async (fetchOptions) => {
367
+ return await $fetch(ANONYMOUS_TOKEN_ENDPOINT, {
368
+ method: "GET",
369
+ ...fetchOptions
370
+ });
371
+ } };
372
+ }
373
+ };
374
+ };
232
375
 
233
376
  //#endregion
234
377
  //#region src/core/better-auth-methods.ts
235
378
  const CURRENT_TAB_CLIENT_ID = crypto.randomUUID();
236
379
  const BETTER_AUTH_METHODS_IN_FLIGHT_REQUESTS = new InFlightRequestManager();
237
380
  const BETTER_AUTH_METHODS_CACHE = new SessionCacheManager();
381
+ const BETTER_AUTH_ANONYMOUS_TOKEN_CACHE = new AnonymousTokenCacheManager();
238
382
  const BETTER_AUTH_ENDPOINTS = {
239
383
  signUp: "/sign-up",
240
384
  signIn: "/sign-in",
241
385
  signOut: "/sign-out",
242
386
  updateUser: "/update-user",
243
387
  getSession: "/get-session",
244
- token: "/token"
388
+ token: "/token",
389
+ anonymousSignIn: "/sign-in/anonymous",
390
+ anonymousToken: "/token/anonymous"
245
391
  };
392
+ /**
393
+ * Handles social sign-in via popup when running inside an iframe.
394
+ * This is necessary because OAuth redirects don't work in iframes due to
395
+ * X-Frame-Options/CSP restrictions from OAuth providers.
396
+ *
397
+ * Flow:
398
+ * 1. Request OAuth URL from server (with disableRedirect)
399
+ * 2. Open popup window with the OAuth URL
400
+ * 3. Wait for popup to complete and send back the session verifier
401
+ * 4. Navigate to callbackURL with verifier - normal page load handles session
402
+ */
403
+ async function handleSocialSignInViaPopup(input, init) {
404
+ const body = JSON.parse(init?.body || "{}");
405
+ const originalCallbackURL = body.callbackURL || "/";
406
+ const popupCallbackUrl = new URL(NEON_AUTH_POPUP_CALLBACK_ROUTE, globalThis.location.origin);
407
+ popupCallbackUrl.searchParams.set(NEON_AUTH_POPUP_PARAM_NAME, "1");
408
+ popupCallbackUrl.searchParams.set(NEON_AUTH_POPUP_CALLBACK_PARAM_NAME, originalCallbackURL);
409
+ body.callbackURL = popupCallbackUrl.toString();
410
+ body.disableRedirect = true;
411
+ const response = await fetch(input, {
412
+ ...init,
413
+ body: JSON.stringify(body)
414
+ });
415
+ const data = await response.json();
416
+ const oauthUrl = data.url;
417
+ if (!oauthUrl) throw new Error("Failed to get OAuth URL");
418
+ const popupResult = await openOAuthPopup(oauthUrl);
419
+ if (!popupResult.verifier) throw new Error("OAuth completed but no session verifier received");
420
+ const navigationUrl = new URL(originalCallbackURL, globalThis.location.origin);
421
+ navigationUrl.searchParams.set(NEON_AUTH_SESSION_VERIFIER_PARAM_NAME, popupResult.verifier);
422
+ globalThis.location.href = navigationUrl.toString();
423
+ return Response.json(data, { status: response.status });
424
+ }
246
425
  const BETTER_AUTH_METHODS_HOOKS = {
247
426
  signUp: {
248
427
  onRequest: () => {},
@@ -261,6 +440,10 @@ const BETTER_AUTH_METHODS_HOOKS = {
261
440
  }
262
441
  },
263
442
  signIn: {
443
+ beforeFetch: (input, init) => {
444
+ if (!(typeof input === "string" ? input : input.toString()).includes("/sign-in/social") || !isIframe()) return null;
445
+ return handleSocialSignInViaPopup(input, init);
446
+ },
264
447
  onRequest: () => {},
265
448
  onSuccess: (responseData) => {
266
449
  if (isSessionResponseData(responseData)) {
@@ -289,17 +472,24 @@ const BETTER_AUTH_METHODS_HOOKS = {
289
472
  updateUser: {
290
473
  onRequest: () => {},
291
474
  onSuccess: (responseData) => {
292
- if (isSessionResponseData(responseData)) emitAuthEvent({
293
- type: "USER_UPDATE",
294
- data: {
475
+ if (isSessionResponseData(responseData)) {
476
+ const sessionData = {
295
477
  session: responseData.session,
296
478
  user: responseData.user
297
- }
298
- });
479
+ };
480
+ BETTER_AUTH_METHODS_CACHE.setCachedSession(sessionData);
481
+ emitAuthEvent({
482
+ type: "USER_UPDATE",
483
+ data: sessionData
484
+ });
485
+ } else {
486
+ BETTER_AUTH_METHODS_CACHE.clearSessionCache();
487
+ emitAuthEvent({ type: "USER_UPDATE" });
488
+ }
299
489
  }
300
490
  },
301
491
  getSession: {
302
- beforeRequest: () => {
492
+ beforeFetch: () => {
303
493
  const cachedData = BETTER_AUTH_METHODS_CACHE.getCachedSession();
304
494
  if (!cachedData) return null;
305
495
  return Response.json(cachedData, { status: 200 });
@@ -337,6 +527,18 @@ const BETTER_AUTH_METHODS_HOOKS = {
337
527
  }
338
528
  }
339
529
  }
530
+ },
531
+ anonymousToken: {
532
+ beforeFetch: () => {
533
+ const cachedResponse = BETTER_AUTH_ANONYMOUS_TOKEN_CACHE.getCachedResponse();
534
+ if (!cachedResponse) return null;
535
+ return Response.json(cachedResponse, { status: 200 });
536
+ },
537
+ onRequest: () => {},
538
+ onSuccess: (responseData) => {
539
+ const parsed = anonymousTokenResponseSchema.safeParse(responseData);
540
+ if (parsed.success) BETTER_AUTH_ANONYMOUS_TOKEN_CACHE.setCachedResponse(parsed.data);
541
+ }
340
542
  }
341
543
  };
342
544
  /**
@@ -394,6 +596,8 @@ function isSessionResponseData(responseData) {
394
596
  return Boolean(responseData && typeof responseData === "object" && "session" in responseData && "user" in responseData && responseData.session !== null && responseData.user !== null);
395
597
  }
396
598
  function deriveBetterAuthMethodFromUrl(url) {
599
+ if (url.includes("/sign-in/anonymous")) return "anonymousSignIn";
600
+ if (url.includes(BETTER_AUTH_ENDPOINTS.anonymousToken)) return "anonymousToken";
397
601
  if (url.includes(BETTER_AUTH_ENDPOINTS.signIn)) return "signIn";
398
602
  if (url.includes(BETTER_AUTH_ENDPOINTS.signUp)) return "signUp";
399
603
  if (url.includes(BETTER_AUTH_ENDPOINTS.signOut)) return "signOut";
@@ -401,6 +605,20 @@ function deriveBetterAuthMethodFromUrl(url) {
401
605
  if (url.includes(BETTER_AUTH_ENDPOINTS.getSession) || url.includes(BETTER_AUTH_ENDPOINTS.token)) return "getSession";
402
606
  }
403
607
  function initBroadcastChannel() {
608
+ if (isBrowser() && globalThis.opener && globalThis.opener !== globalThis) {
609
+ const params = new URLSearchParams(globalThis.location.search);
610
+ if (params.has(NEON_AUTH_POPUP_PARAM_NAME)) {
611
+ const verifier = params.get(NEON_AUTH_SESSION_VERIFIER_PARAM_NAME);
612
+ const originalCallback = params.get(NEON_AUTH_POPUP_CALLBACK_PARAM_NAME);
613
+ globalThis.opener.postMessage({
614
+ type: OAUTH_POPUP_MESSAGE_TYPE,
615
+ verifier,
616
+ originalCallback
617
+ }, "*");
618
+ globalThis.close();
619
+ return;
620
+ }
621
+ }
404
622
  getGlobalBroadcastChannel().subscribe((message) => {
405
623
  if (message.clientId === CURRENT_TAB_CLIENT_ID) return;
406
624
  const trigger = message.data?.trigger;
@@ -408,6 +626,96 @@ function initBroadcastChannel() {
408
626
  });
409
627
  }
410
628
 
629
+ //#endregion
630
+ //#region package.json
631
+ var name = "@neondatabase/auth";
632
+ var version = "0.1.0-beta.21";
633
+
634
+ //#endregion
635
+ //#region ../internal/dist/index.mjs
636
+ const X_NEON_CLIENT_INFO_HEADER = "X-Neon-Client-Info";
637
+ /**
638
+ * Type guard for checking if a property exists on globalThis
639
+ */
640
+ function hasGlobalProperty(key) {
641
+ return key in globalThis;
642
+ }
643
+ /**
644
+ * Detects the JavaScript framework being used at runtime.
645
+ * Detection order matters to avoid false positives (e.g., Next.js includes React).
646
+ */
647
+ function detectFramework() {
648
+ if (typeof process !== "undefined" && process.env && (process.env.NEXT_RUNTIME || process.env.__NEXT_PRIVATE_ORIGIN)) return "next";
649
+ if (typeof globalThis.window !== "undefined") {
650
+ if (hasGlobalProperty("__NEXT_DATA__")) return "next";
651
+ if (hasGlobalProperty("__remixContext")) return "remix";
652
+ if (hasGlobalProperty("__REACT_DEVTOOLS_GLOBAL_HOOK__")) return "react";
653
+ if (hasGlobalProperty("__VUE__")) return "vue";
654
+ if (hasGlobalProperty("Zone")) return "angular";
655
+ }
656
+ }
657
+ function getClientInfo(sdkName, sdkVersion) {
658
+ const base = {
659
+ sdk: sdkName,
660
+ version: sdkVersion,
661
+ runtime: "unknown",
662
+ runtimeVersion: "unknown",
663
+ platform: "unknown",
664
+ arch: "unknown"
665
+ };
666
+ let result;
667
+ if (typeof process !== "undefined" && process.versions?.node) result = {
668
+ ...base,
669
+ runtime: "node",
670
+ runtimeVersion: process.versions.node,
671
+ platform: process.platform,
672
+ arch: process.arch
673
+ };
674
+ else if (typeof Deno !== "undefined") result = {
675
+ ...base,
676
+ runtime: "deno",
677
+ runtimeVersion: Deno.version?.deno ?? "unknown",
678
+ platform: Deno.build?.os ?? "unknown",
679
+ arch: Deno.build?.arch ?? "unknown"
680
+ };
681
+ else if (typeof Bun !== "undefined") result = {
682
+ ...base,
683
+ runtime: "bun",
684
+ runtimeVersion: Bun.version ?? "unknown",
685
+ platform: process?.platform ?? "unknown",
686
+ arch: process?.arch ?? "unknown"
687
+ };
688
+ else if (typeof EdgeRuntime !== "undefined" || typeof process !== "undefined" && !process.versions?.node && typeof globalThis.window === "undefined" && typeof document === "undefined") result = {
689
+ ...base,
690
+ runtime: "edge"
691
+ };
692
+ else if (globalThis.window !== void 0 && typeof document !== "undefined") result = {
693
+ ...base,
694
+ runtime: "browser",
695
+ runtimeVersion: "unknown",
696
+ platform: "web",
697
+ arch: "unknown"
698
+ };
699
+ else result = base;
700
+ const framework = detectFramework();
701
+ if (framework) result.framework = framework;
702
+ return result;
703
+ }
704
+ function createClientInfoInjector(defaultSdkName, defaultSdkVersion) {
705
+ const cachedClientInfo = JSON.stringify(getClientInfo(defaultSdkName, defaultSdkVersion));
706
+ return function injectClientInfo$1(headers, sdkOverride) {
707
+ const result = new Headers(headers);
708
+ if (result.has(X_NEON_CLIENT_INFO_HEADER)) return result;
709
+ const clientInfoString = sdkOverride ? JSON.stringify(getClientInfo(sdkOverride.name, sdkOverride.version)) : cachedClientInfo;
710
+ result.set(X_NEON_CLIENT_INFO_HEADER, clientInfoString);
711
+ return result;
712
+ };
713
+ }
714
+
715
+ //#endregion
716
+ //#region src/utils/client-info.ts
717
+ const injectClientInfo = createClientInfoInjector(name, version);
718
+
411
719
  //#endregion
412
720
  //#region src/core/adapter-core.ts
413
721
  const FORCE_FETCH_HEADER = "X-Force-Fetch";
@@ -416,7 +724,7 @@ const supportedBetterAuthClientPlugins = [
416
724
  adminClient(),
417
725
  organizationClient(),
418
726
  emailOTPClient(),
419
- anonymousClient()
727
+ anonymousTokenClient()
420
728
  ];
421
729
  var NeonAuthAdapterCore = class {
422
730
  betterAuthOptions;
@@ -440,9 +748,9 @@ var NeonAuthAdapterCore = class {
440
748
  userOnRequest?.(request);
441
749
  },
442
750
  customFetchImpl: async (url, init) => {
443
- if (init?.headers && FORCE_FETCH_HEADER in init.headers) {
444
- const headers = { ...init.headers };
445
- delete headers[FORCE_FETCH_HEADER];
751
+ const headers = injectClientInfo(init?.headers);
752
+ if (headers.has(FORCE_FETCH_HEADER)) {
753
+ headers.delete(FORCE_FETCH_HEADER);
446
754
  const response$1 = await fetch(url, {
447
755
  ...init,
448
756
  headers
@@ -458,11 +766,14 @@ var NeonAuthAdapterCore = class {
458
766
  }
459
767
  const betterAuthMethod = deriveBetterAuthMethodFromUrl(url.toString());
460
768
  if (betterAuthMethod) {
461
- const response$1 = await BETTER_AUTH_METHODS_HOOKS[betterAuthMethod].beforeRequest?.(url, init);
769
+ const response$1 = await BETTER_AUTH_METHODS_HOOKS[betterAuthMethod].beforeFetch?.(url, init);
462
770
  if (response$1) return response$1;
463
771
  }
464
772
  const key = `${init?.method || "GET"}:${url}:${init?.body || ""}`;
465
- const response = await BETTER_AUTH_METHODS_IN_FLIGHT_REQUESTS.deduplicate(key, () => fetch(url, init));
773
+ const response = await BETTER_AUTH_METHODS_IN_FLIGHT_REQUESTS.deduplicate(key, () => fetch(url, {
774
+ ...init,
775
+ headers
776
+ }));
466
777
  if (!response.ok) {
467
778
  const errorBody = await response.clone().json().catch(() => ({}));
468
779
  const err = new Error(errorBody.message || `HTTP ${response.status} ${response.statusText}`);
@@ -486,7 +797,21 @@ var NeonAuthAdapterCore = class {
486
797
  };
487
798
  initBroadcastChannel();
488
799
  }
800
+ /**
801
+ * Get JWT token for authenticated or anonymous access.
802
+ * Single source of truth for token retrieval logic.
803
+ *
804
+ * @param allowAnonymous - When true, fetches anonymous token if no session exists
805
+ * @returns JWT token string or null if unavailable
806
+ */
807
+ async getJWTToken(allowAnonymous) {
808
+ const client = this.getBetterAuthInstance();
809
+ const session = await client.getSession();
810
+ if (session.data?.session?.token) return session.data.session.token;
811
+ if (allowAnonymous) return (await client.getAnonymousToken()).data?.token ?? null;
812
+ return null;
813
+ }
489
814
  };
490
815
 
491
816
  //#endregion
492
- export { DEFAULT_SESSION_EXPIRY_MS as a, CURRENT_TAB_CLIENT_ID as i, BETTER_AUTH_METHODS_CACHE as n, BETTER_AUTH_METHODS_HOOKS as r, NeonAuthAdapterCore as t };
817
+ export { CURRENT_TAB_CLIENT_ID as i, BETTER_AUTH_METHODS_CACHE as n, BETTER_AUTH_METHODS_HOOKS as r, NeonAuthAdapterCore as t };