@douvery/auth 0.2.1 → 0.3.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.
@@ -1,763 +1,42 @@
1
1
  import { createContextId, component$, useSignal, useContextProvider, useVisibleTask$, Slot, useContext, useTask$ } from '@builder.io/qwik';
2
+ import { createDouveryAuth } from '@douvery/auth';
3
+ export { DouveryAuthClient, createDouveryAuth } from '@douvery/auth';
2
4
  import { jsx } from '@builder.io/qwik/jsx-runtime';
3
5
 
4
6
  // src/qwik/index.tsx
5
-
6
- // src/types.ts
7
- var AuthError = class extends Error {
8
- constructor(code, message, cause) {
9
- super(message);
10
- this.code = code;
11
- this.cause = cause;
12
- this.name = "AuthError";
13
- }
14
- };
15
-
16
- // src/pkce.ts
17
- function generateCodeVerifier(length = 64) {
18
- const charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~";
19
- const randomValues = crypto.getRandomValues(new Uint8Array(length));
20
- return Array.from(randomValues).map((v) => charset[v % charset.length]).join("");
21
- }
22
- function generateState() {
23
- return generateCodeVerifier(32);
24
- }
25
- function generateNonce() {
26
- return generateCodeVerifier(32);
27
- }
28
- async function generateCodeChallenge(verifier) {
29
- const encoder = new TextEncoder();
30
- const data = encoder.encode(verifier);
31
- const hashBuffer = await crypto.subtle.digest("SHA-256", data);
32
- return base64UrlEncode(hashBuffer);
33
- }
34
- function base64UrlEncode(buffer) {
35
- const bytes = new Uint8Array(buffer);
36
- let binary = "";
37
- for (let i = 0; i < bytes.length; i++) {
38
- binary += String.fromCharCode(bytes[i]);
39
- }
40
- const base64 = btoa(binary);
41
- return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
42
- }
43
- function base64UrlDecode(input) {
44
- let base64 = input.replace(/-/g, "+").replace(/_/g, "/");
45
- const padding = base64.length % 4;
46
- if (padding) {
47
- base64 += "=".repeat(4 - padding);
48
- }
49
- const binary = atob(base64);
50
- const bytes = new Uint8Array(binary.length);
51
- for (let i = 0; i < binary.length; i++) {
52
- bytes[i] = binary.charCodeAt(i);
53
- }
54
- return bytes.buffer;
55
- }
56
- async function generatePKCEPair() {
57
- const codeVerifier = generateCodeVerifier();
58
- const codeChallenge = await generateCodeChallenge(codeVerifier);
59
- return {
60
- codeVerifier,
61
- codeChallenge,
62
- codeChallengeMethod: "S256"
63
- };
64
- }
65
- function decodeJWT(token) {
66
- const parts = token.split(".");
67
- if (parts.length !== 3) {
68
- throw new Error("Invalid JWT format");
69
- }
70
- const payload = parts[1];
71
- const decoded = base64UrlDecode(payload);
72
- const text = new TextDecoder().decode(decoded);
73
- return JSON.parse(text);
74
- }
75
- function isTokenExpired(token, clockSkew = 60) {
76
- try {
77
- const payload = decodeJWT(token);
78
- if (!payload.exp) {
79
- return false;
80
- }
81
- const now = Math.floor(Date.now() / 1e3);
82
- return payload.exp < now - clockSkew;
83
- } catch {
84
- return true;
85
- }
86
- }
87
-
88
- // src/storage.ts
89
- var DEFAULT_PREFIX = "douvery_auth";
90
- var STORAGE_KEYS = {
91
- accessToken: `${DEFAULT_PREFIX}_access_token`,
92
- refreshToken: `${DEFAULT_PREFIX}_refresh_token`,
93
- idToken: `${DEFAULT_PREFIX}_id_token`,
94
- expiresAt: `${DEFAULT_PREFIX}_expires_at`,
95
- state: `${DEFAULT_PREFIX}_state`,
96
- nonce: `${DEFAULT_PREFIX}_nonce`,
97
- codeVerifier: `${DEFAULT_PREFIX}_code_verifier`,
98
- returnTo: `${DEFAULT_PREFIX}_return_to`
99
- };
100
- var MemoryStorage = class {
101
- store = /* @__PURE__ */ new Map();
102
- get(key) {
103
- return this.store.get(key) ?? null;
104
- }
105
- set(key, value) {
106
- this.store.set(key, value);
107
- }
108
- remove(key) {
109
- this.store.delete(key);
110
- }
111
- clear() {
112
- this.store.clear();
113
- }
114
- };
115
- var LocalStorage = class {
116
- get(key) {
117
- if (typeof window === "undefined") return null;
118
- return localStorage.getItem(key);
119
- }
120
- set(key, value) {
121
- if (typeof window === "undefined") return;
122
- localStorage.setItem(key, value);
123
- }
124
- remove(key) {
125
- if (typeof window === "undefined") return;
126
- localStorage.removeItem(key);
127
- }
128
- clear() {
129
- if (typeof window === "undefined") return;
130
- Object.values(STORAGE_KEYS).forEach((key) => {
131
- localStorage.removeItem(key);
132
- });
133
- }
134
- };
135
- var SessionStorage = class {
136
- get(key) {
137
- if (typeof window === "undefined") return null;
138
- return sessionStorage.getItem(key);
139
- }
140
- set(key, value) {
141
- if (typeof window === "undefined") return;
142
- sessionStorage.setItem(key, value);
143
- }
144
- remove(key) {
145
- if (typeof window === "undefined") return;
146
- sessionStorage.removeItem(key);
147
- }
148
- clear() {
149
- if (typeof window === "undefined") return;
150
- Object.values(STORAGE_KEYS).forEach((key) => {
151
- sessionStorage.removeItem(key);
7
+ var DouveryAuthContext = createContextId("douvery-auth");
8
+ var DouveryAuthProvider = component$(
9
+ ({ config }) => {
10
+ const client = createDouveryAuth(config);
11
+ const state = useSignal(client.getState());
12
+ const isInitialized = useSignal(false);
13
+ const isLoading = useSignal(false);
14
+ const error = useSignal(null);
15
+ useContextProvider(DouveryAuthContext, {
16
+ state,
17
+ isInitialized,
18
+ isLoading,
19
+ error,
20
+ client
152
21
  });
153
- }
154
- };
155
- var CookieStorage = class {
156
- constructor(options = {}) {
157
- this.options = options;
158
- this.options = { path: "/", secure: true, sameSite: "Lax", ...options };
159
- }
160
- get(key) {
161
- if (typeof document === "undefined") return null;
162
- const cookies = document.cookie.split(";");
163
- for (const cookie of cookies) {
164
- const [name, value] = cookie.trim().split("=");
165
- if (name === key) {
166
- return decodeURIComponent(value);
167
- }
168
- }
169
- return null;
170
- }
171
- set(key, value) {
172
- if (typeof document === "undefined") return;
173
- const parts = [
174
- `${key}=${encodeURIComponent(value)}`,
175
- `path=${this.options.path}`
176
- ];
177
- if (this.options.domain) parts.push(`domain=${this.options.domain}`);
178
- if (this.options.secure) parts.push("secure");
179
- if (this.options.sameSite) parts.push(`samesite=${this.options.sameSite}`);
180
- if (this.options.maxAge) parts.push(`max-age=${this.options.maxAge}`);
181
- document.cookie = parts.join("; ");
182
- }
183
- remove(key) {
184
- if (typeof document === "undefined") return;
185
- document.cookie = `${key}=; path=${this.options.path}; expires=Thu, 01 Jan 1970 00:00:00 GMT`;
186
- }
187
- clear() {
188
- Object.values(STORAGE_KEYS).forEach((key) => this.remove(key));
189
- }
190
- };
191
- function createStorage(type) {
192
- switch (type) {
193
- case "localStorage":
194
- return new LocalStorage();
195
- case "sessionStorage":
196
- return new SessionStorage();
197
- case "cookie":
198
- return new CookieStorage();
199
- case "memory":
200
- default:
201
- return new MemoryStorage();
202
- }
203
- }
204
- var TokenManager = class {
205
- constructor(storage) {
206
- this.storage = storage;
207
- }
208
- async getTokens() {
209
- const accessToken = await this.storage.get(STORAGE_KEYS.accessToken);
210
- if (!accessToken) return null;
211
- const refreshToken = await this.storage.get(STORAGE_KEYS.refreshToken);
212
- const idToken = await this.storage.get(STORAGE_KEYS.idToken);
213
- const expiresAt = await this.storage.get(STORAGE_KEYS.expiresAt);
214
- return {
215
- accessToken,
216
- refreshToken: refreshToken ?? void 0,
217
- idToken: idToken ?? void 0,
218
- expiresAt: expiresAt ? parseInt(expiresAt, 10) : 0,
219
- tokenType: "Bearer",
220
- scope: []
221
- };
222
- }
223
- async setTokens(tokens) {
224
- await this.storage.set(STORAGE_KEYS.accessToken, tokens.accessToken);
225
- await this.storage.set(STORAGE_KEYS.expiresAt, tokens.expiresAt.toString());
226
- if (tokens.refreshToken) {
227
- await this.storage.set(STORAGE_KEYS.refreshToken, tokens.refreshToken);
228
- }
229
- if (tokens.idToken) {
230
- await this.storage.set(STORAGE_KEYS.idToken, tokens.idToken);
231
- }
232
- }
233
- async clearTokens() {
234
- await this.storage.remove(STORAGE_KEYS.accessToken);
235
- await this.storage.remove(STORAGE_KEYS.refreshToken);
236
- await this.storage.remove(STORAGE_KEYS.idToken);
237
- await this.storage.remove(STORAGE_KEYS.expiresAt);
238
- }
239
- async saveState(state) {
240
- await this.storage.set(STORAGE_KEYS.state, state);
241
- }
242
- async getState() {
243
- return this.storage.get(STORAGE_KEYS.state);
244
- }
245
- async clearState() {
246
- await this.storage.remove(STORAGE_KEYS.state);
247
- }
248
- async saveNonce(nonce) {
249
- await this.storage.set(STORAGE_KEYS.nonce, nonce);
250
- }
251
- async getNonce() {
252
- return this.storage.get(STORAGE_KEYS.nonce);
253
- }
254
- async clearNonce() {
255
- await this.storage.remove(STORAGE_KEYS.nonce);
256
- }
257
- async saveCodeVerifier(verifier) {
258
- await this.storage.set(STORAGE_KEYS.codeVerifier, verifier);
259
- }
260
- async getCodeVerifier() {
261
- return this.storage.get(STORAGE_KEYS.codeVerifier);
262
- }
263
- async clearCodeVerifier() {
264
- await this.storage.remove(STORAGE_KEYS.codeVerifier);
265
- }
266
- async saveReturnTo(url) {
267
- await this.storage.set(STORAGE_KEYS.returnTo, url);
268
- }
269
- async getReturnTo() {
270
- return this.storage.get(STORAGE_KEYS.returnTo);
271
- }
272
- async clearReturnTo() {
273
- await this.storage.remove(STORAGE_KEYS.returnTo);
274
- }
275
- async clearAll() {
276
- await this.storage.clear();
277
- }
278
- };
279
-
280
- // src/client.ts
281
- var DEFAULT_ISSUER = "https://auth.douvery.com";
282
- var DEFAULT_SCOPES = ["openid", "profile", "email"];
283
- var DouveryAuthClient = class {
284
- config;
285
- tokenManager;
286
- discovery = null;
287
- eventHandlers = /* @__PURE__ */ new Set();
288
- refreshTimer = null;
289
- state = {
290
- status: "loading",
291
- user: null,
292
- tokens: null,
293
- error: null
294
- };
295
- constructor(config) {
296
- this.config = {
297
- issuer: DEFAULT_ISSUER,
298
- scopes: DEFAULT_SCOPES,
299
- storage: "localStorage",
300
- autoRefresh: true,
301
- refreshThreshold: 60,
302
- debug: false,
303
- ...config
304
- };
305
- const storage = config.customStorage ?? createStorage(this.config.storage ?? "localStorage");
306
- this.tokenManager = new TokenManager(storage);
307
- }
308
- /** Initialize the auth client */
309
- async initialize() {
310
- this.log("Initializing auth client...");
311
- try {
312
- if (this.isCallback()) {
313
- this.log("Handling OAuth callback...");
314
- const result = await this.handleCallback();
315
- if (result.success && result.user && result.tokens) {
316
- this.updateState({
317
- status: "authenticated",
318
- user: result.user,
319
- tokens: result.tokens,
320
- error: null
321
- });
322
- this.setupAutoRefresh();
323
- } else {
324
- this.updateState({
325
- status: "unauthenticated",
326
- user: null,
327
- tokens: null,
328
- error: result.error ?? null
329
- });
330
- }
331
- } else {
332
- const tokens = await this.tokenManager.getTokens();
333
- if (tokens && tokens.accessToken) {
334
- if (!isTokenExpired(tokens.accessToken)) {
335
- this.log("Found valid existing session");
336
- const user = await this.fetchUser(tokens.accessToken);
337
- this.updateState({
338
- status: "authenticated",
339
- user,
340
- tokens,
341
- error: null
342
- });
343
- this.setupAutoRefresh();
344
- } else if (tokens.refreshToken) {
345
- this.log("Access token expired, attempting refresh...");
346
- await this.refreshTokens();
347
- } else {
348
- this.log("Session expired, no refresh token");
349
- await this.tokenManager.clearTokens();
350
- this.updateState({
351
- status: "unauthenticated",
352
- user: null,
353
- tokens: null,
354
- error: null
355
- });
356
- }
357
- } else {
358
- this.log("No existing session found");
359
- this.updateState({
360
- status: "unauthenticated",
361
- user: null,
362
- tokens: null,
363
- error: null
364
- });
365
- }
366
- }
367
- this.emit({ type: "INITIALIZED" });
368
- } catch (error) {
369
- this.log("Initialization error:", error);
370
- this.updateState({
371
- status: "unauthenticated",
372
- user: null,
373
- tokens: null,
374
- error: error instanceof AuthError ? error : new AuthError(
375
- "unknown_error",
376
- "Initialization failed",
377
- error
378
- )
379
- });
380
- }
381
- return this.state;
382
- }
383
- /** Start the login flow */
384
- async login(options = {}) {
385
- this.log("Starting login flow...");
386
- this.emit({ type: "LOGIN_STARTED" });
387
- try {
388
- const discovery = await this.getDiscovery();
389
- const pkce = await generatePKCEPair();
390
- const state = generateState();
391
- const nonce = generateNonce();
392
- await this.tokenManager.saveState(state);
393
- await this.tokenManager.saveNonce(nonce);
394
- await this.tokenManager.saveCodeVerifier(pkce.codeVerifier);
395
- if (options.returnTo) {
396
- await this.tokenManager.saveReturnTo(options.returnTo);
397
- }
398
- const params = new URLSearchParams({
399
- response_type: "code",
400
- client_id: this.config.clientId,
401
- redirect_uri: this.config.redirectUri,
402
- scope: this.config.scopes.join(" "),
403
- state,
404
- nonce,
405
- code_challenge: pkce.codeChallenge,
406
- code_challenge_method: pkce.codeChallengeMethod,
407
- ...options.authorizationParams
408
- });
409
- if (options.prompt) params.set("prompt", options.prompt);
410
- if (options.loginHint) params.set("login_hint", options.loginHint);
411
- if (options.uiLocales) params.set("ui_locales", options.uiLocales);
412
- if (options.maxAge !== void 0)
413
- params.set("max_age", options.maxAge.toString());
414
- if (options.acrValues) params.set("acr_values", options.acrValues);
415
- const authUrl = `${discovery.authorization_endpoint}?${params}`;
416
- this.log("Redirecting to:", authUrl);
417
- window.location.href = authUrl;
418
- } catch (error) {
419
- const authError = error instanceof AuthError ? error : new AuthError(
420
- "configuration_error",
421
- "Login failed",
422
- error
423
- );
424
- this.emit({ type: "LOGIN_ERROR", error: authError });
425
- throw authError;
426
- }
427
- }
428
- /** Logout the user */
429
- async logout(options = {}) {
430
- this.log("Starting logout...");
431
- this.emit({ type: "LOGOUT_STARTED" });
432
- try {
433
- await this.tokenManager.clearAll();
434
- this.clearAutoRefresh();
435
- this.updateState({
436
- status: "unauthenticated",
437
- user: null,
438
- tokens: null,
439
- error: null
22
+ useVisibleTask$(() => {
23
+ client.initialize().then(() => {
24
+ isInitialized.value = true;
25
+ state.value = client.getState();
26
+ }).catch((err) => {
27
+ error.value = err instanceof Error ? err : new Error(String(err));
440
28
  });
441
- if (options.localOnly) {
442
- this.emit({ type: "LOGOUT_SUCCESS" });
443
- return;
444
- }
445
- if (options.federated !== false) {
446
- const discovery = await this.getDiscovery();
447
- if (discovery.end_session_endpoint) {
448
- const params = new URLSearchParams();
449
- if (this.state.tokens?.idToken) {
450
- params.set("id_token_hint", this.state.tokens.idToken);
451
- }
452
- if (options.returnTo || this.config.postLogoutRedirectUri) {
453
- params.set(
454
- "post_logout_redirect_uri",
455
- options.returnTo || this.config.postLogoutRedirectUri
456
- );
457
- }
458
- params.set("client_id", this.config.clientId);
459
- const logoutUrl = `${discovery.end_session_endpoint}?${params}`;
460
- this.log("Redirecting to logout:", logoutUrl);
461
- window.location.href = logoutUrl;
462
- return;
29
+ const unsubscribe = client.subscribe((event) => {
30
+ state.value = client.getState();
31
+ if (event.type === "LOGIN_ERROR" || event.type === "LOGOUT_ERROR" || event.type === "TOKEN_REFRESH_ERROR") {
32
+ error.value = event.error;
463
33
  }
464
- }
465
- this.emit({ type: "LOGOUT_SUCCESS" });
466
- if (options.returnTo) {
467
- window.location.href = options.returnTo;
468
- }
469
- } catch (error) {
470
- const authError = error instanceof AuthError ? error : new AuthError("unknown_error", "Logout failed", error);
471
- this.emit({ type: "LOGOUT_ERROR", error: authError });
472
- throw authError;
473
- }
474
- }
475
- /** Check if current URL is an OAuth callback */
476
- isCallback() {
477
- if (typeof window === "undefined") return false;
478
- const params = new URLSearchParams(window.location.search);
479
- return params.has("code") || params.has("error");
480
- }
481
- /** Handle the OAuth callback */
482
- async handleCallback() {
483
- this.log("Processing callback...");
484
- if (typeof window === "undefined") {
485
- return {
486
- success: false,
487
- error: new AuthError(
488
- "configuration_error",
489
- "Cannot handle callback on server"
490
- )
491
- };
492
- }
493
- const params = new URLSearchParams(window.location.search);
494
- const code = params.get("code");
495
- const stateParam = params.get("state");
496
- const errorParam = params.get("error");
497
- const errorDescription = params.get("error_description");
498
- if (errorParam) {
499
- const error = new AuthError(
500
- errorParam,
501
- errorDescription ?? "Authorization failed"
502
- );
503
- return { success: false, error };
504
- }
505
- const savedState = await this.tokenManager.getState();
506
- if (!stateParam || stateParam !== savedState) {
507
- return {
508
- success: false,
509
- error: new AuthError("state_mismatch", "State parameter mismatch")
510
- };
511
- }
512
- if (!code) {
513
- return {
514
- success: false,
515
- error: new AuthError(
516
- "invalid_request",
517
- "No authorization code received"
518
- )
519
- };
520
- }
521
- const codeVerifier = await this.tokenManager.getCodeVerifier();
522
- if (!codeVerifier) {
523
- return {
524
- success: false,
525
- error: new AuthError("pkce_error", "No code verifier found")
526
- };
527
- }
528
- try {
529
- const tokens = await this.exchangeCode(code, codeVerifier);
530
- await this.tokenManager.setTokens(tokens);
531
- const user = await this.fetchUser(tokens.accessToken);
532
- const returnTo = await this.tokenManager.getReturnTo();
533
- await this.tokenManager.clearState();
534
- await this.tokenManager.clearNonce();
535
- await this.tokenManager.clearCodeVerifier();
536
- await this.tokenManager.clearReturnTo();
537
- window.history.replaceState({}, "", window.location.pathname);
538
- this.emit({ type: "LOGIN_SUCCESS", user, tokens });
539
- return { success: true, user, tokens, returnTo: returnTo ?? void 0 };
540
- } catch (error) {
541
- const authError = error instanceof AuthError ? error : new AuthError(
542
- "invalid_grant",
543
- "Token exchange failed",
544
- error
545
- );
546
- this.emit({ type: "LOGIN_ERROR", error: authError });
547
- return { success: false, error: authError };
548
- }
549
- }
550
- async exchangeCode(code, codeVerifier) {
551
- const discovery = await this.getDiscovery();
552
- const response = await fetch(discovery.token_endpoint, {
553
- method: "POST",
554
- headers: { "Content-Type": "application/x-www-form-urlencoded" },
555
- body: new URLSearchParams({
556
- grant_type: "authorization_code",
557
- code,
558
- redirect_uri: this.config.redirectUri,
559
- client_id: this.config.clientId,
560
- code_verifier: codeVerifier
561
- })
562
- });
563
- if (!response.ok) {
564
- const error = await response.json().catch(() => ({}));
565
- throw new AuthError(
566
- error.error ?? "invalid_grant",
567
- error.error_description ?? "Token exchange failed"
568
- );
569
- }
570
- const tokenSet = await response.json();
571
- return this.tokenSetToInfo(tokenSet);
572
- }
573
- /** Refresh the access token */
574
- async refreshTokens() {
575
- this.log("Refreshing tokens...");
576
- const tokens = await this.tokenManager.getTokens();
577
- if (!tokens?.refreshToken) {
578
- throw new AuthError("token_refresh_failed", "No refresh token available");
579
- }
580
- const discovery = await this.getDiscovery();
581
- const response = await fetch(discovery.token_endpoint, {
582
- method: "POST",
583
- headers: { "Content-Type": "application/x-www-form-urlencoded" },
584
- body: new URLSearchParams({
585
- grant_type: "refresh_token",
586
- refresh_token: tokens.refreshToken,
587
- client_id: this.config.clientId
588
- })
589
- });
590
- if (!response.ok) {
591
- const error = await response.json().catch(() => ({}));
592
- const authError = new AuthError(
593
- error.error ?? "token_refresh_failed",
594
- error.error_description ?? "Token refresh failed"
595
- );
596
- this.emit({ type: "TOKEN_REFRESH_ERROR", error: authError });
597
- await this.tokenManager.clearTokens();
598
- this.updateState({
599
- status: "unauthenticated",
600
- user: null,
601
- tokens: null,
602
- error: authError
603
34
  });
604
- this.emit({ type: "SESSION_EXPIRED" });
605
- throw authError;
606
- }
607
- const tokenSet = await response.json();
608
- const newTokens = this.tokenSetToInfo(tokenSet);
609
- await this.tokenManager.setTokens(newTokens);
610
- const user = newTokens.idToken ? this.extractUserFromIdToken(newTokens.idToken) : this.state.user;
611
- this.updateState({ ...this.state, tokens: newTokens, user });
612
- this.emit({ type: "TOKEN_REFRESHED", tokens: newTokens });
613
- this.setupAutoRefresh();
614
- return newTokens;
615
- }
616
- /** Get current access token (auto-refreshes if needed) */
617
- async getAccessToken() {
618
- const tokens = await this.tokenManager.getTokens();
619
- if (!tokens) return null;
620
- if (isTokenExpired(tokens.accessToken)) {
621
- if (tokens.refreshToken) {
622
- const newTokens = await this.refreshTokens();
623
- return newTokens.accessToken;
624
- }
625
- return null;
626
- }
627
- return tokens.accessToken;
628
- }
629
- tokenSetToInfo(tokenSet) {
630
- return {
631
- accessToken: tokenSet.access_token,
632
- refreshToken: tokenSet.refresh_token,
633
- idToken: tokenSet.id_token,
634
- expiresAt: Date.now() + tokenSet.expires_in * 1e3,
635
- tokenType: tokenSet.token_type,
636
- scope: tokenSet.scope?.split(" ") ?? []
637
- };
638
- }
639
- async fetchUser(accessToken) {
640
- const discovery = await this.getDiscovery();
641
- const response = await fetch(discovery.userinfo_endpoint, {
642
- headers: { Authorization: `Bearer ${accessToken}` }
643
- });
644
- if (!response.ok) {
645
- throw new AuthError("invalid_token", "Failed to fetch user info");
646
- }
647
- const userInfo = await response.json();
648
- return this.normalizeUser(userInfo);
649
- }
650
- extractUserFromIdToken(idToken) {
651
- const claims = decodeJWT(idToken);
652
- return this.normalizeUser(claims);
653
- }
654
- normalizeUser(claims) {
655
- return {
656
- id: claims.sub,
657
- email: claims.email,
658
- emailVerified: claims.email_verified,
659
- name: claims.name,
660
- firstName: claims.given_name,
661
- lastName: claims.family_name,
662
- picture: claims.picture,
663
- phoneNumber: claims.phone_number,
664
- phoneNumberVerified: claims.phone_number_verified,
665
- locale: claims.locale,
666
- ...claims
667
- };
668
- }
669
- async getDiscovery() {
670
- if (this.discovery) return this.discovery;
671
- const discoveryUrl = `${this.config.issuer}/.well-known/openid-configuration`;
672
- const response = await fetch(discoveryUrl);
673
- if (!response.ok) {
674
- throw new AuthError(
675
- "configuration_error",
676
- "Failed to fetch discovery document"
677
- );
678
- }
679
- this.discovery = await response.json();
680
- return this.discovery;
681
- }
682
- setupAutoRefresh() {
683
- if (!this.config.autoRefresh || !this.state.tokens) return;
684
- this.clearAutoRefresh();
685
- const expiresIn = this.state.tokens.expiresAt - Date.now();
686
- const refreshIn = expiresIn - this.config.refreshThreshold * 1e3;
687
- if (refreshIn > 0) {
688
- this.log(`Scheduling token refresh in ${Math.round(refreshIn / 1e3)}s`);
689
- this.refreshTimer = setTimeout(() => {
690
- this.refreshTokens().catch(
691
- (error) => this.log("Auto-refresh failed:", error)
692
- );
693
- }, refreshIn);
694
- }
695
- }
696
- clearAutoRefresh() {
697
- if (this.refreshTimer) {
698
- clearTimeout(this.refreshTimer);
699
- this.refreshTimer = null;
700
- }
701
- }
702
- getState() {
703
- return { ...this.state };
704
- }
705
- isAuthenticated() {
706
- return this.state.status === "authenticated";
707
- }
708
- getUser() {
709
- return this.state.user;
710
- }
711
- subscribe(handler) {
712
- this.eventHandlers.add(handler);
713
- return () => this.eventHandlers.delete(handler);
714
- }
715
- updateState(newState) {
716
- this.state = newState;
717
- }
718
- emit(event) {
719
- this.eventHandlers.forEach((handler) => {
720
- try {
721
- handler(event);
722
- } catch (error) {
723
- console.error("Event handler error:", error);
724
- }
35
+ return () => unsubscribe();
725
36
  });
37
+ return /* @__PURE__ */ jsx(Slot, {});
726
38
  }
727
- log(...args) {
728
- if (this.config.debug) {
729
- console.log("[DouveryAuth]", ...args);
730
- }
731
- }
732
- };
733
- function createDouveryAuth(config) {
734
- return new DouveryAuthClient(config);
735
- }
736
- var DouveryAuthContext = createContextId("douvery-auth");
737
- var DouveryAuthProvider = component$(({ config }) => {
738
- const client = createDouveryAuth(config);
739
- const state = useSignal(client.getState());
740
- const isInitialized = useSignal(false);
741
- const isLoading = useSignal(false);
742
- const error = useSignal(null);
743
- useContextProvider(DouveryAuthContext, { state, isInitialized, isLoading, error, client });
744
- useVisibleTask$(() => {
745
- client.initialize().then(() => {
746
- isInitialized.value = true;
747
- state.value = client.getState();
748
- }).catch((err) => {
749
- error.value = err instanceof Error ? err : new Error(String(err));
750
- });
751
- const unsubscribe = client.subscribe((event) => {
752
- state.value = client.getState();
753
- if (event.type === "LOGIN_ERROR" || event.type === "LOGOUT_ERROR" || event.type === "TOKEN_REFRESH_ERROR") {
754
- error.value = event.error;
755
- }
756
- });
757
- return () => unsubscribe();
758
- });
759
- return /* @__PURE__ */ jsx(Slot, {});
760
- });
39
+ );
761
40
  function useDouveryAuth() {
762
41
  return useContext(DouveryAuthContext);
763
42
  }
@@ -805,9 +84,86 @@ function useAuthActions() {
805
84
  isLoading.value = false;
806
85
  }
807
86
  };
808
- return { login, logout, isLoading };
87
+ const selectAccount = (options) => {
88
+ client.selectAccount(options);
89
+ };
90
+ const addAccount = (options) => {
91
+ client.addAccount(options);
92
+ };
93
+ const register = (options) => {
94
+ client.register(options);
95
+ };
96
+ const recoverAccount = (options) => {
97
+ client.recoverAccount(options);
98
+ };
99
+ const verifyAccount = (options) => {
100
+ client.verifyAccount(options);
101
+ };
102
+ const upgradeAccount = (options) => {
103
+ client.upgradeAccount(options);
104
+ };
105
+ const setupPasskey = (options) => {
106
+ client.setupPasskey(options);
107
+ };
108
+ const setupAddress = (options) => {
109
+ client.setupAddress(options);
110
+ };
111
+ const revokeToken = async (options) => {
112
+ isLoading.value = true;
113
+ error.value = null;
114
+ try {
115
+ await client.revokeToken(options);
116
+ } catch (err) {
117
+ error.value = err instanceof Error ? err : new Error(String(err));
118
+ throw err;
119
+ } finally {
120
+ isLoading.value = false;
121
+ }
122
+ };
123
+ return {
124
+ login,
125
+ logout,
126
+ selectAccount,
127
+ addAccount,
128
+ register,
129
+ recoverAccount,
130
+ verifyAccount,
131
+ upgradeAccount,
132
+ setupPasskey,
133
+ setupAddress,
134
+ revokeToken,
135
+ isLoading
136
+ };
137
+ }
138
+ function useAuthUrls() {
139
+ const { client } = useDouveryAuth();
140
+ return {
141
+ loginUrl: (options) => client.buildLoginUrl(options),
142
+ logoutUrl: (options) => client.buildLogoutUrl(options),
143
+ selectAccountUrl: (options) => client.buildSelectAccountUrl(options),
144
+ addAccountUrl: (options) => client.buildAddAccountUrl(options),
145
+ registerUrl: (options) => client.buildRegisterUrl(options),
146
+ recoverAccountUrl: (options) => client.buildRecoverAccountUrl(options),
147
+ verifyAccountUrl: (options) => client.buildVerifyAccountUrl(options),
148
+ upgradeAccountUrl: (options) => client.buildUpgradeAccountUrl(options),
149
+ setupPasskeyUrl: (options) => client.buildSetupPasskeyUrl(options),
150
+ setupAddressUrl: (options) => client.buildSetupAddressUrl(options)
151
+ };
152
+ }
153
+ function useSessionStatus() {
154
+ const { client, state } = useDouveryAuth();
155
+ const isExpired = useSignal(client.isSessionExpired());
156
+ const needsVerification = useSignal(client.needsEmailVerification());
157
+ const isGuest = useSignal(client.isGuestAccount());
158
+ useTask$(({ track }) => {
159
+ track(() => state.value);
160
+ isExpired.value = client.isSessionExpired();
161
+ needsVerification.value = client.needsEmailVerification();
162
+ isGuest.value = client.isGuestAccount();
163
+ });
164
+ return { isExpired, needsVerification, isGuest };
809
165
  }
810
166
 
811
- export { DouveryAuthClient, DouveryAuthContext, DouveryAuthProvider, createDouveryAuth, useAuthActions, useDouveryAuth, useIsAuthenticated, useUser };
167
+ export { DouveryAuthContext, DouveryAuthProvider, useAuthActions, useAuthUrls, useDouveryAuth, useIsAuthenticated, useSessionStatus, useUser };
812
168
  //# sourceMappingURL=index.js.map
813
169
  //# sourceMappingURL=index.js.map