@neondatabase/auth 0.1.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.
- package/README.md +260 -0
- package/dist/adapter-core-BDOw-gBC.mjs +494 -0
- package/dist/adapter-core-C12KoaiU.d.mts +2247 -0
- package/dist/adapters-CivF9wql.mjs +1 -0
- package/dist/adapters-Dkx0zoMR.mjs +1 -0
- package/dist/better-auth-react-adapter-BXL48HIU.d.mts +722 -0
- package/dist/better-auth-react-adapter-FnBHa2nr.mjs +49 -0
- package/dist/index-C-svZlpj.d.mts +1 -0
- package/dist/index-DuDD6cIY.d.mts +3 -0
- package/dist/index-UW23fDSn.d.mts +1 -0
- package/dist/index.d.mts +99 -0
- package/dist/index.mjs +3 -0
- package/dist/neon-auth-C9XTFffv.mjs +67 -0
- package/dist/next/index.d.mts +2363 -0
- package/dist/next/index.mjs +179 -0
- package/dist/react/adapters/index.d.mts +4 -0
- package/dist/react/adapters/index.mjs +3 -0
- package/dist/react/index.d.mts +5 -0
- package/dist/react/index.mjs +93 -0
- package/dist/react/ui/index.d.mts +3 -0
- package/dist/react/ui/index.mjs +93 -0
- package/dist/react/ui/server.d.mts +1 -0
- package/dist/react/ui/server.mjs +3 -0
- package/dist/supabase-adapter-crabDnl2.d.mts +128 -0
- package/dist/supabase-adapter-ggmqWgPe.mjs +1623 -0
- package/dist/ui/css.css +2 -0
- package/dist/ui/css.d.ts +1 -0
- package/dist/ui/tailwind.css +6 -0
- package/dist/ui/theme.css +125 -0
- package/dist/ui-CNFBSekF.mjs +401 -0
- package/dist/vanilla/adapters/index.d.mts +4 -0
- package/dist/vanilla/adapters/index.mjs +3 -0
- package/dist/vanilla/index.d.mts +4 -0
- package/dist/vanilla/index.mjs +3 -0
- package/package.json +123 -0
|
@@ -0,0 +1,494 @@
|
|
|
1
|
+
import { getGlobalBroadcastChannel } from "better-auth/client";
|
|
2
|
+
import { adminClient, anonymousClient, emailOTPClient, jwtClient, magicLinkClient, organizationClient, phoneNumberClient } from "better-auth/client/plugins";
|
|
3
|
+
|
|
4
|
+
//#region src/core/in-flight-request-manager.ts
|
|
5
|
+
/**
|
|
6
|
+
* Generic in-flight request deduplication manager.
|
|
7
|
+
*
|
|
8
|
+
* Prevents thundering herd by tracking Promises by key.
|
|
9
|
+
* Multiple concurrent calls with the same key await the same Promise
|
|
10
|
+
* instead of making N identical requests.
|
|
11
|
+
*
|
|
12
|
+
* Example:
|
|
13
|
+
* ```typescript
|
|
14
|
+
* const manager = new InFlightRequestManager();
|
|
15
|
+
*
|
|
16
|
+
* // 10 concurrent calls deduplicate to 1 actual fetch
|
|
17
|
+
* const results = await Promise.all([
|
|
18
|
+
* manager.deduplicate('user:123', () => fetchUser(123)),
|
|
19
|
+
* manager.deduplicate('user:123', () => fetchUser(123)),
|
|
20
|
+
* // ... 8 more calls
|
|
21
|
+
* ]);
|
|
22
|
+
* // Result: 1 fetch call, 10 identical results
|
|
23
|
+
* ```
|
|
24
|
+
*
|
|
25
|
+
* Thread Safety: JavaScript is single-threaded, no race conditions possible
|
|
26
|
+
*/
|
|
27
|
+
var InFlightRequestManager = class {
|
|
28
|
+
/**
|
|
29
|
+
* Map of request keys to in-flight Promises.
|
|
30
|
+
* Automatically cleared after Promise resolution (success or error).
|
|
31
|
+
*/
|
|
32
|
+
inFlightRequests = /* @__PURE__ */ new Map();
|
|
33
|
+
/**
|
|
34
|
+
* Execute function with deduplication.
|
|
35
|
+
*
|
|
36
|
+
* If request with same key is in-flight, returns existing Promise.
|
|
37
|
+
* Otherwise, executes fn and tracks the Promise.
|
|
38
|
+
*
|
|
39
|
+
* @param key - Unique identifier for this request (e.g., "getSession")
|
|
40
|
+
* @param fn - Async function to execute (only called if no in-flight request exists)
|
|
41
|
+
* @returns Promise that resolves to the function result
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* ```typescript
|
|
45
|
+
* // First call: Executes fetchSession(), tracks Promise
|
|
46
|
+
* const result1 = await manager.deduplicate('getSession', fetchSession);
|
|
47
|
+
*
|
|
48
|
+
* // Concurrent call: Returns existing Promise (no fetchSession() call)
|
|
49
|
+
* const result2 = await manager.deduplicate('getSession', fetchSession);
|
|
50
|
+
*
|
|
51
|
+
* // Both results are identical (same object reference)
|
|
52
|
+
* console.log(result1 === result2); // true
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
async deduplicate(key, fn) {
|
|
56
|
+
const existing = this.inFlightRequests.get(key);
|
|
57
|
+
if (existing) return existing;
|
|
58
|
+
const promise = fn().finally(() => {
|
|
59
|
+
this.inFlightRequests.delete(key);
|
|
60
|
+
});
|
|
61
|
+
this.inFlightRequests.set(key, promise);
|
|
62
|
+
return promise;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Clear specific in-flight request.
|
|
66
|
+
*
|
|
67
|
+
* Useful for forced refresh or cache invalidation scenarios.
|
|
68
|
+
* Next call with same key will execute fresh request.
|
|
69
|
+
*
|
|
70
|
+
* @param key - Request key to clear
|
|
71
|
+
*/
|
|
72
|
+
clear(key) {
|
|
73
|
+
this.inFlightRequests.delete(key);
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Clear all in-flight requests.
|
|
77
|
+
*
|
|
78
|
+
* Useful for cleanup on sign-out or reset scenarios.
|
|
79
|
+
*/
|
|
80
|
+
clearAll() {
|
|
81
|
+
this.inFlightRequests.clear();
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Check if request is in-flight.
|
|
85
|
+
*
|
|
86
|
+
* @param key - Request key to check
|
|
87
|
+
* @returns True if request is currently in-flight
|
|
88
|
+
*/
|
|
89
|
+
has(key) {
|
|
90
|
+
return this.inFlightRequests.has(key);
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Get count of in-flight requests (for debugging/testing).
|
|
94
|
+
*
|
|
95
|
+
* @returns Number of currently tracked requests
|
|
96
|
+
*/
|
|
97
|
+
size() {
|
|
98
|
+
return this.inFlightRequests.size;
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
|
|
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
|
+
//#endregion
|
|
122
|
+
//#region src/utils/jwt.ts
|
|
123
|
+
/**
|
|
124
|
+
* Extract expiration timestamp from JWT payload
|
|
125
|
+
* @param jwt - The JWT token string
|
|
126
|
+
* @returns Expiration timestamp in seconds (Unix time) or null if invalid
|
|
127
|
+
*/
|
|
128
|
+
function getJwtExpiration(jwt) {
|
|
129
|
+
try {
|
|
130
|
+
const parts = jwt.split(".");
|
|
131
|
+
if (parts.length !== 3) return null;
|
|
132
|
+
const exp = JSON.parse(atob(parts[1])).exp;
|
|
133
|
+
return typeof exp === "number" ? exp : null;
|
|
134
|
+
} catch {
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
//#endregion
|
|
140
|
+
//#region src/core/session-cache-manager.ts
|
|
141
|
+
/**
|
|
142
|
+
* Manages in-memory session cache with TTL expiration.
|
|
143
|
+
*
|
|
144
|
+
* Features:
|
|
145
|
+
* - Stores sessions in Better Auth native format
|
|
146
|
+
* - Automatic expiration based on JWT token expiration
|
|
147
|
+
* - Invalidation flag for sign-out scenarios
|
|
148
|
+
* - TTL calculation with clock skew buffer
|
|
149
|
+
*
|
|
150
|
+
* Example:
|
|
151
|
+
* ```typescript
|
|
152
|
+
* const cacheManager = new SessionCacheManager();
|
|
153
|
+
* cacheManager.setCachedSession({ session, user });
|
|
154
|
+
* const cached = cacheManager.getCachedSession();
|
|
155
|
+
* ```
|
|
156
|
+
*/
|
|
157
|
+
var SessionCacheManager = class {
|
|
158
|
+
cache = null;
|
|
159
|
+
lastSessionData = null;
|
|
160
|
+
/**
|
|
161
|
+
* Get cached session if valid and not expired.
|
|
162
|
+
* Returns null if cache is invalid, expired, or doesn't exist.
|
|
163
|
+
*/
|
|
164
|
+
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;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Set cached session with optional TTL.
|
|
174
|
+
* If TTL not provided, calculates from JWT expiration.
|
|
175
|
+
* Skips caching if cache was invalidated (sign-out scenario).
|
|
176
|
+
*/
|
|
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
|
+
};
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Invalidate cache (marks as invalid but doesn't clear).
|
|
189
|
+
* Useful for sign-out scenarios where in-flight requests should not cache.
|
|
190
|
+
*/
|
|
191
|
+
invalidateSessionCache() {
|
|
192
|
+
if (this.cache) this.cache.invalidated = true;
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Clear cache completely.
|
|
196
|
+
*/
|
|
197
|
+
clearSessionCache() {
|
|
198
|
+
this.cache = null;
|
|
199
|
+
this.lastSessionData = null;
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Check if token was refreshed by comparing tokens with previous session.
|
|
203
|
+
* Returns true if tokens differ (token was refreshed), false otherwise.
|
|
204
|
+
*/
|
|
205
|
+
wasTokenRefreshed(data) {
|
|
206
|
+
if (!this.lastSessionData?.session?.token || !data?.session?.token) return false;
|
|
207
|
+
return this.lastSessionData.session.token !== data.session.token;
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Calculate cache TTL from JWT expiration.
|
|
211
|
+
* Falls back to default TTL if JWT is invalid or missing.
|
|
212
|
+
*/
|
|
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);
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
//#endregion
|
|
224
|
+
//#region src/utils/browser.ts
|
|
225
|
+
/**
|
|
226
|
+
* Checks if the code is running in a browser environment
|
|
227
|
+
* @returns true if in browser, false otherwise (e.g., Node.js)
|
|
228
|
+
*/
|
|
229
|
+
const isBrowser = () => {
|
|
230
|
+
return globalThis.window !== void 0 && typeof document !== "undefined";
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
//#endregion
|
|
234
|
+
//#region src/core/better-auth-methods.ts
|
|
235
|
+
const CURRENT_TAB_CLIENT_ID = crypto.randomUUID();
|
|
236
|
+
const BETTER_AUTH_METHODS_IN_FLIGHT_REQUESTS = new InFlightRequestManager();
|
|
237
|
+
const BETTER_AUTH_METHODS_CACHE = new SessionCacheManager();
|
|
238
|
+
const BETTER_AUTH_ENDPOINTS = {
|
|
239
|
+
signUp: "/sign-up",
|
|
240
|
+
signIn: "/sign-in",
|
|
241
|
+
signOut: "/sign-out",
|
|
242
|
+
updateUser: "/update-user",
|
|
243
|
+
getSession: "/get-session",
|
|
244
|
+
token: "/token"
|
|
245
|
+
};
|
|
246
|
+
const BETTER_AUTH_METHODS_HOOKS = {
|
|
247
|
+
signUp: {
|
|
248
|
+
onRequest: () => {},
|
|
249
|
+
onSuccess: (responseData) => {
|
|
250
|
+
if (isSessionResponseData(responseData)) {
|
|
251
|
+
const sessionData = {
|
|
252
|
+
session: responseData.session,
|
|
253
|
+
user: responseData.user
|
|
254
|
+
};
|
|
255
|
+
BETTER_AUTH_METHODS_CACHE.setCachedSession(sessionData);
|
|
256
|
+
emitAuthEvent({
|
|
257
|
+
type: "SIGN_IN",
|
|
258
|
+
data: sessionData
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
},
|
|
263
|
+
signIn: {
|
|
264
|
+
onRequest: () => {},
|
|
265
|
+
onSuccess: (responseData) => {
|
|
266
|
+
if (isSessionResponseData(responseData)) {
|
|
267
|
+
const sessionData = {
|
|
268
|
+
session: responseData.session,
|
|
269
|
+
user: responseData.user
|
|
270
|
+
};
|
|
271
|
+
BETTER_AUTH_METHODS_CACHE.setCachedSession(sessionData);
|
|
272
|
+
emitAuthEvent({
|
|
273
|
+
type: "SIGN_IN",
|
|
274
|
+
data: sessionData
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
},
|
|
279
|
+
signOut: {
|
|
280
|
+
onRequest: () => {
|
|
281
|
+
BETTER_AUTH_METHODS_CACHE.invalidateSessionCache();
|
|
282
|
+
BETTER_AUTH_METHODS_IN_FLIGHT_REQUESTS.clearAll();
|
|
283
|
+
},
|
|
284
|
+
onSuccess: () => {
|
|
285
|
+
BETTER_AUTH_METHODS_CACHE.clearSessionCache();
|
|
286
|
+
emitAuthEvent({ type: "SIGN_OUT" });
|
|
287
|
+
}
|
|
288
|
+
},
|
|
289
|
+
updateUser: {
|
|
290
|
+
onRequest: () => {},
|
|
291
|
+
onSuccess: (responseData) => {
|
|
292
|
+
if (isSessionResponseData(responseData)) emitAuthEvent({
|
|
293
|
+
type: "USER_UPDATE",
|
|
294
|
+
data: {
|
|
295
|
+
session: responseData.session,
|
|
296
|
+
user: responseData.user
|
|
297
|
+
}
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
},
|
|
301
|
+
getSession: {
|
|
302
|
+
beforeRequest: () => {
|
|
303
|
+
const cachedData = BETTER_AUTH_METHODS_CACHE.getCachedSession();
|
|
304
|
+
if (!cachedData) return null;
|
|
305
|
+
return Response.json(cachedData, { status: 200 });
|
|
306
|
+
},
|
|
307
|
+
onRequest: (ctx) => {
|
|
308
|
+
if (!isBrowser()) return;
|
|
309
|
+
const neonAuthSessionVerifierParam = new URLSearchParams(globalThis.window.location.search).get(NEON_AUTH_SESSION_VERIFIER_PARAM_NAME);
|
|
310
|
+
if (neonAuthSessionVerifierParam) {
|
|
311
|
+
const url = typeof ctx.url === "string" ? new URL(ctx.url) : ctx.url;
|
|
312
|
+
url.searchParams.set(NEON_AUTH_SESSION_VERIFIER_PARAM_NAME, neonAuthSessionVerifierParam);
|
|
313
|
+
return {
|
|
314
|
+
...ctx,
|
|
315
|
+
url
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
},
|
|
319
|
+
onSuccess: (responseData) => {
|
|
320
|
+
if (isSessionResponseData(responseData)) {
|
|
321
|
+
const sessionData = {
|
|
322
|
+
session: responseData.session,
|
|
323
|
+
user: responseData.user
|
|
324
|
+
};
|
|
325
|
+
const wasRefreshed = BETTER_AUTH_METHODS_CACHE.wasTokenRefreshed(sessionData);
|
|
326
|
+
BETTER_AUTH_METHODS_CACHE.setCachedSession(sessionData);
|
|
327
|
+
if (wasRefreshed) emitAuthEvent({
|
|
328
|
+
type: "TOKEN_REFRESH",
|
|
329
|
+
data: sessionData
|
|
330
|
+
});
|
|
331
|
+
if (isBrowser()) {
|
|
332
|
+
const url = new URL(globalThis.window.location.href);
|
|
333
|
+
if (url.searchParams.get(NEON_AUTH_SESSION_VERIFIER_PARAM_NAME)) {
|
|
334
|
+
url.searchParams.delete(NEON_AUTH_SESSION_VERIFIER_PARAM_NAME);
|
|
335
|
+
history.replaceState(history.state, "", url.href);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
};
|
|
342
|
+
/**
|
|
343
|
+
* Unified event emission method that handles Better Auth broadcasts.
|
|
344
|
+
* Broadcasts use Better Auth native format - each adapter handles
|
|
345
|
+
* conversion to their specific format (e.g., Supabase Session).
|
|
346
|
+
*
|
|
347
|
+
* This ensures:
|
|
348
|
+
* - Single source of truth for all event emissions
|
|
349
|
+
* - Better Auth ecosystem compatibility via getGlobalBroadcastChannel()
|
|
350
|
+
* - Adapter-agnostic event format
|
|
351
|
+
* - Cross-tab synchronization via Better Auth's broadcast system
|
|
352
|
+
*/
|
|
353
|
+
async function emitAuthEvent(event) {
|
|
354
|
+
const eventType = mapToEventType(event);
|
|
355
|
+
const sessionData = "data" in event ? event.data : null;
|
|
356
|
+
const trigger = mapToTrigger(event);
|
|
357
|
+
if (trigger) getGlobalBroadcastChannel().post({
|
|
358
|
+
event: "session",
|
|
359
|
+
data: { trigger },
|
|
360
|
+
clientId: CURRENT_TAB_CLIENT_ID
|
|
361
|
+
});
|
|
362
|
+
getGlobalBroadcastChannel().post({
|
|
363
|
+
event: "session",
|
|
364
|
+
data: {
|
|
365
|
+
trigger: eventType,
|
|
366
|
+
sessionData
|
|
367
|
+
},
|
|
368
|
+
clientId: CURRENT_TAB_CLIENT_ID
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
/** Maps internal event types to NeonAuthChangeEvent */
|
|
372
|
+
function mapToEventType(event) {
|
|
373
|
+
switch (event.type) {
|
|
374
|
+
case "SIGN_IN": return "SIGNED_IN";
|
|
375
|
+
case "SIGN_OUT": return "SIGNED_OUT";
|
|
376
|
+
case "TOKEN_REFRESH": return "TOKEN_REFRESHED";
|
|
377
|
+
case "USER_UPDATE": return "USER_UPDATED";
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
/** Maps internal event types to Better Auth broadcast triggers */
|
|
381
|
+
function mapToTrigger(event) {
|
|
382
|
+
switch (event.type) {
|
|
383
|
+
case "SIGN_OUT": return "signout";
|
|
384
|
+
case "TOKEN_REFRESH": return null;
|
|
385
|
+
case "USER_UPDATE": return "updateUser";
|
|
386
|
+
case "SIGN_IN": return null;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
/**
|
|
390
|
+
* Type guard that validates response data has non-null session and user.
|
|
391
|
+
* Narrows the type to ensure session and user are not null.
|
|
392
|
+
*/
|
|
393
|
+
function isSessionResponseData(responseData) {
|
|
394
|
+
return Boolean(responseData && typeof responseData === "object" && "session" in responseData && "user" in responseData && responseData.session !== null && responseData.user !== null);
|
|
395
|
+
}
|
|
396
|
+
function deriveBetterAuthMethodFromUrl(url) {
|
|
397
|
+
if (url.includes(BETTER_AUTH_ENDPOINTS.signIn)) return "signIn";
|
|
398
|
+
if (url.includes(BETTER_AUTH_ENDPOINTS.signUp)) return "signUp";
|
|
399
|
+
if (url.includes(BETTER_AUTH_ENDPOINTS.signOut)) return "signOut";
|
|
400
|
+
if (url.includes(BETTER_AUTH_ENDPOINTS.updateUser)) return "updateUser";
|
|
401
|
+
if (url.includes(BETTER_AUTH_ENDPOINTS.getSession) || url.includes(BETTER_AUTH_ENDPOINTS.token)) return "getSession";
|
|
402
|
+
}
|
|
403
|
+
function initBroadcastChannel() {
|
|
404
|
+
getGlobalBroadcastChannel().subscribe((message) => {
|
|
405
|
+
if (message.clientId === CURRENT_TAB_CLIENT_ID) return;
|
|
406
|
+
const trigger = message.data?.trigger;
|
|
407
|
+
if (trigger === "signout" || trigger === "updateUser" || trigger === "getSession") BETTER_AUTH_METHODS_CACHE.clearSessionCache();
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
//#endregion
|
|
412
|
+
//#region src/core/adapter-core.ts
|
|
413
|
+
const FORCE_FETCH_HEADER = "X-Force-Fetch";
|
|
414
|
+
const supportedBetterAuthClientPlugins = [
|
|
415
|
+
jwtClient(),
|
|
416
|
+
adminClient(),
|
|
417
|
+
organizationClient(),
|
|
418
|
+
emailOTPClient(),
|
|
419
|
+
anonymousClient(),
|
|
420
|
+
phoneNumberClient(),
|
|
421
|
+
magicLinkClient()
|
|
422
|
+
];
|
|
423
|
+
var NeonAuthAdapterCore = class {
|
|
424
|
+
betterAuthOptions;
|
|
425
|
+
/**
|
|
426
|
+
* Better Auth adapter implementing the NeonAuthClient interface.
|
|
427
|
+
* See CLAUDE.md for architecture details and API mappings.
|
|
428
|
+
*/
|
|
429
|
+
constructor(betterAuthClientOptions) {
|
|
430
|
+
const userOnSuccess = betterAuthClientOptions.fetchOptions?.onSuccess;
|
|
431
|
+
const userOnRequest = betterAuthClientOptions.fetchOptions?.onRequest;
|
|
432
|
+
this.betterAuthOptions = {
|
|
433
|
+
...betterAuthClientOptions,
|
|
434
|
+
plugins: supportedBetterAuthClientPlugins,
|
|
435
|
+
fetchOptions: {
|
|
436
|
+
...betterAuthClientOptions.fetchOptions,
|
|
437
|
+
throw: false,
|
|
438
|
+
onRequest: (request) => {
|
|
439
|
+
const url = request.url;
|
|
440
|
+
const method = deriveBetterAuthMethodFromUrl(url.toString());
|
|
441
|
+
if (method) BETTER_AUTH_METHODS_HOOKS[method].onRequest(request);
|
|
442
|
+
userOnRequest?.(request);
|
|
443
|
+
},
|
|
444
|
+
customFetchImpl: async (url, init) => {
|
|
445
|
+
if (init?.headers && FORCE_FETCH_HEADER in init.headers) {
|
|
446
|
+
const headers = { ...init.headers };
|
|
447
|
+
delete headers[FORCE_FETCH_HEADER];
|
|
448
|
+
const response$1 = await fetch(url, {
|
|
449
|
+
...init,
|
|
450
|
+
headers
|
|
451
|
+
});
|
|
452
|
+
if (!response$1.ok) {
|
|
453
|
+
const body = await response$1.clone().json().catch(() => ({}));
|
|
454
|
+
const err = new Error(body.message || `HTTP ${response$1.status} ${response$1.statusText}`);
|
|
455
|
+
err.status = response$1.status;
|
|
456
|
+
err.statusText = response$1.statusText;
|
|
457
|
+
throw err;
|
|
458
|
+
}
|
|
459
|
+
return response$1;
|
|
460
|
+
}
|
|
461
|
+
const betterAuthMethod = deriveBetterAuthMethodFromUrl(url.toString());
|
|
462
|
+
if (betterAuthMethod) {
|
|
463
|
+
const response$1 = await BETTER_AUTH_METHODS_HOOKS[betterAuthMethod].beforeRequest?.(url, init);
|
|
464
|
+
if (response$1) return response$1;
|
|
465
|
+
}
|
|
466
|
+
const key = `${init?.method || "GET"}:${url}:${init?.body || ""}`;
|
|
467
|
+
const response = await BETTER_AUTH_METHODS_IN_FLIGHT_REQUESTS.deduplicate(key, () => fetch(url, init));
|
|
468
|
+
if (!response.ok) {
|
|
469
|
+
const errorBody = await response.clone().json().catch(() => ({}));
|
|
470
|
+
const err = new Error(errorBody.message || `HTTP ${response.status} ${response.statusText}`);
|
|
471
|
+
err.status = response.status;
|
|
472
|
+
err.statusText = response.statusText;
|
|
473
|
+
throw err;
|
|
474
|
+
}
|
|
475
|
+
return response.clone();
|
|
476
|
+
},
|
|
477
|
+
onSuccess: async (ctx) => {
|
|
478
|
+
const jwt = ctx.response.headers.get("set-auth-jwt");
|
|
479
|
+
if (jwt) if (ctx.data?.session) ctx.data.session.token = jwt;
|
|
480
|
+
else console.warn("[onSuccess] JWT found but no session data to inject into!");
|
|
481
|
+
const url = ctx.request.url.toString();
|
|
482
|
+
const responseData = ctx.data;
|
|
483
|
+
const method = deriveBetterAuthMethodFromUrl(url);
|
|
484
|
+
if (method) BETTER_AUTH_METHODS_HOOKS[method].onSuccess(responseData);
|
|
485
|
+
await userOnSuccess?.(ctx);
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
};
|
|
489
|
+
initBroadcastChannel();
|
|
490
|
+
}
|
|
491
|
+
};
|
|
492
|
+
|
|
493
|
+
//#endregion
|
|
494
|
+
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 };
|