@webtwizz/cloud 0.0.0-stage → 0.2.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/AI_GUIDE.md +118 -0
- package/CHANGELOG.md +11 -0
- package/LICENSE +21 -0
- package/README.md +54 -2
- package/dist/chunk-7EXXDRMF.js +376 -0
- package/dist/chunk-ESAV545E.js +12 -0
- package/dist/chunk-G54JBUO3.js +53 -0
- package/dist/client.d.ts +6 -0
- package/dist/client.js +793 -0
- package/dist/db.d.ts +9 -0
- package/dist/db.js +13 -0
- package/dist/files.d.ts +35 -0
- package/dist/files.js +92 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +10 -0
- package/dist/server.d.ts +13 -0
- package/dist/server.js +1485 -0
- package/package.json +88 -4
package/dist/client.js
ADDED
|
@@ -0,0 +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
|
+
|
|
787
|
+
// src/client.ts
|
|
788
|
+
function createAuthClient5() {
|
|
789
|
+
return createAuthClient4();
|
|
790
|
+
}
|
|
791
|
+
export {
|
|
792
|
+
createAuthClient5 as createAuthClient
|
|
793
|
+
};
|