@webtwizz/cloud 0.1.0 → 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/dist/client.js CHANGED
@@ -1,8 +1,793 @@
1
+ import {
2
+ CLOCK_SKEW_BUFFER_MS,
3
+ NEON_AUTH_POPUP_CALLBACK_PARAM_NAME,
4
+ NEON_AUTH_POPUP_CALLBACK_ROUTE,
5
+ NEON_AUTH_POPUP_PARAM_NAME,
6
+ NEON_AUTH_SESSION_VERIFIER_PARAM_NAME,
7
+ OAUTH_POPUP_MESSAGE_TYPE,
8
+ SESSION_CACHE_TTL_MS,
9
+ normalizeBetterAuthError
10
+ } from "./chunk-7EXXDRMF.js";
11
+
12
+ // node_modules/@neondatabase/auth/dist/adapter-core-CZ8saNEY.mjs
13
+ import { getGlobalBroadcastChannel } from "better-auth/client";
14
+ import { adminClient, emailOTPClient, jwtClient, magicLinkClient, organizationClient, phoneNumberClient } from "better-auth/client/plugins";
15
+ import z from "zod";
16
+ var InFlightRequestManager = class {
17
+ /**
18
+ * Map of request keys to in-flight Promises.
19
+ * Automatically cleared after Promise resolution (success or error).
20
+ */
21
+ inFlightRequests = /* @__PURE__ */ new Map();
22
+ /**
23
+ * Execute function with deduplication.
24
+ *
25
+ * If request with same key is in-flight, returns existing Promise.
26
+ * Otherwise, executes fn and tracks the Promise.
27
+ *
28
+ * @param key - Unique identifier for this request (e.g., "getSession")
29
+ * @param fn - Async function to execute (only called if no in-flight request exists)
30
+ * @returns Promise that resolves to the function result
31
+ *
32
+ * @example
33
+ * ```typescript
34
+ * // First call: Executes fetchSession(), tracks Promise
35
+ * const result1 = await manager.deduplicate('getSession', fetchSession);
36
+ *
37
+ * // Concurrent call: Returns existing Promise (no fetchSession() call)
38
+ * const result2 = await manager.deduplicate('getSession', fetchSession);
39
+ *
40
+ * // Both results are identical (same object reference)
41
+ * console.log(result1 === result2); // true
42
+ * ```
43
+ */
44
+ async deduplicate(key, fn) {
45
+ const existing = this.inFlightRequests.get(key);
46
+ if (existing) return existing;
47
+ const promise = fn().finally(() => {
48
+ this.inFlightRequests.delete(key);
49
+ });
50
+ this.inFlightRequests.set(key, promise);
51
+ return promise;
52
+ }
53
+ /**
54
+ * Clear specific in-flight request.
55
+ *
56
+ * Useful for forced refresh or cache invalidation scenarios.
57
+ * Next call with same key will execute fresh request.
58
+ *
59
+ * @param key - Request key to clear
60
+ */
61
+ clear(key) {
62
+ this.inFlightRequests.delete(key);
63
+ }
64
+ /**
65
+ * Clear all in-flight requests.
66
+ *
67
+ * Useful for cleanup on sign-out or reset scenarios.
68
+ */
69
+ clearAll() {
70
+ this.inFlightRequests.clear();
71
+ }
72
+ /**
73
+ * Check if request is in-flight.
74
+ *
75
+ * @param key - Request key to check
76
+ * @returns True if request is currently in-flight
77
+ */
78
+ has(key) {
79
+ return this.inFlightRequests.has(key);
80
+ }
81
+ /**
82
+ * Get count of in-flight requests (for debugging/testing).
83
+ *
84
+ * @returns Number of currently tracked requests
85
+ */
86
+ size() {
87
+ return this.inFlightRequests.size;
88
+ }
89
+ };
90
+ function getJwtExpiration(jwt) {
91
+ try {
92
+ const parts = jwt.split(".");
93
+ if (parts.length !== 3) return null;
94
+ const exp = JSON.parse(atob(parts[1])).exp;
95
+ return typeof exp === "number" ? exp : null;
96
+ } catch {
97
+ return null;
98
+ }
99
+ }
100
+ var TokenCache = class {
101
+ cache = null;
102
+ /**
103
+ * Get cached data if not expired.
104
+ * Returns null if cache is empty or expired.
105
+ */
106
+ get() {
107
+ if (!this.cache) return null;
108
+ if (Date.now() > this.cache.expiresAt) {
109
+ this.cache = null;
110
+ return null;
111
+ }
112
+ return this.cache.data;
113
+ }
114
+ /**
115
+ * Set cached data with TTL.
116
+ * If jwt is provided, TTL is calculated from its expiration.
117
+ * Otherwise, uses default SESSION_CACHE_TTL_MS.
118
+ */
119
+ set(data, jwt) {
120
+ const ttl = this.calculateTTL(jwt);
121
+ this.cache = {
122
+ data,
123
+ expiresAt: Date.now() + ttl
124
+ };
125
+ }
126
+ /**
127
+ * Clear the cache.
128
+ */
129
+ clear() {
130
+ this.cache = null;
131
+ }
132
+ /**
133
+ * Check if cache has valid (non-expired) data.
134
+ */
135
+ has() {
136
+ return this.get() !== null;
137
+ }
138
+ /**
139
+ * Calculate cache TTL from JWT expiration.
140
+ * Falls back to default TTL if JWT is invalid or missing.
141
+ */
142
+ calculateTTL(jwt) {
143
+ if (!jwt) return SESSION_CACHE_TTL_MS;
144
+ const exp = getJwtExpiration(jwt);
145
+ if (!exp) return SESSION_CACHE_TTL_MS;
146
+ const now = Date.now();
147
+ const ttl = exp * 1e3 - now - CLOCK_SKEW_BUFFER_MS;
148
+ return Math.max(ttl, 1e3);
149
+ }
150
+ };
151
+ var SessionCacheManager = class {
152
+ cache = new TokenCache();
153
+ lastSessionData = null;
154
+ invalidated = false;
155
+ /**
156
+ * Get cached session if valid and not expired.
157
+ * Returns null if cache is invalid, expired, or doesn't exist.
158
+ */
159
+ getCachedSession() {
160
+ if (this.invalidated) return null;
161
+ return this.cache.get();
162
+ }
163
+ /**
164
+ * Set cached session with JWT-based TTL.
165
+ * Skips caching if cache was invalidated (sign-out scenario).
166
+ */
167
+ setCachedSession(data) {
168
+ if (this.invalidated) return;
169
+ this.lastSessionData = this.cache.get();
170
+ this.cache.set(data, data.session.token);
171
+ }
172
+ /**
173
+ * Invalidate cache (marks as invalid but doesn't clear).
174
+ * Useful for sign-out scenarios where in-flight requests should not cache.
175
+ */
176
+ invalidateSessionCache() {
177
+ this.invalidated = true;
178
+ }
179
+ /**
180
+ * Clear cache completely.
181
+ */
182
+ clearSessionCache() {
183
+ this.cache.clear();
184
+ this.lastSessionData = null;
185
+ this.invalidated = false;
186
+ }
187
+ /**
188
+ * Check if token was refreshed by comparing tokens with previous session.
189
+ * Returns true if tokens differ (token was refreshed), false otherwise.
190
+ */
191
+ wasTokenRefreshed(data) {
192
+ if (!this.lastSessionData?.session?.token || !data?.session?.token) return false;
193
+ return this.lastSessionData.session.token !== data.session.token;
194
+ }
195
+ };
196
+ var AnonymousTokenCacheManager = class {
197
+ cache = new TokenCache();
198
+ /**
199
+ * Get cached anonymous token response if not expired.
200
+ * Returns null if cache is empty or expired.
201
+ */
202
+ getCachedResponse() {
203
+ return this.cache.get();
204
+ }
205
+ /**
206
+ * Set cached anonymous token response with JWT-based TTL.
207
+ * TTL is automatically calculated from the JWT expiration.
208
+ */
209
+ setCachedResponse(data) {
210
+ this.cache.set(data, data.token);
211
+ }
212
+ /**
213
+ * Clear the cache.
214
+ */
215
+ clearCache() {
216
+ this.cache.clear();
217
+ }
218
+ /**
219
+ * Check if cache has a valid (non-expired) response.
220
+ */
221
+ hasCachedResponse() {
222
+ return this.cache.has();
223
+ }
224
+ };
225
+ async function openOAuthPopup(url) {
226
+ const timeout = 12e4;
227
+ const pollInterval = 500;
228
+ return new Promise((resolve, reject) => {
229
+ const popup = globalThis.open(url, "neon_oauth_popup", "width=500,height=700,popup=yes");
230
+ if (!popup || popup.closed) {
231
+ reject(/* @__PURE__ */ new Error("Popup blocked. Please allow popups for this site."));
232
+ return;
233
+ }
234
+ const timeoutId = setTimeout(() => {
235
+ cleanup();
236
+ try {
237
+ popup.close();
238
+ } catch {
239
+ }
240
+ reject(/* @__PURE__ */ new Error("OAuth popup timed out. Please try again."));
241
+ }, timeout);
242
+ const pollId = setInterval(() => {
243
+ try {
244
+ if (popup.closed) {
245
+ cleanup();
246
+ reject(/* @__PURE__ */ new Error("OAuth popup was closed. Please try again."));
247
+ }
248
+ } catch {
249
+ }
250
+ }, pollInterval);
251
+ function cleanup() {
252
+ clearTimeout(timeoutId);
253
+ clearInterval(pollId);
254
+ globalThis.removeEventListener("message", handleMessage);
255
+ }
256
+ function handleMessage(event) {
257
+ if (event.origin !== globalThis.location.origin) return;
258
+ if (event.data?.type !== OAUTH_POPUP_MESSAGE_TYPE) return;
259
+ cleanup();
260
+ resolve({ verifier: event.data.verifier || null });
261
+ }
262
+ globalThis.addEventListener("message", handleMessage);
263
+ });
264
+ }
265
+ var isBrowser = () => {
266
+ return globalThis.window !== void 0 && typeof document !== "undefined";
267
+ };
268
+ var isIframe = () => {
269
+ if (!isBrowser()) return false;
270
+ try {
271
+ return globalThis.self !== globalThis.top;
272
+ } catch {
273
+ return true;
274
+ }
275
+ };
276
+ var ANONYMOUS_TOKEN_ENDPOINT = "/token/anonymous";
277
+ var anonymousTokenResponseSchema = z.object({
278
+ token: z.string(),
279
+ expires_at: z.number()
280
+ });
281
+ var anonymousTokenClient = () => {
282
+ return {
283
+ id: "anonymous-token",
284
+ pathMethods: { [ANONYMOUS_TOKEN_ENDPOINT]: "GET" },
285
+ getActions: ($fetch) => {
286
+ return { getAnonymousToken: async (fetchOptions) => {
287
+ return await $fetch(ANONYMOUS_TOKEN_ENDPOINT, {
288
+ method: "GET",
289
+ ...fetchOptions
290
+ });
291
+ } };
292
+ }
293
+ };
294
+ };
295
+ var CURRENT_TAB_CLIENT_ID = crypto.randomUUID();
296
+ var BETTER_AUTH_METHODS_IN_FLIGHT_REQUESTS = new InFlightRequestManager();
297
+ var BETTER_AUTH_METHODS_CACHE = new SessionCacheManager();
298
+ var BETTER_AUTH_ANONYMOUS_TOKEN_CACHE = new AnonymousTokenCacheManager();
299
+ var BETTER_AUTH_ENDPOINTS = {
300
+ signUp: "/sign-up",
301
+ signIn: "/sign-in",
302
+ signOut: "/sign-out",
303
+ updateUser: "/update-user",
304
+ getSession: "/get-session",
305
+ token: "/token",
306
+ anonymousSignIn: "/sign-in/anonymous",
307
+ anonymousToken: "/token/anonymous"
308
+ };
309
+ async function handleSocialSignInViaPopup(input, init) {
310
+ const body = JSON.parse(init?.body || "{}");
311
+ const originalCallbackURL = body.callbackURL || "/";
312
+ const popupCallbackUrl = new URL(NEON_AUTH_POPUP_CALLBACK_ROUTE, globalThis.location.origin);
313
+ popupCallbackUrl.searchParams.set(NEON_AUTH_POPUP_PARAM_NAME, "1");
314
+ popupCallbackUrl.searchParams.set(NEON_AUTH_POPUP_CALLBACK_PARAM_NAME, originalCallbackURL);
315
+ body.callbackURL = popupCallbackUrl.toString();
316
+ body.disableRedirect = true;
317
+ const response = await fetch(input, {
318
+ ...init,
319
+ body: JSON.stringify(body)
320
+ });
321
+ const data = await response.json();
322
+ const oauthUrl = data.url;
323
+ if (!oauthUrl) throw new Error("Failed to get OAuth URL");
324
+ const popupResult = await openOAuthPopup(oauthUrl);
325
+ if (!popupResult.verifier) throw new Error("OAuth completed but no session verifier received");
326
+ const navigationUrl = new URL(originalCallbackURL, globalThis.location.origin);
327
+ navigationUrl.searchParams.set(NEON_AUTH_SESSION_VERIFIER_PARAM_NAME, popupResult.verifier);
328
+ globalThis.location.href = navigationUrl.toString();
329
+ return Response.json(data, { status: response.status });
330
+ }
331
+ var BETTER_AUTH_METHODS_HOOKS = {
332
+ signUp: {
333
+ onRequest: () => {
334
+ },
335
+ onSuccess: (responseData) => {
336
+ if (isSessionResponseData(responseData)) {
337
+ const sessionData = {
338
+ session: responseData.session,
339
+ user: responseData.user
340
+ };
341
+ BETTER_AUTH_METHODS_CACHE.setCachedSession(sessionData);
342
+ emitAuthEvent({
343
+ type: "SIGN_IN",
344
+ data: sessionData
345
+ });
346
+ }
347
+ }
348
+ },
349
+ signIn: {
350
+ beforeFetch: (input, init) => {
351
+ if (!(typeof input === "string" ? input : input.toString()).includes("/sign-in/social") || !isIframe()) return null;
352
+ return handleSocialSignInViaPopup(input, init);
353
+ },
354
+ onRequest: () => {
355
+ },
356
+ onSuccess: (responseData) => {
357
+ if (isSessionResponseData(responseData)) {
358
+ const sessionData = {
359
+ session: responseData.session,
360
+ user: responseData.user
361
+ };
362
+ BETTER_AUTH_METHODS_CACHE.setCachedSession(sessionData);
363
+ emitAuthEvent({
364
+ type: "SIGN_IN",
365
+ data: sessionData
366
+ });
367
+ }
368
+ }
369
+ },
370
+ signOut: {
371
+ onRequest: () => {
372
+ BETTER_AUTH_METHODS_CACHE.invalidateSessionCache();
373
+ BETTER_AUTH_METHODS_IN_FLIGHT_REQUESTS.clearAll();
374
+ },
375
+ onSuccess: () => {
376
+ BETTER_AUTH_METHODS_CACHE.clearSessionCache();
377
+ emitAuthEvent({ type: "SIGN_OUT" });
378
+ }
379
+ },
380
+ updateUser: {
381
+ onRequest: () => {
382
+ },
383
+ onSuccess: (responseData) => {
384
+ if (isSessionResponseData(responseData)) {
385
+ const sessionData = {
386
+ session: responseData.session,
387
+ user: responseData.user
388
+ };
389
+ BETTER_AUTH_METHODS_CACHE.setCachedSession(sessionData);
390
+ emitAuthEvent({
391
+ type: "USER_UPDATE",
392
+ data: sessionData
393
+ });
394
+ } else {
395
+ BETTER_AUTH_METHODS_CACHE.clearSessionCache();
396
+ emitAuthEvent({ type: "USER_UPDATE" });
397
+ }
398
+ }
399
+ },
400
+ getSession: {
401
+ beforeFetch: () => {
402
+ if (isBrowser()) {
403
+ if (new URLSearchParams(globalThis.window.location.search).has(NEON_AUTH_SESSION_VERIFIER_PARAM_NAME)) return null;
404
+ }
405
+ const cachedData = BETTER_AUTH_METHODS_CACHE.getCachedSession();
406
+ if (!cachedData) return null;
407
+ return Response.json(cachedData, { status: 200 });
408
+ },
409
+ onRequest: (ctx) => {
410
+ if (!isBrowser()) return;
411
+ const neonAuthSessionVerifierParam = new URLSearchParams(globalThis.window.location.search).get(NEON_AUTH_SESSION_VERIFIER_PARAM_NAME);
412
+ if (neonAuthSessionVerifierParam) {
413
+ const url = typeof ctx.url === "string" ? new URL(ctx.url) : ctx.url;
414
+ url.searchParams.set(NEON_AUTH_SESSION_VERIFIER_PARAM_NAME, neonAuthSessionVerifierParam);
415
+ return {
416
+ ...ctx,
417
+ url
418
+ };
419
+ }
420
+ },
421
+ onSuccess: (responseData) => {
422
+ if (isSessionResponseData(responseData)) {
423
+ const sessionData = {
424
+ session: responseData.session,
425
+ user: responseData.user
426
+ };
427
+ const wasRefreshed = BETTER_AUTH_METHODS_CACHE.wasTokenRefreshed(sessionData);
428
+ BETTER_AUTH_METHODS_CACHE.setCachedSession(sessionData);
429
+ if (wasRefreshed) emitAuthEvent({
430
+ type: "TOKEN_REFRESH",
431
+ data: sessionData
432
+ });
433
+ if (isBrowser()) {
434
+ const url = new URL(globalThis.window.location.href);
435
+ if (url.searchParams.get(NEON_AUTH_SESSION_VERIFIER_PARAM_NAME)) {
436
+ url.searchParams.delete(NEON_AUTH_SESSION_VERIFIER_PARAM_NAME);
437
+ history.replaceState(history.state, "", url.href);
438
+ }
439
+ }
440
+ }
441
+ }
442
+ },
443
+ anonymousToken: {
444
+ beforeFetch: () => {
445
+ const cachedResponse = BETTER_AUTH_ANONYMOUS_TOKEN_CACHE.getCachedResponse();
446
+ if (!cachedResponse) return null;
447
+ return Response.json(cachedResponse, { status: 200 });
448
+ },
449
+ onRequest: () => {
450
+ },
451
+ onSuccess: (responseData) => {
452
+ const parsed = anonymousTokenResponseSchema.safeParse(responseData);
453
+ if (parsed.success) BETTER_AUTH_ANONYMOUS_TOKEN_CACHE.setCachedResponse(parsed.data);
454
+ }
455
+ }
456
+ };
457
+ async function emitAuthEvent(event) {
458
+ const eventType = mapToEventType(event);
459
+ const sessionData = "data" in event ? event.data : null;
460
+ const trigger = mapToTrigger(event);
461
+ if (trigger) getGlobalBroadcastChannel().post({
462
+ event: "session",
463
+ data: { trigger },
464
+ clientId: CURRENT_TAB_CLIENT_ID
465
+ });
466
+ getGlobalBroadcastChannel().post({
467
+ event: "session",
468
+ data: {
469
+ trigger: eventType,
470
+ sessionData
471
+ },
472
+ clientId: CURRENT_TAB_CLIENT_ID
473
+ });
474
+ }
475
+ function mapToEventType(event) {
476
+ switch (event.type) {
477
+ case "SIGN_IN":
478
+ return "SIGNED_IN";
479
+ case "SIGN_OUT":
480
+ return "SIGNED_OUT";
481
+ case "TOKEN_REFRESH":
482
+ return "TOKEN_REFRESHED";
483
+ case "USER_UPDATE":
484
+ return "USER_UPDATED";
485
+ }
486
+ }
487
+ function mapToTrigger(event) {
488
+ switch (event.type) {
489
+ case "SIGN_OUT":
490
+ return "signout";
491
+ case "TOKEN_REFRESH":
492
+ return null;
493
+ case "USER_UPDATE":
494
+ return "updateUser";
495
+ case "SIGN_IN":
496
+ return null;
497
+ }
498
+ }
499
+ function isSessionResponseData(responseData) {
500
+ return Boolean(responseData && typeof responseData === "object" && "session" in responseData && "user" in responseData && responseData.session !== null && responseData.user !== null);
501
+ }
502
+ function deriveBetterAuthMethodFromUrl(url) {
503
+ if (url.includes("/sign-in/anonymous")) return "anonymousSignIn";
504
+ if (url.includes(BETTER_AUTH_ENDPOINTS.anonymousToken)) return "anonymousToken";
505
+ if (url.includes(BETTER_AUTH_ENDPOINTS.signIn)) return "signIn";
506
+ if (url.includes(BETTER_AUTH_ENDPOINTS.signUp)) return "signUp";
507
+ if (url.includes(BETTER_AUTH_ENDPOINTS.signOut)) return "signOut";
508
+ if (url.includes(BETTER_AUTH_ENDPOINTS.updateUser)) return "updateUser";
509
+ if (url.includes(BETTER_AUTH_ENDPOINTS.getSession) || url.includes(BETTER_AUTH_ENDPOINTS.token)) return "getSession";
510
+ }
511
+ function initBroadcastChannel() {
512
+ if (isBrowser() && globalThis.opener && globalThis.opener !== globalThis) {
513
+ const params = new URLSearchParams(globalThis.location.search);
514
+ if (params.has(NEON_AUTH_POPUP_PARAM_NAME)) {
515
+ const verifier = params.get(NEON_AUTH_SESSION_VERIFIER_PARAM_NAME);
516
+ const originalCallback = params.get(NEON_AUTH_POPUP_CALLBACK_PARAM_NAME);
517
+ globalThis.opener.postMessage({
518
+ type: OAUTH_POPUP_MESSAGE_TYPE,
519
+ verifier,
520
+ originalCallback
521
+ }, "*");
522
+ globalThis.close();
523
+ return;
524
+ }
525
+ }
526
+ getGlobalBroadcastChannel().subscribe((message) => {
527
+ if (message.clientId === CURRENT_TAB_CLIENT_ID) return;
528
+ const trigger = message.data?.trigger;
529
+ if (trigger === "signout" || trigger === "updateUser" || trigger === "getSession") BETTER_AUTH_METHODS_CACHE.clearSessionCache();
530
+ });
531
+ }
532
+ var name = "@webtwizz/cloud";
533
+ var version = "0.5.0-beta";
534
+ var X_NEON_CLIENT_INFO_HEADER = "X-WebTwizz-Client-Info";
535
+ function hasGlobalProperty(key) {
536
+ return key in globalThis;
537
+ }
538
+ function detectFramework() {
539
+ if (typeof process !== "undefined" && process.env && (process.env.NEXT_RUNTIME || process.env.__NEXT_PRIVATE_ORIGIN)) return "next";
540
+ if (typeof globalThis.window !== "undefined") {
541
+ if (hasGlobalProperty("__NEXT_DATA__")) return "next";
542
+ if (hasGlobalProperty("__remixContext")) return "remix";
543
+ if (hasGlobalProperty("__REACT_DEVTOOLS_GLOBAL_HOOK__")) return "react";
544
+ if (hasGlobalProperty("__VUE__")) return "vue";
545
+ if (hasGlobalProperty("Zone")) return "angular";
546
+ }
547
+ }
548
+ function getClientInfo(sdkName, sdkVersion) {
549
+ const base = {
550
+ sdk: sdkName,
551
+ version: sdkVersion,
552
+ runtime: "unknown",
553
+ runtimeVersion: "unknown",
554
+ platform: "unknown",
555
+ arch: "unknown"
556
+ };
557
+ let result;
558
+ if (typeof process !== "undefined" && process.versions?.node) result = {
559
+ ...base,
560
+ runtime: "node",
561
+ runtimeVersion: process.versions.node,
562
+ platform: process.platform,
563
+ arch: process.arch
564
+ };
565
+ else if (typeof Deno !== "undefined") result = {
566
+ ...base,
567
+ runtime: "deno",
568
+ runtimeVersion: Deno.version?.deno ?? "unknown",
569
+ platform: Deno.build?.os ?? "unknown",
570
+ arch: Deno.build?.arch ?? "unknown"
571
+ };
572
+ else if (typeof Bun !== "undefined") result = {
573
+ ...base,
574
+ runtime: "bun",
575
+ runtimeVersion: Bun.version ?? "unknown",
576
+ platform: process?.platform ?? "unknown",
577
+ arch: process?.arch ?? "unknown"
578
+ };
579
+ else if (typeof EdgeRuntime !== "undefined" || typeof process !== "undefined" && !process.versions?.node && typeof globalThis.window === "undefined" && typeof document === "undefined") result = {
580
+ ...base,
581
+ runtime: "edge"
582
+ };
583
+ else if (globalThis.window !== void 0 && typeof document !== "undefined") result = {
584
+ ...base,
585
+ runtime: "browser",
586
+ runtimeVersion: "unknown",
587
+ platform: "web",
588
+ arch: "unknown"
589
+ };
590
+ else result = base;
591
+ const framework = detectFramework();
592
+ if (framework) result.framework = framework;
593
+ return result;
594
+ }
595
+ function createClientInfoInjector(defaultSdkName, defaultSdkVersion) {
596
+ const cachedClientInfo = JSON.stringify(getClientInfo(defaultSdkName, defaultSdkVersion));
597
+ return function injectClientInfo$1(headers, sdkOverride) {
598
+ const result = new Headers(headers);
599
+ if (result.has(X_NEON_CLIENT_INFO_HEADER)) return result;
600
+ const clientInfoString = sdkOverride ? JSON.stringify(getClientInfo(sdkOverride.name, sdkOverride.version)) : cachedClientInfo;
601
+ result.set(X_NEON_CLIENT_INFO_HEADER, clientInfoString);
602
+ return result;
603
+ };
604
+ }
605
+ var injectClientInfo = createClientInfoInjector(name, version);
606
+ var FORCE_FETCH_HEADER = "X-Force-Fetch";
607
+ var supportedBetterAuthClientPlugins = [
608
+ jwtClient(),
609
+ adminClient(),
610
+ organizationClient(),
611
+ emailOTPClient(),
612
+ magicLinkClient(),
613
+ phoneNumberClient(),
614
+ anonymousTokenClient()
615
+ ];
616
+ var NeonAuthAdapterCore = class {
617
+ betterAuthOptions;
618
+ /**
619
+ * Better Auth adapter implementing the NeonAuthClient interface.
620
+ * See CLAUDE.md for architecture details and API mappings.
621
+ */
622
+ constructor(betterAuthClientOptions) {
623
+ const userOnSuccess = betterAuthClientOptions.fetchOptions?.onSuccess;
624
+ const userOnRequest = betterAuthClientOptions.fetchOptions?.onRequest;
625
+ this.betterAuthOptions = {
626
+ ...betterAuthClientOptions,
627
+ plugins: supportedBetterAuthClientPlugins,
628
+ fetchOptions: {
629
+ ...betterAuthClientOptions.fetchOptions,
630
+ throw: false,
631
+ onRequest: (request) => {
632
+ const url = request.url;
633
+ const method = deriveBetterAuthMethodFromUrl(url.toString());
634
+ if (method) BETTER_AUTH_METHODS_HOOKS[method].onRequest(request);
635
+ userOnRequest?.(request);
636
+ },
637
+ customFetchImpl: async (url, init) => {
638
+ const headers = injectClientInfo(init?.headers);
639
+ if (headers.has(FORCE_FETCH_HEADER)) {
640
+ headers.delete(FORCE_FETCH_HEADER);
641
+ const response$1 = await fetch(url, {
642
+ ...init,
643
+ headers
644
+ });
645
+ if (!response$1.ok) {
646
+ const body = await response$1.clone().json().catch(() => ({}));
647
+ throw normalizeBetterAuthError({
648
+ status: response$1.status,
649
+ statusText: response$1.statusText,
650
+ message: body.message || `HTTP ${response$1.status} ${response$1.statusText}`,
651
+ code: body.code,
652
+ body
653
+ });
654
+ }
655
+ return response$1;
656
+ }
657
+ const betterAuthMethod = deriveBetterAuthMethodFromUrl(url.toString());
658
+ if (betterAuthMethod) {
659
+ const response$1 = await BETTER_AUTH_METHODS_HOOKS[betterAuthMethod].beforeFetch?.(url, init);
660
+ if (response$1) return response$1;
661
+ }
662
+ const key = `${init?.method || "GET"}:${url}:${init?.body || ""}`;
663
+ const response = await BETTER_AUTH_METHODS_IN_FLIGHT_REQUESTS.deduplicate(key, () => fetch(url, {
664
+ ...init,
665
+ headers
666
+ }));
667
+ if (!response.ok) {
668
+ const errorBody = await response.clone().json().catch(() => ({}));
669
+ throw normalizeBetterAuthError({
670
+ status: response.status,
671
+ statusText: response.statusText,
672
+ message: errorBody.message || `HTTP ${response.status} ${response.statusText}`,
673
+ code: errorBody.code,
674
+ body: errorBody
675
+ });
676
+ }
677
+ return response.clone();
678
+ },
679
+ onSuccess: async (ctx) => {
680
+ const jwt = ctx.response.headers.get("set-auth-jwt");
681
+ if (jwt) if (ctx.data?.session) ctx.data.session.token = jwt;
682
+ else console.warn("[onSuccess] JWT found but no session data to inject into!");
683
+ const url = ctx.request.url.toString();
684
+ const responseData = ctx.data;
685
+ const method = deriveBetterAuthMethodFromUrl(url);
686
+ if (method) BETTER_AUTH_METHODS_HOOKS[method].onSuccess(responseData);
687
+ await userOnSuccess?.(ctx);
688
+ }
689
+ }
690
+ };
691
+ initBroadcastChannel();
692
+ }
693
+ /**
694
+ * Get JWT token for authenticated or anonymous access.
695
+ * Single source of truth for token retrieval logic.
696
+ *
697
+ * @param allowAnonymous - When true, fetches anonymous token if no session exists
698
+ * @returns JWT token string or null if unavailable
699
+ */
700
+ async getJWTToken(allowAnonymous) {
701
+ const client = this.getBetterAuthInstance();
702
+ const session = await client.getSession();
703
+ if (session.data?.session?.token) return session.data.session.token;
704
+ if (allowAnonymous) return (await client.getAnonymousToken()).data?.token ?? null;
705
+ return null;
706
+ }
707
+ };
708
+
709
+ // node_modules/@neondatabase/auth/dist/better-auth-react-adapter-DMizSZtB.mjs
710
+ import { createAuthClient } from "better-auth/react";
711
+ var BetterAuthReactAdapterImpl = class extends NeonAuthAdapterCore {
712
+ _betterAuth;
713
+ constructor(betterAuthClientOptions) {
714
+ super(betterAuthClientOptions);
715
+ this._betterAuth = createAuthClient(this.betterAuthOptions);
716
+ }
717
+ getBetterAuthInstance() {
718
+ return this._betterAuth;
719
+ }
720
+ };
721
+ function BetterAuthReactAdapter(options) {
722
+ return (url, fetchOptions) => new BetterAuthReactAdapterImpl({
723
+ baseURL: url,
724
+ ...options,
725
+ fetchOptions: {
726
+ ...options?.fetchOptions,
727
+ headers: {
728
+ ...options?.fetchOptions?.headers,
729
+ ...fetchOptions?.headers
730
+ }
731
+ }
732
+ });
733
+ }
734
+
735
+ // node_modules/@neondatabase/auth/dist/supabase-adapter-CKXlmI3i.mjs
736
+ import { createAuthClient as createAuthClient2, getGlobalBroadcastChannel as getGlobalBroadcastChannel2 } from "better-auth/client";
737
+ import "@supabase/auth-js";
738
+ import { base64url, decodeJwt, decodeProtectedHeader } from "jose";
739
+ var BetterAuthVanillaAdapterImpl = class extends NeonAuthAdapterCore {
740
+ _betterAuth;
741
+ constructor(betterAuthClientOptions) {
742
+ super(betterAuthClientOptions);
743
+ this._betterAuth = createAuthClient2(this.betterAuthOptions);
744
+ }
745
+ getBetterAuthInstance() {
746
+ return this._betterAuth;
747
+ }
748
+ };
749
+ function BetterAuthVanillaAdapter(options) {
750
+ return (url, fetchOptions) => new BetterAuthVanillaAdapterImpl({
751
+ baseURL: url,
752
+ ...options,
753
+ fetchOptions: {
754
+ ...options?.fetchOptions,
755
+ headers: {
756
+ ...options?.fetchOptions?.headers,
757
+ ...fetchOptions?.headers
758
+ }
759
+ }
760
+ });
761
+ }
762
+
763
+ // node_modules/@neondatabase/auth/dist/neon-auth-BLCDc-bu.mjs
764
+ function createInternalNeonAuth(url, config) {
765
+ const adapterBuilder = config?.adapter ?? BetterAuthVanillaAdapter();
766
+ const { fetchOptions } = config ?? {};
767
+ const adapter = adapterBuilder(url, fetchOptions);
768
+ const allowAnonymous = config?.allowAnonymous ?? false;
769
+ if (!(typeof adapter.initialize === "function")) return {
770
+ getJWTToken: () => adapter.getJWTToken(allowAnonymous),
771
+ adapter: adapter.getBetterAuthInstance()
772
+ };
773
+ return {
774
+ getJWTToken: () => adapter.getJWTToken(allowAnonymous),
775
+ adapter
776
+ };
777
+ }
778
+ function createAuthClient3(url, config) {
779
+ return createInternalNeonAuth(url, config).adapter;
780
+ }
781
+
782
+ // node_modules/@neondatabase/auth/dist/next/index.mjs
783
+ function createAuthClient4() {
784
+ return createAuthClient3(void 0, { adapter: BetterAuthReactAdapter() });
785
+ }
786
+
1
787
  // src/client.ts
2
- import { createAuthClient as create } from "@neondatabase/auth/next";
3
- function createAuthClient() {
4
- return create();
788
+ function createAuthClient5() {
789
+ return createAuthClient4();
5
790
  }
6
791
  export {
7
- createAuthClient
792
+ createAuthClient5 as createAuthClient
8
793
  };