@vaiftech/auth 1.0.4 → 1.1.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/README.md CHANGED
@@ -1,6 +1,10 @@
1
1
  # @vaiftech/auth
2
2
 
3
- Comprehensive authentication SDK for VAIF with session management, OAuth integration, MFA support, and React hooks.
3
+ [![npm version](https://img.shields.io/npm/v/@vaiftech/auth)](https://www.npmjs.com/package/@vaiftech/auth)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
5
+ [![TypeScript](https://img.shields.io/badge/TypeScript-5.0+-blue)](https://www.typescriptlang.org/)
6
+
7
+ Comprehensive authentication SDK for VAIF Studio - session management, OAuth, MFA, magic links, and React hooks.
4
8
 
5
9
  ## Installation
6
10
 
@@ -30,13 +34,13 @@ const { session, user } = await auth.signUp({
30
34
  });
31
35
 
32
36
  // Sign in
33
- const { session, user } = await auth.signInWithPassword({
37
+ const result = await auth.signInWithPassword({
34
38
  email: 'user@example.com',
35
39
  password: 'secure-password',
36
40
  });
37
41
 
38
- // Listen to auth changes
39
- auth.onAuthStateChange((event) => {
42
+ // Listen to auth state changes
43
+ const { unsubscribe } = auth.onAuthStateChange((event) => {
40
44
  console.log('Auth event:', event.event);
41
45
  console.log('User:', event.session?.user);
42
46
  });
@@ -47,15 +51,202 @@ await auth.signOut();
47
51
 
48
52
  ## Features
49
53
 
50
- - **Multiple Auth Methods**: Email/password, OAuth, Magic Link, OTP, Anonymous
51
- - **Session Management**: Automatic token refresh, session persistence
52
- - **MFA Support**: TOTP, SMS, Email verification
53
- - **OAuth Providers**: Google, GitHub, Microsoft, Apple, Discord, and more
54
- - **React Integration**: Context provider and hooks
55
- - **Storage Adapters**: localStorage, sessionStorage, cookies, or custom
56
- - **SSR Support**: Works with Next.js, Remix, and other frameworks
54
+ - **Email/Password** - Sign up, sign in, password reset
55
+ - **OAuth** - Google, GitHub, Microsoft, Apple, Discord, Slack, Twitter, Facebook, LinkedIn, Spotify, Twitch, Notion, Figma, Zoom
56
+ - **Magic Link** - Passwordless email authentication
57
+ - **OTP** - One-time password via SMS, WhatsApp, or email
58
+ - **Anonymous Auth** - Guest sessions with optional conversion
59
+ - **SSO** - SAML and OIDC enterprise single sign-on
60
+ - **MFA** - TOTP, SMS, and email second factors with backup codes
61
+ - **Session Management** - Auto-refresh, multi-tab sync, session listing/revocation
62
+ - **React Integration** - Provider, hooks, and SSR support
63
+ - **Storage Adapters** - localStorage, sessionStorage, cookies, memory, or custom
64
+ - **PKCE Flow** - Secure OAuth with code challenge
65
+
66
+ ## Authentication Methods
67
+
68
+ ### Email/Password
69
+
70
+ ```typescript
71
+ // Sign up
72
+ const { session, user } = await auth.signUp({
73
+ email: 'user@example.com',
74
+ password: 'secure-password',
75
+ name: 'Jane Doe',
76
+ metadata: { role: 'developer' },
77
+ });
78
+
79
+ // Sign in
80
+ const result = await auth.signInWithPassword({
81
+ email: 'user@example.com',
82
+ password: 'secure-password',
83
+ rememberMe: true,
84
+ });
85
+
86
+ // Handle MFA challenge if enabled
87
+ if ('mfaRequired' in result) {
88
+ const session = await auth.verifyMFAChallenge(result.mfaToken, '123456');
89
+ }
90
+ ```
91
+
92
+ ### OAuth
93
+
94
+ ```typescript
95
+ // Redirect to OAuth provider
96
+ await auth.signInWithOAuth({
97
+ provider: 'google',
98
+ redirectTo: 'https://myapp.com/auth/callback',
99
+ scopes: ['email', 'profile'],
100
+ });
101
+
102
+ // Link additional provider to existing account
103
+ await auth.linkIdentity('github', { redirectTo: 'https://myapp.com/settings' });
104
+
105
+ // Get linked identities
106
+ const identities = await auth.getUserIdentities();
107
+
108
+ // Unlink provider
109
+ await auth.unlinkIdentity('github');
110
+ ```
111
+
112
+ ### Magic Link
113
+
114
+ ```typescript
115
+ await auth.signInWithMagicLink({
116
+ email: 'user@example.com',
117
+ redirectTo: 'https://myapp.com/dashboard',
118
+ shouldCreateUser: true,
119
+ });
120
+ ```
121
+
122
+ ### OTP (One-Time Password)
57
123
 
58
- ## React Usage
124
+ ```typescript
125
+ // Send OTP
126
+ await auth.signInWithOTP({
127
+ phone: '+1234567890',
128
+ channel: 'sms', // 'sms' | 'whatsapp' | 'email'
129
+ });
130
+
131
+ // Verify OTP
132
+ const { session, user } = await auth.verifyOTP({
133
+ phone: '+1234567890',
134
+ token: '123456',
135
+ type: 'magiclink',
136
+ });
137
+ ```
138
+
139
+ ### Anonymous Auth
140
+
141
+ ```typescript
142
+ const { session, user } = await auth.signInAnonymously({
143
+ metadata: { source: 'landing-page' },
144
+ });
145
+ ```
146
+
147
+ ### SSO (Enterprise)
148
+
149
+ ```typescript
150
+ await auth.signInWithSSO({
151
+ domain: 'acme.com',
152
+ redirectTo: 'https://myapp.com/auth/callback',
153
+ });
154
+ ```
155
+
156
+ ## MFA (Multi-Factor Authentication)
157
+
158
+ ```typescript
159
+ // List enrolled factors
160
+ const factors = await auth.listMFAFactors();
161
+
162
+ // Enroll TOTP
163
+ const { qrCode, secret, backupCodes } = await auth.enrollMFA({
164
+ type: 'totp',
165
+ friendlyName: 'My Authenticator',
166
+ });
167
+
168
+ // Verify and activate factor
169
+ await auth.verifyMFA({ factorId: factor.id, code: '123456' });
170
+
171
+ // Regenerate backup codes
172
+ const { backupCodes } = await auth.regenerateBackupCodes();
173
+
174
+ // Unenroll factor
175
+ await auth.unenrollMFA(factorId, '123456');
176
+ ```
177
+
178
+ ## Session Management
179
+
180
+ ```typescript
181
+ // Get current session and user
182
+ const session = await auth.getSession();
183
+ const user = await auth.getUser();
184
+
185
+ // Manually set session (SSR)
186
+ await auth.setSession(accessToken, refreshToken);
187
+
188
+ // Refresh session
189
+ await auth.refreshSession();
190
+
191
+ // List all active sessions
192
+ const sessions = await auth.listSessions();
193
+
194
+ // Revoke a specific session
195
+ await auth.revokeSession(sessionId);
196
+
197
+ // Revoke all sessions except current
198
+ await auth.revokeOtherSessions();
199
+
200
+ // Sign out from all devices
201
+ await auth.signOutAll();
202
+ ```
203
+
204
+ ## Password Management
205
+
206
+ ```typescript
207
+ // Request password reset email
208
+ await auth.resetPasswordForEmail({
209
+ email: 'user@example.com',
210
+ redirectTo: 'https://myapp.com/reset-password',
211
+ });
212
+
213
+ // Update password (authenticated)
214
+ await auth.updatePassword({
215
+ currentPassword: 'old-password',
216
+ newPassword: 'new-password',
217
+ });
218
+
219
+ // Set password with recovery token
220
+ await auth.setPassword({
221
+ token: 'recovery-token-from-email',
222
+ password: 'new-password',
223
+ });
224
+ ```
225
+
226
+ ## User Management
227
+
228
+ ```typescript
229
+ // Update user profile
230
+ const user = await auth.updateUser({
231
+ name: 'Updated Name',
232
+ avatarUrl: 'https://example.com/avatar.jpg',
233
+ metadata: { theme: 'dark' },
234
+ });
235
+
236
+ // Resend email verification
237
+ await auth.resendEmailVerification({
238
+ redirectTo: 'https://myapp.com/verify',
239
+ });
240
+
241
+ // Verify email
242
+ await auth.verifyEmail(token);
243
+ ```
244
+
245
+ ## React Integration
246
+
247
+ The package includes a complete React integration via the `@vaiftech/auth/react` subpath.
248
+
249
+ ### AuthProvider
59
250
 
60
251
  ```tsx
61
252
  import { AuthProvider, useAuth } from '@vaiftech/auth/react';
@@ -68,11 +259,41 @@ function App() {
68
259
  );
69
260
  }
70
261
 
262
+ // Or pass a pre-configured client
263
+ import { createAuthClient } from '@vaiftech/auth';
264
+
265
+ const client = createAuthClient({ url: '...', apiKey: '...' });
266
+
267
+ function App() {
268
+ return (
269
+ <AuthProvider client={client}>
270
+ <YourApp />
271
+ </AuthProvider>
272
+ );
273
+ }
274
+ ```
275
+
276
+ ### useAuth
277
+
278
+ ```tsx
71
279
  function LoginPage() {
72
- const { user, signInWithPassword, signInWithOAuth, isLoading } = useAuth();
280
+ const {
281
+ user,
282
+ session,
283
+ isLoading,
284
+ isAuthenticated,
285
+ error,
286
+ signUp,
287
+ signInWithPassword,
288
+ signInWithOAuth,
289
+ signInWithMagicLink,
290
+ signOut,
291
+ refreshSession,
292
+ client, // for advanced usage
293
+ } = useAuth();
73
294
 
74
295
  if (isLoading) return <Loading />;
75
- if (user) return <Redirect to="/dashboard" />;
296
+ if (isAuthenticated) return <Redirect to="/dashboard" />;
76
297
 
77
298
  return (
78
299
  <div>
@@ -93,85 +314,186 @@ function LoginPage() {
93
314
  }
94
315
  ```
95
316
 
96
- ## OAuth
317
+ ### Convenience Hooks
97
318
 
98
- ```typescript
99
- // Sign in with OAuth provider
100
- await auth.signInWithOAuth({
101
- provider: 'google',
102
- redirectTo: 'https://myapp.com/auth/callback',
103
- scopes: ['email', 'profile'],
104
- });
319
+ ```tsx
320
+ import {
321
+ useUser,
322
+ useSession,
323
+ useIsAuthenticated,
324
+ useAuthClient,
325
+ usePassword,
326
+ useMFA,
327
+ useIdentities,
328
+ useSessions,
329
+ useSSO,
330
+ } from '@vaiftech/auth/react';
105
331
 
106
- // Handle OAuth callback (automatic if detectSessionInUrl is true)
107
- // Or manually:
108
- const session = await auth.handleOAuthCallback(code, state);
332
+ // Get current user
333
+ const user = useUser();
109
334
 
110
- // Link additional provider
111
- await auth.linkIdentity('github');
335
+ // Get current session
336
+ const session = useSession();
112
337
 
113
- // Unlink provider
114
- await auth.unlinkIdentity('github');
115
- ```
338
+ // Check auth status
339
+ const isAuthenticated = useIsAuthenticated();
116
340
 
117
- ## MFA
341
+ // Password management
342
+ const { resetPassword, updatePassword, isLoading } = usePassword();
118
343
 
119
- ```typescript
120
- // Enroll TOTP
121
- const { qrCode, secret, backupCodes } = await auth.enrollMFA({ type: 'totp' });
344
+ // MFA management
345
+ const { factors, enrollTOTP, verifyMFA, unenroll, isLoading } = useMFA();
122
346
 
123
- // Verify and enable MFA
124
- await auth.verifyMFA({ factorId, code: '123456' });
347
+ // OAuth identities
348
+ const { identities, linkIdentity, unlinkIdentity } = useIdentities();
125
349
 
126
- // During login with MFA
127
- const result = await auth.signInWithPassword({ email, password });
128
- if ('mfaRequired' in result) {
129
- const session = await auth.verifyMFAChallenge(result.mfaToken, '123456');
130
- }
350
+ // Session management
351
+ const { sessions, revokeSession, revokeOtherSessions } = useSessions();
352
+
353
+ // SSO
354
+ const { signInWithSSO, isLoading } = useSSO();
131
355
  ```
132
356
 
133
- ## Session Management
357
+ ## Auth State Events
134
358
 
135
359
  ```typescript
136
- // Get current session
137
- const session = await auth.getSession();
138
-
139
- // Get current user
140
- const user = await auth.getUser();
141
-
142
- // Refresh session manually
143
- await auth.refreshSession();
144
-
145
- // List all sessions
146
- const sessions = await auth.listSessions();
147
-
148
- // Revoke a session
149
- await auth.revokeSession(sessionId);
360
+ const { unsubscribe } = auth.onAuthStateChange((event) => {
361
+ switch (event.event) {
362
+ case 'SIGNED_IN':
363
+ console.log('User signed in:', event.session?.user.email);
364
+ break;
365
+ case 'SIGNED_OUT':
366
+ console.log('User signed out');
367
+ break;
368
+ case 'TOKEN_REFRESHED':
369
+ console.log('Token refreshed');
370
+ break;
371
+ case 'USER_UPDATED':
372
+ console.log('User updated:', event.session?.user);
373
+ break;
374
+ case 'PASSWORD_RECOVERY':
375
+ console.log('Password recovery flow completed');
376
+ break;
377
+ case 'MFA_CHALLENGE_VERIFIED':
378
+ console.log('MFA challenge verified');
379
+ break;
380
+ case 'INITIAL_SESSION':
381
+ console.log('Session restored from storage');
382
+ break;
383
+ }
384
+ });
150
385
 
151
- // Revoke all other sessions
152
- await auth.revokeOtherSessions();
386
+ // Unsubscribe when done
387
+ unsubscribe();
153
388
  ```
154
389
 
155
390
  ## Configuration
156
391
 
157
392
  ```typescript
393
+ import { createAuthClient, cookieStorage } from '@vaiftech/auth';
394
+
158
395
  const auth = createAuthClient({
159
396
  // Required
160
397
  url: 'https://api.vaif.studio',
161
398
 
162
- // Optional
163
- apiKey: 'your-project-key',
164
- headers: { 'x-custom-header': 'value' },
165
- storage: localStorage, // or sessionStorage, cookieStorage(), or custom
166
- storageKey: 'vaif.auth.',
167
- autoRefreshToken: true,
168
- persistSession: true,
169
- detectSessionInUrl: true,
170
- flowType: 'implicit', // or 'pkce'
171
- debug: false,
399
+ // Authentication
400
+ apiKey: 'your-project-key', // Publishable project key
401
+ headers: {}, // Custom request headers
402
+
403
+ // Session persistence
404
+ storage: cookieStorage({ // localStorage (default), sessionStorage, cookieStorage, or custom
405
+ secure: true,
406
+ sameSite: 'lax',
407
+ domain: '.myapp.com',
408
+ }),
409
+ storageKey: 'vaif.auth.', // Storage key prefix
410
+ persistSession: true, // Persist session to storage
411
+ autoRefreshToken: true, // Auto-refresh before expiry
412
+
413
+ // OAuth
414
+ detectSessionInUrl: true, // Auto-detect OAuth callbacks
415
+ flowType: 'pkce', // 'implicit' or 'pkce'
416
+
417
+ // Debug
418
+ debug: false, // Log auth events to console
172
419
  });
173
420
  ```
174
421
 
422
+ ## Error Handling
423
+
424
+ ```typescript
425
+ import { AuthError, SessionExpiredError, InvalidCredentialsError } from '@vaiftech/auth';
426
+
427
+ try {
428
+ await auth.signInWithPassword({ email, password });
429
+ } catch (error) {
430
+ if (error instanceof InvalidCredentialsError) {
431
+ showError('Invalid email or password');
432
+ } else if (error instanceof SessionExpiredError) {
433
+ redirectTo('/login');
434
+ } else if (AuthError.isAuthError(error)) {
435
+ switch (error.code) {
436
+ case 'user_already_exists':
437
+ showError('Account already exists');
438
+ break;
439
+ case 'weak_password':
440
+ showError('Password is too weak');
441
+ break;
442
+ case 'rate_limited':
443
+ showError('Too many attempts, please try later');
444
+ break;
445
+ case 'mfa_required':
446
+ showMFAPrompt();
447
+ break;
448
+ default:
449
+ showError(error.message);
450
+ }
451
+ }
452
+ }
453
+ ```
454
+
455
+ ### Error Codes
456
+
457
+ | Code | Description |
458
+ |------|-------------|
459
+ | `invalid_credentials` | Wrong email or password |
460
+ | `user_not_found` | No user with that email |
461
+ | `user_already_exists` | Email already registered |
462
+ | `email_not_verified` | Email verification required |
463
+ | `invalid_token` | Invalid or malformed token |
464
+ | `token_expired` | Access or refresh token expired |
465
+ | `session_expired` | Session no longer valid |
466
+ | `mfa_required` | MFA verification needed |
467
+ | `mfa_invalid` | Invalid MFA code |
468
+ | `rate_limited` | Too many requests |
469
+ | `weak_password` | Password doesn't meet requirements |
470
+ | `network_error` | Network connectivity issue |
471
+
472
+ ## Storage Adapters
473
+
474
+ ```typescript
475
+ import {
476
+ memoryStorage, // In-memory (default for Node.js)
477
+ localStorage, // Browser localStorage (default for browser)
478
+ sessionStorage, // Browser sessionStorage
479
+ cookieStorage, // Secure cookies
480
+ } from '@vaiftech/auth';
481
+
482
+ // Custom adapter
483
+ const customStorage = {
484
+ getItem: (key: string) => redis.get(key),
485
+ setItem: (key: string, value: string) => redis.set(key, value),
486
+ removeItem: (key: string) => redis.del(key),
487
+ };
488
+ ```
489
+
490
+ ## Related Packages
491
+
492
+ - [@vaiftech/client](https://www.npmjs.com/package/@vaiftech/client) - Full VAIF Studio SDK (includes auth)
493
+ - [@vaiftech/react](https://www.npmjs.com/package/@vaiftech/react) - React hooks for all VAIF services
494
+ - [@vaiftech/sdk-expo](https://www.npmjs.com/package/@vaiftech/sdk-expo) - React Native/Expo SDK
495
+ - [@vaiftech/cli](https://www.npmjs.com/package/@vaiftech/cli) - CLI tools
496
+
175
497
  ## License
176
498
 
177
- MIT License - VAIF Technologies
499
+ MIT
@@ -0,0 +1,2 @@
1
+ function l(r){return "mfaRequired"in r&&r.mfaRequired===true}var h=class r extends Error{constructor(e,t,s,i){super(e),this.name="AuthError",this.code=t,this.status=s,this.details=i;}static isAuthError(e){return e instanceof r}},m=class extends h{constructor(e="Session has expired"){super(e,"session_expired",401),this.name="SessionExpiredError";}},S=class extends h{constructor(e="Invalid email or password"){super(e,"invalid_credentials",401),this.name="InvalidCredentialsError";}};function n(){return typeof window<"u"&&typeof window.document<"u"}function y(){if(!n())return false;try{let r="__vaif_test__";return window.localStorage.setItem(r,r),window.localStorage.removeItem(r),!0}catch{return false}}var c=class{constructor(){this.storage=new Map;}getItem(e){return this.storage.get(e)??null}setItem(e,t){this.storage.set(e,t);}removeItem(e){this.storage.delete(e);}clear(){this.storage.clear();}},u=class{getItem(e){if(!n())return null;try{return window.localStorage.getItem(e)}catch{return null}}setItem(e,t){if(n())try{window.localStorage.setItem(e,t);}catch{}}removeItem(e){if(n())try{window.localStorage.removeItem(e);}catch{}}},p=class{getItem(e){if(!n())return null;try{return window.sessionStorage.getItem(e)}catch{return null}}setItem(e,t){if(n())try{window.sessionStorage.setItem(e,t);}catch{}}removeItem(e){if(n())try{window.sessionStorage.removeItem(e);}catch{}}},g=class{constructor(e){this.options={secure:n()&&window.location.protocol==="https:",sameSite:"lax",path:"/",...e};}getItem(e){if(!n())return null;try{let t=document.cookie.split(";");for(let s of t){let[i,o]=s.trim().split("=");if(i===e)return decodeURIComponent(o)}return null}catch{return null}}setItem(e,t){if(n())try{let s=`${e}=${encodeURIComponent(t)}`;s+=`; path=${this.options.path}`,s+=`; samesite=${this.options.sameSite}`,this.options.secure&&(s+="; secure"),this.options.domain&&(s+=`; domain=${this.options.domain}`),this.options.maxAge&&(s+=`; max-age=${this.options.maxAge}`),document.cookie=s;}catch{}}removeItem(e){if(n())try{document.cookie=`${e}=; path=${this.options.path}; expires=Thu, 01 Jan 1970 00:00:00 GMT`;}catch{}}};function _(){return y()?new u:new c}var d=class{constructor(e,t="vaif.auth."){this.adapter=e,this.keyPrefix=t;}key(e){return `${this.keyPrefix}${e}`}async getSession(){try{let e=await this.adapter.getItem(this.key("session"));if(!e)return null;let t=JSON.parse(e);return t.expiresAt&&t.expiresAt<Date.now()?(await this.removeSession(),null):t}catch{return null}}async setSession(e){try{await this.adapter.setItem(this.key("session"),JSON.stringify(e));}catch{}}async removeSession(){try{await this.adapter.removeItem(this.key("session"));}catch{}}async getRefreshToken(){try{return await this.adapter.getItem(this.key("refresh_token"))}catch{return null}}async setRefreshToken(e){try{await this.adapter.setItem(this.key("refresh_token"),e);}catch{}}async removeRefreshToken(){try{await this.adapter.removeItem(this.key("refresh_token"));}catch{}}async clear(){await this.removeSession(),await this.removeRefreshToken();}},A=new c,P=y()?new u:A,T=n()?new p:A,I=r=>new g(r);function R(){if(typeof crypto<"u"&&crypto.getRandomValues){let r=new Uint8Array(32);return crypto.getRandomValues(r),Array.from(r,e=>e.toString(16).padStart(2,"0")).join("")}return Array.from({length:64},()=>Math.floor(Math.random()*36).toString(36)).join("")}async function b(r){if(typeof crypto<"u"&&crypto.subtle){let t=new TextEncoder().encode(r),s=await crypto.subtle.digest("SHA-256",t);return btoa(String.fromCharCode(...new Uint8Array(s))).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")}return r}var v={storageKey:"vaif.auth.",autoRefreshToken:true,persistSession:true,detectSessionInUrl:true,flowType:"implicit",debug:false},O=60*1e3,f=class{constructor(e){this.refreshTimer=null;this.listeners=new Set;this.initialized=false;this.initPromise=null;this.currentSession=null;this.broadcastChannel=null;this.config={...v,...e,headers:e.headers||{},storage:e.storage||_()},this.storage=new d(this.config.storage,this.config.storageKey);}async initialize(){return this.initPromise?this.initPromise.then(()=>this.currentSession):(this.initPromise=this._initialize(),await this.initPromise,this.currentSession)}async _initialize(){if(!this.initialized){if(this.config.detectSessionInUrl&&n()){let e=await this._handleUrlSession();if(e){this.currentSession=e,await this._persistSession(e),this._notifyListeners("SIGNED_IN",e),this._setupAutoRefresh(e),this.initialized=true;return}}if(this.config.persistSession){let e=await this.storage.getSession();e&&(this.currentSession=e,this._notifyListeners("INITIAL_SESSION",e),this._setupAutoRefresh(e));}this.initialized=true,n()&&typeof BroadcastChannel<"u"&&(this.broadcastChannel=new BroadcastChannel(`${this.config.storageKey}sync`),this.broadcastChannel.onmessage=e=>{let{type:t,session:s}=e.data;t==="SESSION_UPDATE"&&(this.currentSession=s,this._notifyListeners(s?"TOKEN_REFRESHED":"SIGNED_OUT",s),s?this._setupAutoRefresh(s):this._clearRefreshTimer());});}}async getSession(){return await this.initialize(),this.currentSession}async getUser(){return (await this.getSession())?.user??null}async setSession(e,t){let s=await this._fetchUser(e),i={accessToken:e,refreshToken:t,expiresAt:Date.now()+3600*1e3,expiresIn:3600,tokenType:"bearer",user:s};return this.currentSession=i,await this._persistSession(i),this._notifyListeners("SIGNED_IN",i),this._setupAutoRefresh(i),{session:i,user:s}}async refreshSession(){let e=await this.getSession();if(!e?.refreshToken)return null;try{let t=await this._refreshToken(e.refreshToken),s={...e,accessToken:t.accessToken,refreshToken:t.refreshToken||e.refreshToken,expiresAt:t.expiresAt,expiresIn:t.expiresIn};return this.currentSession=s,await this._persistSession(s),this._notifyListeners("TOKEN_REFRESHED",s),this._setupAutoRefresh(s),s}catch(t){return this._log("Failed to refresh session:",t),await this.signOut(),null}}async signUp(e){let t=await this._fetch("/auth/signup",{method:"POST",body:JSON.stringify({email:e.email,password:e.password,name:e.name,phone:e.phone,metadata:e.metadata,redirectUrl:e.redirectUrl||e.emailRedirectTo})});return l(t)||(this.currentSession=t.session,await this._persistSession(t.session),this._notifyListeners("SIGNED_IN",t.session),this._setupAutoRefresh(t.session)),t}async signInWithPassword(e){let t=await this._fetch("/auth/login",{method:"POST",body:JSON.stringify({email:e.email,password:e.password,mfaCode:e.mfaCode,rememberMe:e.rememberMe})});return l(t)||(this.currentSession=t.session,await this._persistSession(t.session),this._notifyListeners("SIGNED_IN",t.session),this._setupAutoRefresh(t.session)),t}async signInWithOAuth(e){let t=await this._fetch("/auth/oauth/authorize",{method:"POST",body:JSON.stringify({provider:e.provider,redirectUrl:e.redirectTo,scopes:e.scopes,queryParams:e.queryParams,flowType:this.config.flowType})});return n()&&(window.location.href=t.url),t}async signInWithMagicLink(e){return this._fetch("/auth/magic-link/send",{method:"POST",body:JSON.stringify({email:e.email,redirectUrl:e.redirectTo,shouldCreateUser:e.shouldCreateUser})})}async signInWithOTP(e){let t=e.phone?"/auth/phone/send":"/auth/otp/send";return this._fetch(t,{method:"POST",body:JSON.stringify({email:e.email,phone:e.phone,channel:e.channel,shouldCreateUser:e.shouldCreateUser})})}async verifyOTP(e){let t=await this._fetch("/auth/otp/verify",{method:"POST",body:JSON.stringify({email:e.email,phone:e.phone,token:e.token,type:e.type})});return this.currentSession=t.session,await this._persistSession(t.session),this._notifyListeners("SIGNED_IN",t.session),this._setupAutoRefresh(t.session),t}async signInAnonymously(e){let t=await this._fetch("/auth/anonymous",{method:"POST",body:JSON.stringify({metadata:e?.metadata})});return this.currentSession=t.session,await this._persistSession(t.session),this._notifyListeners("SIGNED_IN",t.session),this._setupAutoRefresh(t.session),t}async signInWithSSO(e){let t=await this._fetch("/auth/sso/authorize",{method:"POST",body:JSON.stringify({domain:e.domain,providerId:e.providerId,redirectUrl:e.redirectTo})});return n()&&(window.location.href=t.url),t}async signOut(){try{this.currentSession&&await this._fetch("/auth/logout",{method:"POST"});}catch{}this._clearRefreshTimer(),this.currentSession=null,await this.storage.clear(),this._notifyListeners("SIGNED_OUT",null),this._broadcastSessionUpdate(null);}async signOutAll(){try{await this._fetch("/auth/logout-all",{method:"POST"});}catch{}this._clearRefreshTimer(),this.currentSession=null,await this.storage.clear(),this._notifyListeners("SIGNED_OUT",null);}async resetPasswordForEmail(e){return this._fetch("/auth/forgot-password",{method:"POST",body:JSON.stringify({email:e.email,redirectUrl:e.redirectTo})})}async updatePassword(e){return this._fetch("/users/me/change-password",{method:"POST",body:JSON.stringify({currentPassword:e.currentPassword,newPassword:e.newPassword})})}async setPassword(e){let t=await this._fetch("/auth/reset-password",{method:"POST",body:JSON.stringify({token:e.token,password:e.password})});return this.currentSession=t.session,await this._persistSession(t.session),this._notifyListeners("PASSWORD_RECOVERY",t.session),this._setupAutoRefresh(t.session),t}async updateUser(e){let t=await this._fetch("/users/me",{method:"PATCH",body:JSON.stringify(e)});return this.currentSession&&(this.currentSession={...this.currentSession,user:t.user},await this._persistSession(this.currentSession),this._notifyListeners("USER_UPDATED",this.currentSession)),t.user}async getUserIdentities(){return (await this._fetch("/auth/oauth/providers")).identities}async linkIdentity(e,t){let s=await this._fetch("/auth/oauth/link",{method:"POST",body:JSON.stringify({provider:e,redirectUrl:t?.redirectTo})});return n()&&(window.location.href=s.url),s}async unlinkIdentity(e){return this._fetch(`/auth/oauth/unlink/${e}`,{method:"POST"})}async listMFAFactors(){return (await this._fetch("/auth/mfa/factors")).factors}async enrollMFA(e){return this._fetch("/auth/mfa/setup",{method:"POST",body:JSON.stringify({method:e.type,friendlyName:e.friendlyName})})}async verifyMFA(e){return this._fetch("/auth/mfa/enable",{method:"POST",body:JSON.stringify({factorId:e.factorId,code:e.code})})}async challengeMFA(e){return this._fetch("/auth/mfa/challenge",{method:"POST",body:JSON.stringify({factorId:e.factorId})})}async verifyMFAChallenge(e,t){let s=await this._fetch("/auth/mfa/verify",{method:"POST",body:JSON.stringify({mfaToken:e,code:t})});return this.currentSession=s.session,await this._persistSession(s.session),this._notifyListeners("MFA_CHALLENGE_VERIFIED",s.session),this._setupAutoRefresh(s.session),s}async unenrollMFA(e,t){return this._fetch("/auth/mfa/disable",{method:"POST",body:JSON.stringify({factorId:e,code:t})})}async regenerateBackupCodes(){return this._fetch("/auth/mfa/backup-codes",{method:"POST"})}async listSessions(){return (await this._fetch("/auth/sessions")).sessions}async revokeSession(e){return this._fetch(`/auth/sessions/${e}`,{method:"DELETE"})}async revokeOtherSessions(){return this._fetch("/auth/sessions/revoke-others",{method:"POST"})}async resendEmailVerification(e){return this._fetch("/auth/verify-email/send",{method:"POST",body:JSON.stringify({redirectUrl:e?.redirectTo})})}async verifyEmail(e){let t=await this._fetch("/auth/verify-email/confirm",{method:"POST",body:JSON.stringify({token:e})});return this.currentSession&&(this.currentSession=t.session,await this._persistSession(t.session),this._notifyListeners("USER_UPDATED",t.session)),t}onAuthStateChange(e){return this.listeners.add(e),this.initialized&&e({event:this.currentSession?"INITIAL_SESSION":"SIGNED_OUT",session:this.currentSession}),{unsubscribe:()=>{this.listeners.delete(e);}}}async _fetch(e,t={}){let s=`${this.config.url}${e}`,i={"Content-Type":"application/json",...this.config.headers};this.config.apiKey&&(i["x-vaif-key"]=this.config.apiKey),this.currentSession?.accessToken&&(i.Authorization=`Bearer ${this.currentSession.accessToken}`);let o;try{o=await fetch(s,{...t,headers:i});}catch(a){throw new h(a instanceof Error?a.message:"Network request failed","network_error")}if(!o.ok){let a=await o.json().catch(()=>({}));throw new h(a.message||"Request failed",this._mapErrorCode(o.status,a.code),o.status,a)}return o.json()}async _fetchUser(e){let t=await fetch(`${this.config.url}/auth/me`,{headers:{Authorization:`Bearer ${e}`,...this.config.headers}});if(!t.ok)throw new h("Failed to fetch user","invalid_token",t.status);return (await t.json()).user}async _refreshToken(e){let t=await fetch(`${this.config.url}/auth/refresh`,{method:"POST",headers:{"Content-Type":"application/json",...this.config.headers},body:JSON.stringify({refreshToken:e})});if(!t.ok)throw new h("Failed to refresh token","token_expired",t.status);return t.json()}async _handleUrlSession(){if(!n())return null;let e=new URLSearchParams(window.location.hash.substring(1)),t=new URLSearchParams(window.location.search),s=e.get("access_token")||t.get("access_token"),i=e.get("refresh_token")||t.get("refresh_token"),o=e.get("expires_in")||t.get("expires_in");if(!s)return null;try{let a=await this._fetchUser(s),w={accessToken:s,refreshToken:i||void 0,expiresAt:Date.now()+(o?parseInt(o,10)*1e3:36e5),expiresIn:o?parseInt(o,10):3600,tokenType:"bearer",user:a};return window.history.replaceState(null,"",window.location.pathname),w}catch(a){return this._log("Failed to handle URL session:",a),null}}async _persistSession(e){this.config.persistSession&&(await this.storage.setSession(e),e.refreshToken&&await this.storage.setRefreshToken(e.refreshToken),this._broadcastSessionUpdate(e));}_setupAutoRefresh(e){if(!this.config.autoRefreshToken||!e.refreshToken)return;this._clearRefreshTimer();let t=e.expiresAt,s=Date.now(),i=Math.max(0,t-s-O);this._log(`Setting up auto refresh in ${Math.round(i/1e3)}s`),this.refreshTimer=setTimeout(()=>{this.refreshSession();},i);}_clearRefreshTimer(){this.refreshTimer&&(clearTimeout(this.refreshTimer),this.refreshTimer=null);}_notifyListeners(e,t){let s={event:e,session:t};this.listeners.forEach(i=>{try{i(s);}catch(o){this._log("Error in auth state change listener:",o);}});}_mapErrorCode(e,t){if(t){let s={invalid_credentials:"invalid_credentials",user_not_found:"user_not_found",user_already_exists:"user_already_exists",email_not_verified:"email_not_verified",phone_not_verified:"phone_not_verified",invalid_token:"invalid_token",token_expired:"token_expired",mfa_required:"mfa_required",mfa_invalid:"mfa_invalid",rate_limited:"rate_limited",weak_password:"weak_password",invalid_email:"invalid_email",invalid_phone:"invalid_phone"};if(s[t])return s[t]}switch(e){case 401:return "invalid_credentials";case 403:return "token_expired";case 404:return "user_not_found";case 409:return "user_already_exists";case 429:return "rate_limited";default:return "unknown_error"}}_broadcastSessionUpdate(e){try{this.broadcastChannel?.postMessage({type:"SESSION_UPDATE",session:e});}catch{}}destroy(){this._clearRefreshTimer(),this.broadcastChannel?.close(),this.broadcastChannel=null,this.listeners.clear();}_log(...e){this.config.debug&&console.log("[VaifAuth]",...e);}};function U(r){return new f(r)}
2
+ export{l as a,h as b,m as c,S as d,n as e,_ as f,d as g,A as h,P as i,T as j,I as k,R as l,b as m,f as n,U as o};
@@ -423,6 +423,7 @@ declare class VaifAuthClient {
423
423
  private initialized;
424
424
  private initPromise;
425
425
  private currentSession;
426
+ private broadcastChannel;
426
427
  constructor(config: AuthClientConfig);
427
428
  /**
428
429
  * Initialize the auth client
@@ -478,6 +479,10 @@ declare class VaifAuthClient {
478
479
  * Sign in anonymously
479
480
  */
480
481
  signInAnonymously(options?: SignInAnonymouslyOptions): Promise<AuthResponse>;
482
+ /**
483
+ * Sign in with SSO (SAML/OIDC)
484
+ */
485
+ signInWithSSO(options: SignInWithSSOOptions): Promise<OAuthResponse>;
481
486
  /**
482
487
  * Sign out the current user
483
488
  */
@@ -601,6 +606,11 @@ declare class VaifAuthClient {
601
606
  private _clearRefreshTimer;
602
607
  private _notifyListeners;
603
608
  private _mapErrorCode;
609
+ private _broadcastSessionUpdate;
610
+ /**
611
+ * Clean up resources (timers, broadcast channel)
612
+ */
613
+ destroy(): void;
604
614
  private _log;
605
615
  }
606
616
  /**
@@ -423,6 +423,7 @@ declare class VaifAuthClient {
423
423
  private initialized;
424
424
  private initPromise;
425
425
  private currentSession;
426
+ private broadcastChannel;
426
427
  constructor(config: AuthClientConfig);
427
428
  /**
428
429
  * Initialize the auth client
@@ -478,6 +479,10 @@ declare class VaifAuthClient {
478
479
  * Sign in anonymously
479
480
  */
480
481
  signInAnonymously(options?: SignInAnonymouslyOptions): Promise<AuthResponse>;
482
+ /**
483
+ * Sign in with SSO (SAML/OIDC)
484
+ */
485
+ signInWithSSO(options: SignInWithSSOOptions): Promise<OAuthResponse>;
481
486
  /**
482
487
  * Sign out the current user
483
488
  */
@@ -601,6 +606,11 @@ declare class VaifAuthClient {
601
606
  private _clearRefreshTimer;
602
607
  private _notifyListeners;
603
608
  private _mapErrorCode;
609
+ private _broadcastSessionUpdate;
610
+ /**
611
+ * Clean up resources (timers, broadcast channel)
612
+ */
613
+ destroy(): void;
604
614
  private _log;
605
615
  }
606
616
  /**
package/dist/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
- import { S as StorageAdapter, A as AsyncStorageAdapter, a as Session } from './client-28ISGmu6.mjs';
2
- export { z as AuthChangeEvent, E as AuthClientConfig, G as AuthError, F as AuthErrorCode, y as AuthEventType, e as AuthResponse, B as AuthStateChangeCallback, D as AuthSubscription, C as ConfirmEmailChangeOptions, I as InvalidCredentialsError, M as MFAChallenge, w as MFAChallengeOptions, x as MFAChallengeResponse, t as MFAFactor, s as MFAMethod, u as MFASetupResponse, v as MFAVerifyOptions, r as OAuthIdentity, O as OAuthProviderType, q as OAuthResponse, R as ResetPasswordOptions, H as SessionExpiredError, d as SessionInfo, o as SetPasswordOptions, m as SignInAnonymouslyOptions, i as SignInWithMagicLinkOptions, h as SignInWithOAuthOptions, j as SignInWithOTPOptions, g as SignInWithPasswordOptions, l as SignInWithSSOOptions, f as SignUpOptions, T as TokenRefreshResponse, n as UpdatePasswordOptions, p as UpdateUserOptions, U as User, b as UserInfo, V as VaifAuthClient, k as VerifyOTPOptions, c as createAuthClient, J as isMFAChallenge } from './client-28ISGmu6.mjs';
1
+ import { S as StorageAdapter, A as AsyncStorageAdapter, a as Session } from './client-DYj4LiUx.mjs';
2
+ export { z as AuthChangeEvent, E as AuthClientConfig, G as AuthError, F as AuthErrorCode, y as AuthEventType, e as AuthResponse, B as AuthStateChangeCallback, D as AuthSubscription, C as ConfirmEmailChangeOptions, I as InvalidCredentialsError, M as MFAChallenge, w as MFAChallengeOptions, x as MFAChallengeResponse, t as MFAFactor, s as MFAMethod, u as MFASetupResponse, v as MFAVerifyOptions, r as OAuthIdentity, O as OAuthProviderType, q as OAuthResponse, R as ResetPasswordOptions, H as SessionExpiredError, d as SessionInfo, o as SetPasswordOptions, m as SignInAnonymouslyOptions, i as SignInWithMagicLinkOptions, h as SignInWithOAuthOptions, j as SignInWithOTPOptions, g as SignInWithPasswordOptions, l as SignInWithSSOOptions, f as SignUpOptions, T as TokenRefreshResponse, n as UpdatePasswordOptions, p as UpdateUserOptions, U as User, b as UserInfo, V as VaifAuthClient, k as VerifyOTPOptions, c as createAuthClient, J as isMFAChallenge } from './client-DYj4LiUx.mjs';
3
3
 
4
4
  /**
5
5
  * @vaiftech/auth - Storage utilities
@@ -79,5 +79,13 @@ declare const memoryStorage: MemoryStorage;
79
79
  declare const localStorage: MemoryStorage | LocalStorageAdapter;
80
80
  declare const sessionStorage: MemoryStorage | SessionStorageAdapter;
81
81
  declare const cookieStorage: (options?: CookieStorageOptions) => CookieStorageAdapter;
82
+ /**
83
+ * Generate a random PKCE code verifier
84
+ */
85
+ declare function generateCodeVerifier(): string;
86
+ /**
87
+ * Generate a PKCE code challenge from a verifier
88
+ */
89
+ declare function generateCodeChallenge(verifier: string): Promise<string>;
82
90
 
83
- export { AsyncStorageAdapter, type CookieStorageOptions, Session, SessionStorage, StorageAdapter, cookieStorage, getDefaultStorage, isBrowser, localStorage, memoryStorage, sessionStorage };
91
+ export { AsyncStorageAdapter, type CookieStorageOptions, Session, SessionStorage, StorageAdapter, cookieStorage, generateCodeChallenge, generateCodeVerifier, getDefaultStorage, isBrowser, localStorage, memoryStorage, sessionStorage };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { S as StorageAdapter, A as AsyncStorageAdapter, a as Session } from './client-28ISGmu6.js';
2
- export { z as AuthChangeEvent, E as AuthClientConfig, G as AuthError, F as AuthErrorCode, y as AuthEventType, e as AuthResponse, B as AuthStateChangeCallback, D as AuthSubscription, C as ConfirmEmailChangeOptions, I as InvalidCredentialsError, M as MFAChallenge, w as MFAChallengeOptions, x as MFAChallengeResponse, t as MFAFactor, s as MFAMethod, u as MFASetupResponse, v as MFAVerifyOptions, r as OAuthIdentity, O as OAuthProviderType, q as OAuthResponse, R as ResetPasswordOptions, H as SessionExpiredError, d as SessionInfo, o as SetPasswordOptions, m as SignInAnonymouslyOptions, i as SignInWithMagicLinkOptions, h as SignInWithOAuthOptions, j as SignInWithOTPOptions, g as SignInWithPasswordOptions, l as SignInWithSSOOptions, f as SignUpOptions, T as TokenRefreshResponse, n as UpdatePasswordOptions, p as UpdateUserOptions, U as User, b as UserInfo, V as VaifAuthClient, k as VerifyOTPOptions, c as createAuthClient, J as isMFAChallenge } from './client-28ISGmu6.js';
1
+ import { S as StorageAdapter, A as AsyncStorageAdapter, a as Session } from './client-DYj4LiUx.js';
2
+ export { z as AuthChangeEvent, E as AuthClientConfig, G as AuthError, F as AuthErrorCode, y as AuthEventType, e as AuthResponse, B as AuthStateChangeCallback, D as AuthSubscription, C as ConfirmEmailChangeOptions, I as InvalidCredentialsError, M as MFAChallenge, w as MFAChallengeOptions, x as MFAChallengeResponse, t as MFAFactor, s as MFAMethod, u as MFASetupResponse, v as MFAVerifyOptions, r as OAuthIdentity, O as OAuthProviderType, q as OAuthResponse, R as ResetPasswordOptions, H as SessionExpiredError, d as SessionInfo, o as SetPasswordOptions, m as SignInAnonymouslyOptions, i as SignInWithMagicLinkOptions, h as SignInWithOAuthOptions, j as SignInWithOTPOptions, g as SignInWithPasswordOptions, l as SignInWithSSOOptions, f as SignUpOptions, T as TokenRefreshResponse, n as UpdatePasswordOptions, p as UpdateUserOptions, U as User, b as UserInfo, V as VaifAuthClient, k as VerifyOTPOptions, c as createAuthClient, J as isMFAChallenge } from './client-DYj4LiUx.js';
3
3
 
4
4
  /**
5
5
  * @vaiftech/auth - Storage utilities
@@ -79,5 +79,13 @@ declare const memoryStorage: MemoryStorage;
79
79
  declare const localStorage: MemoryStorage | LocalStorageAdapter;
80
80
  declare const sessionStorage: MemoryStorage | SessionStorageAdapter;
81
81
  declare const cookieStorage: (options?: CookieStorageOptions) => CookieStorageAdapter;
82
+ /**
83
+ * Generate a random PKCE code verifier
84
+ */
85
+ declare function generateCodeVerifier(): string;
86
+ /**
87
+ * Generate a PKCE code challenge from a verifier
88
+ */
89
+ declare function generateCodeChallenge(verifier: string): Promise<string>;
82
90
 
83
- export { AsyncStorageAdapter, type CookieStorageOptions, Session, SessionStorage, StorageAdapter, cookieStorage, getDefaultStorage, isBrowser, localStorage, memoryStorage, sessionStorage };
91
+ export { AsyncStorageAdapter, type CookieStorageOptions, Session, SessionStorage, StorageAdapter, cookieStorage, generateCodeChallenge, generateCodeVerifier, getDefaultStorage, isBrowser, localStorage, memoryStorage, sessionStorage };
package/dist/index.js CHANGED
@@ -1 +1,2 @@
1
- 'use strict';function c(i){return "mfaRequired"in i&&i.mfaRequired===true}var a=class i extends Error{constructor(e,t,s,r){super(e),this.name="AuthError",this.code=t,this.status=s,this.details=r;}static isAuthError(e){return e instanceof i}},g=class extends a{constructor(e="Session has expired"){super(e,"session_expired",401),this.name="SessionExpiredError";}},f=class extends a{constructor(e="Invalid email or password"){super(e,"invalid_credentials",401),this.name="InvalidCredentialsError";}};function n(){return typeof window<"u"&&typeof window.document<"u"}function _(){if(!n())return false;try{let i="__vaif_test__";return window.localStorage.setItem(i,i),window.localStorage.removeItem(i),!0}catch{return false}}var p=class{constructor(){this.storage=new Map;}getItem(e){return this.storage.get(e)??null}setItem(e,t){this.storage.set(e,t);}removeItem(e){this.storage.delete(e);}clear(){this.storage.clear();}},l=class{getItem(e){if(!n())return null;try{return window.localStorage.getItem(e)}catch{return null}}setItem(e,t){if(n())try{window.localStorage.setItem(e,t);}catch{}}removeItem(e){if(n())try{window.localStorage.removeItem(e);}catch{}}},m=class{getItem(e){if(!n())return null;try{return window.sessionStorage.getItem(e)}catch{return null}}setItem(e,t){if(n())try{window.sessionStorage.setItem(e,t);}catch{}}removeItem(e){if(n())try{window.sessionStorage.removeItem(e);}catch{}}},S=class{constructor(e){this.options={secure:n()&&window.location.protocol==="https:",sameSite:"lax",path:"/",...e};}getItem(e){if(!n())return null;try{let t=document.cookie.split(";");for(let s of t){let[r,o]=s.trim().split("=");if(r===e)return decodeURIComponent(o)}return null}catch{return null}}setItem(e,t){if(n())try{let s=`${e}=${encodeURIComponent(t)}`;s+=`; path=${this.options.path}`,s+=`; samesite=${this.options.sameSite}`,this.options.secure&&(s+="; secure"),this.options.domain&&(s+=`; domain=${this.options.domain}`),this.options.maxAge&&(s+=`; max-age=${this.options.maxAge}`),document.cookie=s;}catch{}}removeItem(e){if(n())try{document.cookie=`${e}=; path=${this.options.path}; expires=Thu, 01 Jan 1970 00:00:00 GMT`;}catch{}}};function y(){return _()?new l:new p}var u=class{constructor(e,t="vaif.auth."){this.adapter=e,this.keyPrefix=t;}key(e){return `${this.keyPrefix}${e}`}async getSession(){try{let e=await this.adapter.getItem(this.key("session"));if(!e)return null;let t=JSON.parse(e);return t.expiresAt&&t.expiresAt<Date.now()?(await this.removeSession(),null):t}catch{return null}}async setSession(e){try{await this.adapter.setItem(this.key("session"),JSON.stringify(e));}catch{}}async removeSession(){try{await this.adapter.removeItem(this.key("session"));}catch{}}async getRefreshToken(){try{return await this.adapter.getItem(this.key("refresh_token"))}catch{return null}}async setRefreshToken(e){try{await this.adapter.setItem(this.key("refresh_token"),e);}catch{}}async removeRefreshToken(){try{await this.adapter.removeItem(this.key("refresh_token"));}catch{}}async clear(){await this.removeSession(),await this.removeRefreshToken();}},A=new p,O=_()?new l:A,k=n()?new m:A,v=i=>new S(i);var P={storageKey:"vaif.auth.",autoRefreshToken:true,persistSession:true,detectSessionInUrl:true,flowType:"implicit",debug:false},I=60*1e3,d=class{constructor(e){this.refreshTimer=null;this.listeners=new Set;this.initialized=false;this.initPromise=null;this.currentSession=null;this.config={...P,...e,headers:e.headers||{},storage:e.storage||y()},this.storage=new u(this.config.storage,this.config.storageKey);}async initialize(){return this.initPromise?this.initPromise.then(()=>this.currentSession):(this.initPromise=this._initialize(),await this.initPromise,this.currentSession)}async _initialize(){if(!this.initialized){if(this.config.detectSessionInUrl&&n()){let e=await this._handleUrlSession();if(e){this.currentSession=e,await this._persistSession(e),this._notifyListeners("SIGNED_IN",e),this._setupAutoRefresh(e),this.initialized=true;return}}if(this.config.persistSession){let e=await this.storage.getSession();e&&(this.currentSession=e,this._notifyListeners("INITIAL_SESSION",e),this._setupAutoRefresh(e));}this.initialized=true;}}async getSession(){return await this.initialize(),this.currentSession}async getUser(){return (await this.getSession())?.user??null}async setSession(e,t){let s=await this._fetchUser(e),r={accessToken:e,refreshToken:t,expiresAt:Date.now()+3600*1e3,expiresIn:3600,tokenType:"bearer",user:s};return this.currentSession=r,await this._persistSession(r),this._notifyListeners("SIGNED_IN",r),this._setupAutoRefresh(r),{session:r,user:s}}async refreshSession(){let e=await this.getSession();if(!e?.refreshToken)return null;try{let t=await this._refreshToken(e.refreshToken),s={...e,accessToken:t.accessToken,refreshToken:t.refreshToken||e.refreshToken,expiresAt:t.expiresAt,expiresIn:t.expiresIn};return this.currentSession=s,await this._persistSession(s),this._notifyListeners("TOKEN_REFRESHED",s),this._setupAutoRefresh(s),s}catch(t){return this._log("Failed to refresh session:",t),await this.signOut(),null}}async signUp(e){let t=await this._fetch("/auth/signup",{method:"POST",body:JSON.stringify({email:e.email,password:e.password,name:e.name,phone:e.phone,metadata:e.metadata,redirectUrl:e.redirectUrl||e.emailRedirectTo})});return c(t)||(this.currentSession=t.session,await this._persistSession(t.session),this._notifyListeners("SIGNED_IN",t.session),this._setupAutoRefresh(t.session)),t}async signInWithPassword(e){let t=await this._fetch("/auth/login",{method:"POST",body:JSON.stringify({email:e.email,password:e.password,mfaCode:e.mfaCode,rememberMe:e.rememberMe})});return c(t)||(this.currentSession=t.session,await this._persistSession(t.session),this._notifyListeners("SIGNED_IN",t.session),this._setupAutoRefresh(t.session)),t}async signInWithOAuth(e){let t=await this._fetch("/auth/oauth/authorize",{method:"POST",body:JSON.stringify({provider:e.provider,redirectUrl:e.redirectTo,scopes:e.scopes,queryParams:e.queryParams,flowType:this.config.flowType})});return n()&&(window.location.href=t.url),t}async signInWithMagicLink(e){return this._fetch("/auth/magic-link/send",{method:"POST",body:JSON.stringify({email:e.email,redirectUrl:e.redirectTo,shouldCreateUser:e.shouldCreateUser})})}async signInWithOTP(e){let t=e.phone?"/auth/phone/send":"/auth/otp/send";return this._fetch(t,{method:"POST",body:JSON.stringify({email:e.email,phone:e.phone,channel:e.channel,shouldCreateUser:e.shouldCreateUser})})}async verifyOTP(e){let t=await this._fetch("/auth/otp/verify",{method:"POST",body:JSON.stringify({email:e.email,phone:e.phone,token:e.token,type:e.type})});return this.currentSession=t.session,await this._persistSession(t.session),this._notifyListeners("SIGNED_IN",t.session),this._setupAutoRefresh(t.session),t}async signInAnonymously(e){let t=await this._fetch("/auth/anonymous",{method:"POST",body:JSON.stringify({metadata:e?.metadata})});return this.currentSession=t.session,await this._persistSession(t.session),this._notifyListeners("SIGNED_IN",t.session),this._setupAutoRefresh(t.session),t}async signOut(){try{this.currentSession&&await this._fetch("/auth/logout",{method:"POST"});}catch{}this._clearRefreshTimer(),this.currentSession=null,await this.storage.clear(),this._notifyListeners("SIGNED_OUT",null);}async signOutAll(){try{await this._fetch("/auth/logout-all",{method:"POST"});}catch{}this._clearRefreshTimer(),this.currentSession=null,await this.storage.clear(),this._notifyListeners("SIGNED_OUT",null);}async resetPasswordForEmail(e){return this._fetch("/auth/forgot-password",{method:"POST",body:JSON.stringify({email:e.email,redirectUrl:e.redirectTo})})}async updatePassword(e){return this._fetch("/users/me/change-password",{method:"POST",body:JSON.stringify({currentPassword:e.currentPassword,newPassword:e.newPassword})})}async setPassword(e){let t=await this._fetch("/auth/reset-password",{method:"POST",body:JSON.stringify({token:e.token,password:e.password})});return this.currentSession=t.session,await this._persistSession(t.session),this._notifyListeners("PASSWORD_RECOVERY",t.session),this._setupAutoRefresh(t.session),t}async updateUser(e){let t=await this._fetch("/users/me",{method:"PATCH",body:JSON.stringify(e)});return this.currentSession&&(this.currentSession={...this.currentSession,user:t.user},await this._persistSession(this.currentSession),this._notifyListeners("USER_UPDATED",this.currentSession)),t.user}async getUserIdentities(){return (await this._fetch("/auth/oauth/providers")).identities}async linkIdentity(e,t){let s=await this._fetch("/auth/oauth/link",{method:"POST",body:JSON.stringify({provider:e,redirectUrl:t?.redirectTo})});return n()&&(window.location.href=s.url),s}async unlinkIdentity(e){return this._fetch(`/auth/oauth/unlink/${e}`,{method:"POST"})}async listMFAFactors(){return (await this._fetch("/auth/mfa/factors")).factors}async enrollMFA(e){return this._fetch("/auth/mfa/setup",{method:"POST",body:JSON.stringify({method:e.type,friendlyName:e.friendlyName})})}async verifyMFA(e){return this._fetch("/auth/mfa/enable",{method:"POST",body:JSON.stringify({factorId:e.factorId,code:e.code})})}async challengeMFA(e){return this._fetch("/auth/mfa/challenge",{method:"POST",body:JSON.stringify({factorId:e.factorId})})}async verifyMFAChallenge(e,t){let s=await this._fetch("/auth/mfa/verify",{method:"POST",body:JSON.stringify({mfaToken:e,code:t})});return this.currentSession=s.session,await this._persistSession(s.session),this._notifyListeners("MFA_CHALLENGE_VERIFIED",s.session),this._setupAutoRefresh(s.session),s}async unenrollMFA(e,t){return this._fetch("/auth/mfa/disable",{method:"POST",body:JSON.stringify({factorId:e,code:t})})}async regenerateBackupCodes(){return this._fetch("/auth/mfa/backup-codes",{method:"POST"})}async listSessions(){return (await this._fetch("/auth/sessions")).sessions}async revokeSession(e){return this._fetch(`/auth/sessions/${e}`,{method:"DELETE"})}async revokeOtherSessions(){return this._fetch("/auth/sessions/revoke-others",{method:"POST"})}async resendEmailVerification(e){return this._fetch("/auth/verify-email/send",{method:"POST",body:JSON.stringify({redirectUrl:e?.redirectTo})})}async verifyEmail(e){let t=await this._fetch("/auth/verify-email/confirm",{method:"POST",body:JSON.stringify({token:e})});return this.currentSession&&(this.currentSession=t.session,await this._persistSession(t.session),this._notifyListeners("USER_UPDATED",t.session)),t}onAuthStateChange(e){return this.listeners.add(e),this.initialized&&e({event:this.currentSession?"INITIAL_SESSION":"SIGNED_OUT",session:this.currentSession}),{unsubscribe:()=>{this.listeners.delete(e);}}}async _fetch(e,t={}){let s=`${this.config.url}${e}`,r={"Content-Type":"application/json",...this.config.headers};this.config.apiKey&&(r["x-vaif-key"]=this.config.apiKey),this.currentSession?.accessToken&&(r.Authorization=`Bearer ${this.currentSession.accessToken}`);let o=await fetch(s,{...t,headers:r});if(!o.ok){let h=await o.json().catch(()=>({}));throw new a(h.message||"Request failed",this._mapErrorCode(o.status,h.code),o.status,h)}return o.json()}async _fetchUser(e){let t=await fetch(`${this.config.url}/auth/me`,{headers:{Authorization:`Bearer ${e}`,...this.config.headers}});if(!t.ok)throw new a("Failed to fetch user","invalid_token",t.status);return (await t.json()).user}async _refreshToken(e){let t=await fetch(`${this.config.url}/auth/refresh`,{method:"POST",headers:{"Content-Type":"application/json",...this.config.headers},body:JSON.stringify({refreshToken:e})});if(!t.ok)throw new a("Failed to refresh token","token_expired",t.status);return t.json()}async _handleUrlSession(){if(!n())return null;let e=new URLSearchParams(window.location.hash.substring(1)),t=new URLSearchParams(window.location.search),s=e.get("access_token")||t.get("access_token"),r=e.get("refresh_token")||t.get("refresh_token"),o=e.get("expires_in")||t.get("expires_in");if(!s)return null;try{let h=await this._fetchUser(s),w={accessToken:s,refreshToken:r||void 0,expiresAt:Date.now()+(o?parseInt(o,10)*1e3:36e5),expiresIn:o?parseInt(o,10):3600,tokenType:"bearer",user:h};return window.history.replaceState(null,"",window.location.pathname),w}catch(h){return this._log("Failed to handle URL session:",h),null}}async _persistSession(e){this.config.persistSession&&(await this.storage.setSession(e),e.refreshToken&&await this.storage.setRefreshToken(e.refreshToken));}_setupAutoRefresh(e){if(!this.config.autoRefreshToken||!e.refreshToken)return;this._clearRefreshTimer();let t=e.expiresAt,s=Date.now(),r=Math.max(0,t-s-I);this._log(`Setting up auto refresh in ${Math.round(r/1e3)}s`),this.refreshTimer=setTimeout(()=>{this.refreshSession();},r);}_clearRefreshTimer(){this.refreshTimer&&(clearTimeout(this.refreshTimer),this.refreshTimer=null);}_notifyListeners(e,t){let s={event:e,session:t};this.listeners.forEach(r=>{try{r(s);}catch(o){this._log("Error in auth state change listener:",o);}});}_mapErrorCode(e,t){if(t){let s={invalid_credentials:"invalid_credentials",user_not_found:"user_not_found",user_already_exists:"user_already_exists",email_not_verified:"email_not_verified",phone_not_verified:"phone_not_verified",invalid_token:"invalid_token",token_expired:"token_expired",mfa_required:"mfa_required",mfa_invalid:"mfa_invalid",rate_limited:"rate_limited",weak_password:"weak_password",invalid_email:"invalid_email",invalid_phone:"invalid_phone"};if(s[t])return s[t]}switch(e){case 401:return "invalid_credentials";case 403:return "token_expired";case 404:return "user_not_found";case 409:return "user_already_exists";case 429:return "rate_limited";default:return "unknown_error"}}_log(...e){this.config.debug&&console.log("[VaifAuth]",...e);}};function T(i){return new d(i)}exports.AuthError=a;exports.InvalidCredentialsError=f;exports.SessionExpiredError=g;exports.SessionStorage=u;exports.VaifAuthClient=d;exports.cookieStorage=v;exports.createAuthClient=T;exports.getDefaultStorage=y;exports.isBrowser=n;exports.isMFAChallenge=c;exports.localStorage=O;exports.memoryStorage=A;exports.sessionStorage=k;
1
+ 'use strict';function u(r){return "mfaRequired"in r&&r.mfaRequired===true}var h=class r extends Error{constructor(e,t,s,i){super(e),this.name="AuthError",this.code=t,this.status=s,this.details=i;}static isAuthError(e){return e instanceof r}},g=class extends h{constructor(e="Session has expired"){super(e,"session_expired",401),this.name="SessionExpiredError";}},f=class extends h{constructor(e="Invalid email or password"){super(e,"invalid_credentials",401),this.name="InvalidCredentialsError";}};function n(){return typeof window<"u"&&typeof window.document<"u"}function _(){if(!n())return false;try{let r="__vaif_test__";return window.localStorage.setItem(r,r),window.localStorage.removeItem(r),!0}catch{return false}}var d=class{constructor(){this.storage=new Map;}getItem(e){return this.storage.get(e)??null}setItem(e,t){this.storage.set(e,t);}removeItem(e){this.storage.delete(e);}clear(){this.storage.clear();}},l=class{getItem(e){if(!n())return null;try{return window.localStorage.getItem(e)}catch{return null}}setItem(e,t){if(n())try{window.localStorage.setItem(e,t);}catch{}}removeItem(e){if(n())try{window.localStorage.removeItem(e);}catch{}}},m=class{getItem(e){if(!n())return null;try{return window.sessionStorage.getItem(e)}catch{return null}}setItem(e,t){if(n())try{window.sessionStorage.setItem(e,t);}catch{}}removeItem(e){if(n())try{window.sessionStorage.removeItem(e);}catch{}}},S=class{constructor(e){this.options={secure:n()&&window.location.protocol==="https:",sameSite:"lax",path:"/",...e};}getItem(e){if(!n())return null;try{let t=document.cookie.split(";");for(let s of t){let[i,o]=s.trim().split("=");if(i===e)return decodeURIComponent(o)}return null}catch{return null}}setItem(e,t){if(n())try{let s=`${e}=${encodeURIComponent(t)}`;s+=`; path=${this.options.path}`,s+=`; samesite=${this.options.sameSite}`,this.options.secure&&(s+="; secure"),this.options.domain&&(s+=`; domain=${this.options.domain}`),this.options.maxAge&&(s+=`; max-age=${this.options.maxAge}`),document.cookie=s;}catch{}}removeItem(e){if(n())try{document.cookie=`${e}=; path=${this.options.path}; expires=Thu, 01 Jan 1970 00:00:00 GMT`;}catch{}}};function y(){return _()?new l:new d}var c=class{constructor(e,t="vaif.auth."){this.adapter=e,this.keyPrefix=t;}key(e){return `${this.keyPrefix}${e}`}async getSession(){try{let e=await this.adapter.getItem(this.key("session"));if(!e)return null;let t=JSON.parse(e);return t.expiresAt&&t.expiresAt<Date.now()?(await this.removeSession(),null):t}catch{return null}}async setSession(e){try{await this.adapter.setItem(this.key("session"),JSON.stringify(e));}catch{}}async removeSession(){try{await this.adapter.removeItem(this.key("session"));}catch{}}async getRefreshToken(){try{return await this.adapter.getItem(this.key("refresh_token"))}catch{return null}}async setRefreshToken(e){try{await this.adapter.setItem(this.key("refresh_token"),e);}catch{}}async removeRefreshToken(){try{await this.adapter.removeItem(this.key("refresh_token"));}catch{}}async clear(){await this.removeSession(),await this.removeRefreshToken();}},A=new d,O=_()?new l:A,k=n()?new m:A,v=r=>new S(r);function P(){if(typeof crypto<"u"&&crypto.getRandomValues){let r=new Uint8Array(32);return crypto.getRandomValues(r),Array.from(r,e=>e.toString(16).padStart(2,"0")).join("")}return Array.from({length:64},()=>Math.floor(Math.random()*36).toString(36)).join("")}async function I(r){if(typeof crypto<"u"&&crypto.subtle){let t=new TextEncoder().encode(r),s=await crypto.subtle.digest("SHA-256",t);return btoa(String.fromCharCode(...new Uint8Array(s))).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")}return r}var T={storageKey:"vaif.auth.",autoRefreshToken:true,persistSession:true,detectSessionInUrl:true,flowType:"implicit",debug:false},R=60*1e3,p=class{constructor(e){this.refreshTimer=null;this.listeners=new Set;this.initialized=false;this.initPromise=null;this.currentSession=null;this.broadcastChannel=null;this.config={...T,...e,headers:e.headers||{},storage:e.storage||y()},this.storage=new c(this.config.storage,this.config.storageKey);}async initialize(){return this.initPromise?this.initPromise.then(()=>this.currentSession):(this.initPromise=this._initialize(),await this.initPromise,this.currentSession)}async _initialize(){if(!this.initialized){if(this.config.detectSessionInUrl&&n()){let e=await this._handleUrlSession();if(e){this.currentSession=e,await this._persistSession(e),this._notifyListeners("SIGNED_IN",e),this._setupAutoRefresh(e),this.initialized=true;return}}if(this.config.persistSession){let e=await this.storage.getSession();e&&(this.currentSession=e,this._notifyListeners("INITIAL_SESSION",e),this._setupAutoRefresh(e));}this.initialized=true,n()&&typeof BroadcastChannel<"u"&&(this.broadcastChannel=new BroadcastChannel(`${this.config.storageKey}sync`),this.broadcastChannel.onmessage=e=>{let{type:t,session:s}=e.data;t==="SESSION_UPDATE"&&(this.currentSession=s,this._notifyListeners(s?"TOKEN_REFRESHED":"SIGNED_OUT",s),s?this._setupAutoRefresh(s):this._clearRefreshTimer());});}}async getSession(){return await this.initialize(),this.currentSession}async getUser(){return (await this.getSession())?.user??null}async setSession(e,t){let s=await this._fetchUser(e),i={accessToken:e,refreshToken:t,expiresAt:Date.now()+3600*1e3,expiresIn:3600,tokenType:"bearer",user:s};return this.currentSession=i,await this._persistSession(i),this._notifyListeners("SIGNED_IN",i),this._setupAutoRefresh(i),{session:i,user:s}}async refreshSession(){let e=await this.getSession();if(!e?.refreshToken)return null;try{let t=await this._refreshToken(e.refreshToken),s={...e,accessToken:t.accessToken,refreshToken:t.refreshToken||e.refreshToken,expiresAt:t.expiresAt,expiresIn:t.expiresIn};return this.currentSession=s,await this._persistSession(s),this._notifyListeners("TOKEN_REFRESHED",s),this._setupAutoRefresh(s),s}catch(t){return this._log("Failed to refresh session:",t),await this.signOut(),null}}async signUp(e){let t=await this._fetch("/auth/signup",{method:"POST",body:JSON.stringify({email:e.email,password:e.password,name:e.name,phone:e.phone,metadata:e.metadata,redirectUrl:e.redirectUrl||e.emailRedirectTo})});return u(t)||(this.currentSession=t.session,await this._persistSession(t.session),this._notifyListeners("SIGNED_IN",t.session),this._setupAutoRefresh(t.session)),t}async signInWithPassword(e){let t=await this._fetch("/auth/login",{method:"POST",body:JSON.stringify({email:e.email,password:e.password,mfaCode:e.mfaCode,rememberMe:e.rememberMe})});return u(t)||(this.currentSession=t.session,await this._persistSession(t.session),this._notifyListeners("SIGNED_IN",t.session),this._setupAutoRefresh(t.session)),t}async signInWithOAuth(e){let t=await this._fetch("/auth/oauth/authorize",{method:"POST",body:JSON.stringify({provider:e.provider,redirectUrl:e.redirectTo,scopes:e.scopes,queryParams:e.queryParams,flowType:this.config.flowType})});return n()&&(window.location.href=t.url),t}async signInWithMagicLink(e){return this._fetch("/auth/magic-link/send",{method:"POST",body:JSON.stringify({email:e.email,redirectUrl:e.redirectTo,shouldCreateUser:e.shouldCreateUser})})}async signInWithOTP(e){let t=e.phone?"/auth/phone/send":"/auth/otp/send";return this._fetch(t,{method:"POST",body:JSON.stringify({email:e.email,phone:e.phone,channel:e.channel,shouldCreateUser:e.shouldCreateUser})})}async verifyOTP(e){let t=await this._fetch("/auth/otp/verify",{method:"POST",body:JSON.stringify({email:e.email,phone:e.phone,token:e.token,type:e.type})});return this.currentSession=t.session,await this._persistSession(t.session),this._notifyListeners("SIGNED_IN",t.session),this._setupAutoRefresh(t.session),t}async signInAnonymously(e){let t=await this._fetch("/auth/anonymous",{method:"POST",body:JSON.stringify({metadata:e?.metadata})});return this.currentSession=t.session,await this._persistSession(t.session),this._notifyListeners("SIGNED_IN",t.session),this._setupAutoRefresh(t.session),t}async signInWithSSO(e){let t=await this._fetch("/auth/sso/authorize",{method:"POST",body:JSON.stringify({domain:e.domain,providerId:e.providerId,redirectUrl:e.redirectTo})});return n()&&(window.location.href=t.url),t}async signOut(){try{this.currentSession&&await this._fetch("/auth/logout",{method:"POST"});}catch{}this._clearRefreshTimer(),this.currentSession=null,await this.storage.clear(),this._notifyListeners("SIGNED_OUT",null),this._broadcastSessionUpdate(null);}async signOutAll(){try{await this._fetch("/auth/logout-all",{method:"POST"});}catch{}this._clearRefreshTimer(),this.currentSession=null,await this.storage.clear(),this._notifyListeners("SIGNED_OUT",null);}async resetPasswordForEmail(e){return this._fetch("/auth/forgot-password",{method:"POST",body:JSON.stringify({email:e.email,redirectUrl:e.redirectTo})})}async updatePassword(e){return this._fetch("/users/me/change-password",{method:"POST",body:JSON.stringify({currentPassword:e.currentPassword,newPassword:e.newPassword})})}async setPassword(e){let t=await this._fetch("/auth/reset-password",{method:"POST",body:JSON.stringify({token:e.token,password:e.password})});return this.currentSession=t.session,await this._persistSession(t.session),this._notifyListeners("PASSWORD_RECOVERY",t.session),this._setupAutoRefresh(t.session),t}async updateUser(e){let t=await this._fetch("/users/me",{method:"PATCH",body:JSON.stringify(e)});return this.currentSession&&(this.currentSession={...this.currentSession,user:t.user},await this._persistSession(this.currentSession),this._notifyListeners("USER_UPDATED",this.currentSession)),t.user}async getUserIdentities(){return (await this._fetch("/auth/oauth/providers")).identities}async linkIdentity(e,t){let s=await this._fetch("/auth/oauth/link",{method:"POST",body:JSON.stringify({provider:e,redirectUrl:t?.redirectTo})});return n()&&(window.location.href=s.url),s}async unlinkIdentity(e){return this._fetch(`/auth/oauth/unlink/${e}`,{method:"POST"})}async listMFAFactors(){return (await this._fetch("/auth/mfa/factors")).factors}async enrollMFA(e){return this._fetch("/auth/mfa/setup",{method:"POST",body:JSON.stringify({method:e.type,friendlyName:e.friendlyName})})}async verifyMFA(e){return this._fetch("/auth/mfa/enable",{method:"POST",body:JSON.stringify({factorId:e.factorId,code:e.code})})}async challengeMFA(e){return this._fetch("/auth/mfa/challenge",{method:"POST",body:JSON.stringify({factorId:e.factorId})})}async verifyMFAChallenge(e,t){let s=await this._fetch("/auth/mfa/verify",{method:"POST",body:JSON.stringify({mfaToken:e,code:t})});return this.currentSession=s.session,await this._persistSession(s.session),this._notifyListeners("MFA_CHALLENGE_VERIFIED",s.session),this._setupAutoRefresh(s.session),s}async unenrollMFA(e,t){return this._fetch("/auth/mfa/disable",{method:"POST",body:JSON.stringify({factorId:e,code:t})})}async regenerateBackupCodes(){return this._fetch("/auth/mfa/backup-codes",{method:"POST"})}async listSessions(){return (await this._fetch("/auth/sessions")).sessions}async revokeSession(e){return this._fetch(`/auth/sessions/${e}`,{method:"DELETE"})}async revokeOtherSessions(){return this._fetch("/auth/sessions/revoke-others",{method:"POST"})}async resendEmailVerification(e){return this._fetch("/auth/verify-email/send",{method:"POST",body:JSON.stringify({redirectUrl:e?.redirectTo})})}async verifyEmail(e){let t=await this._fetch("/auth/verify-email/confirm",{method:"POST",body:JSON.stringify({token:e})});return this.currentSession&&(this.currentSession=t.session,await this._persistSession(t.session),this._notifyListeners("USER_UPDATED",t.session)),t}onAuthStateChange(e){return this.listeners.add(e),this.initialized&&e({event:this.currentSession?"INITIAL_SESSION":"SIGNED_OUT",session:this.currentSession}),{unsubscribe:()=>{this.listeners.delete(e);}}}async _fetch(e,t={}){let s=`${this.config.url}${e}`,i={"Content-Type":"application/json",...this.config.headers};this.config.apiKey&&(i["x-vaif-key"]=this.config.apiKey),this.currentSession?.accessToken&&(i.Authorization=`Bearer ${this.currentSession.accessToken}`);let o;try{o=await fetch(s,{...t,headers:i});}catch(a){throw new h(a instanceof Error?a.message:"Network request failed","network_error")}if(!o.ok){let a=await o.json().catch(()=>({}));throw new h(a.message||"Request failed",this._mapErrorCode(o.status,a.code),o.status,a)}return o.json()}async _fetchUser(e){let t=await fetch(`${this.config.url}/auth/me`,{headers:{Authorization:`Bearer ${e}`,...this.config.headers}});if(!t.ok)throw new h("Failed to fetch user","invalid_token",t.status);return (await t.json()).user}async _refreshToken(e){let t=await fetch(`${this.config.url}/auth/refresh`,{method:"POST",headers:{"Content-Type":"application/json",...this.config.headers},body:JSON.stringify({refreshToken:e})});if(!t.ok)throw new h("Failed to refresh token","token_expired",t.status);return t.json()}async _handleUrlSession(){if(!n())return null;let e=new URLSearchParams(window.location.hash.substring(1)),t=new URLSearchParams(window.location.search),s=e.get("access_token")||t.get("access_token"),i=e.get("refresh_token")||t.get("refresh_token"),o=e.get("expires_in")||t.get("expires_in");if(!s)return null;try{let a=await this._fetchUser(s),w={accessToken:s,refreshToken:i||void 0,expiresAt:Date.now()+(o?parseInt(o,10)*1e3:36e5),expiresIn:o?parseInt(o,10):3600,tokenType:"bearer",user:a};return window.history.replaceState(null,"",window.location.pathname),w}catch(a){return this._log("Failed to handle URL session:",a),null}}async _persistSession(e){this.config.persistSession&&(await this.storage.setSession(e),e.refreshToken&&await this.storage.setRefreshToken(e.refreshToken),this._broadcastSessionUpdate(e));}_setupAutoRefresh(e){if(!this.config.autoRefreshToken||!e.refreshToken)return;this._clearRefreshTimer();let t=e.expiresAt,s=Date.now(),i=Math.max(0,t-s-R);this._log(`Setting up auto refresh in ${Math.round(i/1e3)}s`),this.refreshTimer=setTimeout(()=>{this.refreshSession();},i);}_clearRefreshTimer(){this.refreshTimer&&(clearTimeout(this.refreshTimer),this.refreshTimer=null);}_notifyListeners(e,t){let s={event:e,session:t};this.listeners.forEach(i=>{try{i(s);}catch(o){this._log("Error in auth state change listener:",o);}});}_mapErrorCode(e,t){if(t){let s={invalid_credentials:"invalid_credentials",user_not_found:"user_not_found",user_already_exists:"user_already_exists",email_not_verified:"email_not_verified",phone_not_verified:"phone_not_verified",invalid_token:"invalid_token",token_expired:"token_expired",mfa_required:"mfa_required",mfa_invalid:"mfa_invalid",rate_limited:"rate_limited",weak_password:"weak_password",invalid_email:"invalid_email",invalid_phone:"invalid_phone"};if(s[t])return s[t]}switch(e){case 401:return "invalid_credentials";case 403:return "token_expired";case 404:return "user_not_found";case 409:return "user_already_exists";case 429:return "rate_limited";default:return "unknown_error"}}_broadcastSessionUpdate(e){try{this.broadcastChannel?.postMessage({type:"SESSION_UPDATE",session:e});}catch{}}destroy(){this._clearRefreshTimer(),this.broadcastChannel?.close(),this.broadcastChannel=null,this.listeners.clear();}_log(...e){this.config.debug&&console.log("[VaifAuth]",...e);}};function C(r){return new p(r)}
2
+ exports.AuthError=h;exports.InvalidCredentialsError=f;exports.SessionExpiredError=g;exports.SessionStorage=c;exports.VaifAuthClient=p;exports.cookieStorage=v;exports.createAuthClient=C;exports.generateCodeChallenge=I;exports.generateCodeVerifier=P;exports.getDefaultStorage=y;exports.isBrowser=n;exports.isMFAChallenge=u;exports.localStorage=O;exports.memoryStorage=A;exports.sessionStorage=k;
package/dist/index.mjs CHANGED
@@ -1 +1 @@
1
- export{b as AuthError,d as InvalidCredentialsError,c as SessionExpiredError,g as SessionStorage,l as VaifAuthClient,k as cookieStorage,m as createAuthClient,f as getDefaultStorage,e as isBrowser,a as isMFAChallenge,i as localStorage,h as memoryStorage,j as sessionStorage}from'./chunk-JF55RF72.mjs';
1
+ export{b as AuthError,d as InvalidCredentialsError,c as SessionExpiredError,g as SessionStorage,n as VaifAuthClient,k as cookieStorage,o as createAuthClient,m as generateCodeChallenge,l as generateCodeVerifier,f as getDefaultStorage,e as isBrowser,a as isMFAChallenge,i as localStorage,h as memoryStorage,j as sessionStorage}from'./chunk-VKUCT6DW.mjs';
package/dist/react.d.mts CHANGED
@@ -1,5 +1,5 @@
1
- import { E as AuthClientConfig, V as VaifAuthClient, a as Session, U as User, f as SignUpOptions, e as AuthResponse, M as MFAChallenge, g as SignInWithPasswordOptions, h as SignInWithOAuthOptions, i as SignInWithMagicLinkOptions, t as MFAFactor, u as MFASetupResponse, r as OAuthIdentity, O as OAuthProviderType, q as OAuthResponse, d as SessionInfo } from './client-28ISGmu6.mjs';
2
- export { z as AuthChangeEvent, y as AuthEventType, D as AuthSubscription, c as createAuthClient } from './client-28ISGmu6.mjs';
1
+ import { E as AuthClientConfig, V as VaifAuthClient, a as Session, U as User, f as SignUpOptions, e as AuthResponse, M as MFAChallenge, g as SignInWithPasswordOptions, h as SignInWithOAuthOptions, i as SignInWithMagicLinkOptions, t as MFAFactor, u as MFASetupResponse, r as OAuthIdentity, O as OAuthProviderType, q as OAuthResponse, d as SessionInfo } from './client-DYj4LiUx.mjs';
2
+ export { z as AuthChangeEvent, y as AuthEventType, D as AuthSubscription, l as SignInWithSSOOptions, c as createAuthClient } from './client-DYj4LiUx.mjs';
3
3
  import * as react_jsx_runtime from 'react/jsx-runtime';
4
4
  import { ReactNode } from 'react';
5
5
 
@@ -154,5 +154,17 @@ declare function useSessions(): {
154
154
  }>;
155
155
  refresh: () => Promise<void>;
156
156
  };
157
+ /**
158
+ * SSO authentication hook
159
+ */
160
+ declare function useSSO(): {
161
+ signInWithSSO: (options: {
162
+ domain?: string;
163
+ providerId?: string;
164
+ redirectTo?: string;
165
+ }) => Promise<OAuthResponse>;
166
+ isLoading: boolean;
167
+ error: Error | null;
168
+ };
157
169
 
158
- export { AuthClientConfig, AuthProvider, type AuthProviderProps, AuthResponse, MFAChallenge, MFAFactor, Session, User, VaifAuthClient, useAuth, useAuthClient, useIdentities, useIsAuthenticated, useMFA, usePassword, useSession, useSessions, useUser };
170
+ export { AuthClientConfig, AuthProvider, type AuthProviderProps, AuthResponse, MFAChallenge, MFAFactor, Session, User, VaifAuthClient, useAuth, useAuthClient, useIdentities, useIsAuthenticated, useMFA, usePassword, useSSO, useSession, useSessions, useUser };
package/dist/react.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { E as AuthClientConfig, V as VaifAuthClient, a as Session, U as User, f as SignUpOptions, e as AuthResponse, M as MFAChallenge, g as SignInWithPasswordOptions, h as SignInWithOAuthOptions, i as SignInWithMagicLinkOptions, t as MFAFactor, u as MFASetupResponse, r as OAuthIdentity, O as OAuthProviderType, q as OAuthResponse, d as SessionInfo } from './client-28ISGmu6.js';
2
- export { z as AuthChangeEvent, y as AuthEventType, D as AuthSubscription, c as createAuthClient } from './client-28ISGmu6.js';
1
+ import { E as AuthClientConfig, V as VaifAuthClient, a as Session, U as User, f as SignUpOptions, e as AuthResponse, M as MFAChallenge, g as SignInWithPasswordOptions, h as SignInWithOAuthOptions, i as SignInWithMagicLinkOptions, t as MFAFactor, u as MFASetupResponse, r as OAuthIdentity, O as OAuthProviderType, q as OAuthResponse, d as SessionInfo } from './client-DYj4LiUx.js';
2
+ export { z as AuthChangeEvent, y as AuthEventType, D as AuthSubscription, l as SignInWithSSOOptions, c as createAuthClient } from './client-DYj4LiUx.js';
3
3
  import * as react_jsx_runtime from 'react/jsx-runtime';
4
4
  import { ReactNode } from 'react';
5
5
 
@@ -154,5 +154,17 @@ declare function useSessions(): {
154
154
  }>;
155
155
  refresh: () => Promise<void>;
156
156
  };
157
+ /**
158
+ * SSO authentication hook
159
+ */
160
+ declare function useSSO(): {
161
+ signInWithSSO: (options: {
162
+ domain?: string;
163
+ providerId?: string;
164
+ redirectTo?: string;
165
+ }) => Promise<OAuthResponse>;
166
+ isLoading: boolean;
167
+ error: Error | null;
168
+ };
157
169
 
158
- export { AuthClientConfig, AuthProvider, type AuthProviderProps, AuthResponse, MFAChallenge, MFAFactor, Session, User, VaifAuthClient, useAuth, useAuthClient, useIdentities, useIsAuthenticated, useMFA, usePassword, useSession, useSessions, useUser };
170
+ export { AuthClientConfig, AuthProvider, type AuthProviderProps, AuthResponse, MFAChallenge, MFAFactor, Session, User, VaifAuthClient, useAuth, useAuthClient, useIdentities, useIsAuthenticated, useMFA, usePassword, useSSO, useSession, useSessions, useUser };
package/dist/react.js CHANGED
@@ -1 +1 @@
1
- 'use strict';var react=require('react'),jsxRuntime=require('react/jsx-runtime');function I(s){return "mfaRequired"in s&&s.mfaRequired===true}var m=class s extends Error{constructor(e,t,r,n){super(e),this.name="AuthError",this.code=t,this.status=r,this.details=n;}static isAuthError(e){return e instanceof s}};function f(){return typeof window<"u"&&typeof window.document<"u"}function C(){if(!f())return false;try{let s="__vaif_test__";return window.localStorage.setItem(s,s),window.localStorage.removeItem(s),!0}catch{return false}}var w=class{constructor(){this.storage=new Map;}getItem(e){return this.storage.get(e)??null}setItem(e,t){this.storage.set(e,t);}removeItem(e){this.storage.delete(e);}clear(){this.storage.clear();}},_=class{getItem(e){if(!f())return null;try{return window.localStorage.getItem(e)}catch{return null}}setItem(e,t){if(f())try{window.localStorage.setItem(e,t);}catch{}}removeItem(e){if(f())try{window.localStorage.removeItem(e);}catch{}}};function R(){return C()?new _:new w}var v=class{constructor(e,t="vaif.auth."){this.adapter=e,this.keyPrefix=t;}key(e){return `${this.keyPrefix}${e}`}async getSession(){try{let e=await this.adapter.getItem(this.key("session"));if(!e)return null;let t=JSON.parse(e);return t.expiresAt&&t.expiresAt<Date.now()?(await this.removeSession(),null):t}catch{return null}}async setSession(e){try{await this.adapter.setItem(this.key("session"),JSON.stringify(e));}catch{}}async removeSession(){try{await this.adapter.removeItem(this.key("session"));}catch{}}async getRefreshToken(){try{return await this.adapter.getItem(this.key("refresh_token"))}catch{return null}}async setRefreshToken(e){try{await this.adapter.setItem(this.key("refresh_token"),e);}catch{}}async removeRefreshToken(){try{await this.adapter.removeItem(this.key("refresh_token"));}catch{}}async clear(){await this.removeSession(),await this.removeRefreshToken();}},b=new w;C()?new _:b;var U={storageKey:"vaif.auth.",autoRefreshToken:true,persistSession:true,detectSessionInUrl:true,flowType:"implicit",debug:false},F=60*1e3,P=class{constructor(e){this.refreshTimer=null;this.listeners=new Set;this.initialized=false;this.initPromise=null;this.currentSession=null;this.config={...U,...e,headers:e.headers||{},storage:e.storage||R()},this.storage=new v(this.config.storage,this.config.storageKey);}async initialize(){return this.initPromise?this.initPromise.then(()=>this.currentSession):(this.initPromise=this._initialize(),await this.initPromise,this.currentSession)}async _initialize(){if(!this.initialized){if(this.config.detectSessionInUrl&&f()){let e=await this._handleUrlSession();if(e){this.currentSession=e,await this._persistSession(e),this._notifyListeners("SIGNED_IN",e),this._setupAutoRefresh(e),this.initialized=true;return}}if(this.config.persistSession){let e=await this.storage.getSession();e&&(this.currentSession=e,this._notifyListeners("INITIAL_SESSION",e),this._setupAutoRefresh(e));}this.initialized=true;}}async getSession(){return await this.initialize(),this.currentSession}async getUser(){return (await this.getSession())?.user??null}async setSession(e,t){let r=await this._fetchUser(e),n={accessToken:e,refreshToken:t,expiresAt:Date.now()+3600*1e3,expiresIn:3600,tokenType:"bearer",user:r};return this.currentSession=n,await this._persistSession(n),this._notifyListeners("SIGNED_IN",n),this._setupAutoRefresh(n),{session:n,user:r}}async refreshSession(){let e=await this.getSession();if(!e?.refreshToken)return null;try{let t=await this._refreshToken(e.refreshToken),r={...e,accessToken:t.accessToken,refreshToken:t.refreshToken||e.refreshToken,expiresAt:t.expiresAt,expiresIn:t.expiresIn};return this.currentSession=r,await this._persistSession(r),this._notifyListeners("TOKEN_REFRESHED",r),this._setupAutoRefresh(r),r}catch(t){return this._log("Failed to refresh session:",t),await this.signOut(),null}}async signUp(e){let t=await this._fetch("/auth/signup",{method:"POST",body:JSON.stringify({email:e.email,password:e.password,name:e.name,phone:e.phone,metadata:e.metadata,redirectUrl:e.redirectUrl||e.emailRedirectTo})});return I(t)||(this.currentSession=t.session,await this._persistSession(t.session),this._notifyListeners("SIGNED_IN",t.session),this._setupAutoRefresh(t.session)),t}async signInWithPassword(e){let t=await this._fetch("/auth/login",{method:"POST",body:JSON.stringify({email:e.email,password:e.password,mfaCode:e.mfaCode,rememberMe:e.rememberMe})});return I(t)||(this.currentSession=t.session,await this._persistSession(t.session),this._notifyListeners("SIGNED_IN",t.session),this._setupAutoRefresh(t.session)),t}async signInWithOAuth(e){let t=await this._fetch("/auth/oauth/authorize",{method:"POST",body:JSON.stringify({provider:e.provider,redirectUrl:e.redirectTo,scopes:e.scopes,queryParams:e.queryParams,flowType:this.config.flowType})});return f()&&(window.location.href=t.url),t}async signInWithMagicLink(e){return this._fetch("/auth/magic-link/send",{method:"POST",body:JSON.stringify({email:e.email,redirectUrl:e.redirectTo,shouldCreateUser:e.shouldCreateUser})})}async signInWithOTP(e){let t=e.phone?"/auth/phone/send":"/auth/otp/send";return this._fetch(t,{method:"POST",body:JSON.stringify({email:e.email,phone:e.phone,channel:e.channel,shouldCreateUser:e.shouldCreateUser})})}async verifyOTP(e){let t=await this._fetch("/auth/otp/verify",{method:"POST",body:JSON.stringify({email:e.email,phone:e.phone,token:e.token,type:e.type})});return this.currentSession=t.session,await this._persistSession(t.session),this._notifyListeners("SIGNED_IN",t.session),this._setupAutoRefresh(t.session),t}async signInAnonymously(e){let t=await this._fetch("/auth/anonymous",{method:"POST",body:JSON.stringify({metadata:e?.metadata})});return this.currentSession=t.session,await this._persistSession(t.session),this._notifyListeners("SIGNED_IN",t.session),this._setupAutoRefresh(t.session),t}async signOut(){try{this.currentSession&&await this._fetch("/auth/logout",{method:"POST"});}catch{}this._clearRefreshTimer(),this.currentSession=null,await this.storage.clear(),this._notifyListeners("SIGNED_OUT",null);}async signOutAll(){try{await this._fetch("/auth/logout-all",{method:"POST"});}catch{}this._clearRefreshTimer(),this.currentSession=null,await this.storage.clear(),this._notifyListeners("SIGNED_OUT",null);}async resetPasswordForEmail(e){return this._fetch("/auth/forgot-password",{method:"POST",body:JSON.stringify({email:e.email,redirectUrl:e.redirectTo})})}async updatePassword(e){return this._fetch("/users/me/change-password",{method:"POST",body:JSON.stringify({currentPassword:e.currentPassword,newPassword:e.newPassword})})}async setPassword(e){let t=await this._fetch("/auth/reset-password",{method:"POST",body:JSON.stringify({token:e.token,password:e.password})});return this.currentSession=t.session,await this._persistSession(t.session),this._notifyListeners("PASSWORD_RECOVERY",t.session),this._setupAutoRefresh(t.session),t}async updateUser(e){let t=await this._fetch("/users/me",{method:"PATCH",body:JSON.stringify(e)});return this.currentSession&&(this.currentSession={...this.currentSession,user:t.user},await this._persistSession(this.currentSession),this._notifyListeners("USER_UPDATED",this.currentSession)),t.user}async getUserIdentities(){return (await this._fetch("/auth/oauth/providers")).identities}async linkIdentity(e,t){let r=await this._fetch("/auth/oauth/link",{method:"POST",body:JSON.stringify({provider:e,redirectUrl:t?.redirectTo})});return f()&&(window.location.href=r.url),r}async unlinkIdentity(e){return this._fetch(`/auth/oauth/unlink/${e}`,{method:"POST"})}async listMFAFactors(){return (await this._fetch("/auth/mfa/factors")).factors}async enrollMFA(e){return this._fetch("/auth/mfa/setup",{method:"POST",body:JSON.stringify({method:e.type,friendlyName:e.friendlyName})})}async verifyMFA(e){return this._fetch("/auth/mfa/enable",{method:"POST",body:JSON.stringify({factorId:e.factorId,code:e.code})})}async challengeMFA(e){return this._fetch("/auth/mfa/challenge",{method:"POST",body:JSON.stringify({factorId:e.factorId})})}async verifyMFAChallenge(e,t){let r=await this._fetch("/auth/mfa/verify",{method:"POST",body:JSON.stringify({mfaToken:e,code:t})});return this.currentSession=r.session,await this._persistSession(r.session),this._notifyListeners("MFA_CHALLENGE_VERIFIED",r.session),this._setupAutoRefresh(r.session),r}async unenrollMFA(e,t){return this._fetch("/auth/mfa/disable",{method:"POST",body:JSON.stringify({factorId:e,code:t})})}async regenerateBackupCodes(){return this._fetch("/auth/mfa/backup-codes",{method:"POST"})}async listSessions(){return (await this._fetch("/auth/sessions")).sessions}async revokeSession(e){return this._fetch(`/auth/sessions/${e}`,{method:"DELETE"})}async revokeOtherSessions(){return this._fetch("/auth/sessions/revoke-others",{method:"POST"})}async resendEmailVerification(e){return this._fetch("/auth/verify-email/send",{method:"POST",body:JSON.stringify({redirectUrl:e?.redirectTo})})}async verifyEmail(e){let t=await this._fetch("/auth/verify-email/confirm",{method:"POST",body:JSON.stringify({token:e})});return this.currentSession&&(this.currentSession=t.session,await this._persistSession(t.session),this._notifyListeners("USER_UPDATED",t.session)),t}onAuthStateChange(e){return this.listeners.add(e),this.initialized&&e({event:this.currentSession?"INITIAL_SESSION":"SIGNED_OUT",session:this.currentSession}),{unsubscribe:()=>{this.listeners.delete(e);}}}async _fetch(e,t={}){let r=`${this.config.url}${e}`,n={"Content-Type":"application/json",...this.config.headers};this.config.apiKey&&(n["x-vaif-key"]=this.config.apiKey),this.currentSession?.accessToken&&(n.Authorization=`Bearer ${this.currentSession.accessToken}`);let o=await fetch(r,{...t,headers:n});if(!o.ok){let h=await o.json().catch(()=>({}));throw new m(h.message||"Request failed",this._mapErrorCode(o.status,h.code),o.status,h)}return o.json()}async _fetchUser(e){let t=await fetch(`${this.config.url}/auth/me`,{headers:{Authorization:`Bearer ${e}`,...this.config.headers}});if(!t.ok)throw new m("Failed to fetch user","invalid_token",t.status);return (await t.json()).user}async _refreshToken(e){let t=await fetch(`${this.config.url}/auth/refresh`,{method:"POST",headers:{"Content-Type":"application/json",...this.config.headers},body:JSON.stringify({refreshToken:e})});if(!t.ok)throw new m("Failed to refresh token","token_expired",t.status);return t.json()}async _handleUrlSession(){if(!f())return null;let e=new URLSearchParams(window.location.hash.substring(1)),t=new URLSearchParams(window.location.search),r=e.get("access_token")||t.get("access_token"),n=e.get("refresh_token")||t.get("refresh_token"),o=e.get("expires_in")||t.get("expires_in");if(!r)return null;try{let h=await this._fetchUser(r),i={accessToken:r,refreshToken:n||void 0,expiresAt:Date.now()+(o?parseInt(o,10)*1e3:36e5),expiresIn:o?parseInt(o,10):3600,tokenType:"bearer",user:h};return window.history.replaceState(null,"",window.location.pathname),i}catch(h){return this._log("Failed to handle URL session:",h),null}}async _persistSession(e){this.config.persistSession&&(await this.storage.setSession(e),e.refreshToken&&await this.storage.setRefreshToken(e.refreshToken));}_setupAutoRefresh(e){if(!this.config.autoRefreshToken||!e.refreshToken)return;this._clearRefreshTimer();let t=e.expiresAt,r=Date.now(),n=Math.max(0,t-r-F);this._log(`Setting up auto refresh in ${Math.round(n/1e3)}s`),this.refreshTimer=setTimeout(()=>{this.refreshSession();},n);}_clearRefreshTimer(){this.refreshTimer&&(clearTimeout(this.refreshTimer),this.refreshTimer=null);}_notifyListeners(e,t){let r={event:e,session:t};this.listeners.forEach(n=>{try{n(r);}catch(o){this._log("Error in auth state change listener:",o);}});}_mapErrorCode(e,t){if(t){let r={invalid_credentials:"invalid_credentials",user_not_found:"user_not_found",user_already_exists:"user_already_exists",email_not_verified:"email_not_verified",phone_not_verified:"phone_not_verified",invalid_token:"invalid_token",token_expired:"token_expired",mfa_required:"mfa_required",mfa_invalid:"mfa_invalid",rate_limited:"rate_limited",weak_password:"weak_password",invalid_email:"invalid_email",invalid_phone:"invalid_phone"};if(r[t])return r[t]}switch(e){case 401:return "invalid_credentials";case 403:return "token_expired";case 404:return "user_not_found";case 409:return "user_already_exists";case 429:return "rate_limited";default:return "unknown_error"}}_log(...e){this.config.debug&&console.log("[VaifAuth]",...e);}};function x(s){return new P(s)}var E=react.createContext(null);function B({children:s,config:e,client:t,initialSession:r=null}){let[n]=react.useState(()=>{if(t)return t;if(!e)throw new Error("AuthProvider requires either config or client prop");return x(e)}),[o,h]=react.useState(r),[i,g]=react.useState(!r),[c,a]=react.useState(null);react.useEffect(()=>{let d=true;n.initialize().then(S=>{d&&(h(S),g(false));}).catch(S=>{d&&(a(S),g(false));});let{unsubscribe:y}=n.onAuthStateChange(S=>{d&&(h(S.session),a(null));});return ()=>{d=false,y();}},[n]);let u=react.useMemo(()=>({client:n,session:o,user:o?.user??null,isLoading:i,isAuthenticated:!!o,error:c}),[n,o,i,c]);return jsxRuntime.jsx(E.Provider,{value:u,children:s})}function A(){let s=react.useContext(E);if(!s)throw new Error("useAuth must be used within an AuthProvider");return s}function O(){let{client:s}=A();return s}function Y(){let{client:s,session:e,user:t,isLoading:r,isAuthenticated:n,error:o}=A(),h=react.useCallback(async d=>s.signUp(d),[s]),i=react.useCallback(async d=>s.signInWithPassword(d),[s]),g=react.useCallback(async d=>{await s.signInWithOAuth(d);},[s]),c=react.useCallback(async d=>s.signInWithMagicLink(d),[s]),a=react.useCallback(async()=>s.signOut(),[s]),u=react.useCallback(async()=>s.refreshSession(),[s]);return {session:e,user:t,isLoading:r,isAuthenticated:n,error:o,signUp:h,signInWithPassword:i,signInWithOAuth:g,signInWithMagicLink:c,signOut:a,refreshSession:u,client:s}}function Q(){let{user:s}=A();return s}function X(){let{session:s}=A();return s}function Z(){let{isAuthenticated:s}=A();return s}function ee(){let s=O(),[e,t]=react.useState(false),[r,n]=react.useState(null),o=react.useCallback(async(i,g)=>{t(true),n(null);try{return await s.resetPasswordForEmail({email:i,redirectTo:g})}catch(c){throw n(c),c}finally{t(false);}},[s]),h=react.useCallback(async(i,g)=>{t(true),n(null);try{return await s.updatePassword({currentPassword:i,newPassword:g})}catch(c){throw n(c),c}finally{t(false);}},[s]);return {resetPassword:o,updatePassword:h,isLoading:e,error:r}}function te(){let s=O(),[e,t]=react.useState([]),[r,n]=react.useState(false),[o,h]=react.useState(null),i=react.useCallback(async()=>{n(true);try{let u=await s.listMFAFactors();t(u);}catch(u){h(u);}finally{n(false);}},[s]),g=react.useCallback(async u=>s.enrollMFA({type:"totp",friendlyName:u}),[s]),c=react.useCallback(async(u,d)=>{let y=await s.verifyMFA({factorId:u,code:d});return await i(),y},[s,i]),a=react.useCallback(async(u,d)=>{let y=await s.unenrollMFA(u,d);return await i(),y},[s,i]);return react.useEffect(()=>{i();},[i]),{factors:e,isLoading:r,error:o,enrollTOTP:g,verifyMFA:c,unenroll:a,refresh:i}}function se(){let s=O(),[e,t]=react.useState([]),[r,n]=react.useState(true),[o,h]=react.useState(null),i=react.useCallback(async()=>{n(true);try{let a=await s.getUserIdentities();t(a);}catch(a){h(a);}finally{n(false);}},[s]),g=react.useCallback(async(a,u)=>s.linkIdentity(a,{redirectTo:u}),[s]),c=react.useCallback(async a=>{let u=await s.unlinkIdentity(a);return await i(),u},[s,i]);return react.useEffect(()=>{i();},[i]),{identities:e,isLoading:r,error:o,linkIdentity:g,unlinkIdentity:c,refresh:i}}function re(){let s=O(),[e,t]=react.useState([]),[r,n]=react.useState(true),[o,h]=react.useState(null),i=react.useCallback(async()=>{n(true);try{let a=await s.listSessions();t(a);}catch(a){h(a);}finally{n(false);}},[s]),g=react.useCallback(async a=>{let u=await s.revokeSession(a);return await i(),u},[s,i]),c=react.useCallback(async()=>{let a=await s.revokeOtherSessions();return await i(),a},[s,i]);return react.useEffect(()=>{i();},[i]),{sessions:e,isLoading:r,error:o,revokeSession:g,revokeOtherSessions:c,refresh:i}}exports.AuthProvider=B;exports.VaifAuthClient=P;exports.createAuthClient=x;exports.useAuth=Y;exports.useAuthClient=O;exports.useIdentities=se;exports.useIsAuthenticated=Z;exports.useMFA=te;exports.usePassword=ee;exports.useSession=X;exports.useSessions=re;exports.useUser=Q;
1
+ 'use strict';var react=require('react'),jsxRuntime=require('react/jsx-runtime');function k(s){return "mfaRequired"in s&&s.mfaRequired===true}var m=class s extends Error{constructor(e,t,r,n){super(e),this.name="AuthError",this.code=t,this.status=r,this.details=n;}static isAuthError(e){return e instanceof s}};function g(){return typeof window<"u"&&typeof window.document<"u"}function R(){if(!g())return false;try{let s="__vaif_test__";return window.localStorage.setItem(s,s),window.localStorage.removeItem(s),!0}catch{return false}}var _=class{constructor(){this.storage=new Map;}getItem(e){return this.storage.get(e)??null}setItem(e,t){this.storage.set(e,t);}removeItem(e){this.storage.delete(e);}clear(){this.storage.clear();}},O=class{getItem(e){if(!g())return null;try{return window.localStorage.getItem(e)}catch{return null}}setItem(e,t){if(g())try{window.localStorage.setItem(e,t);}catch{}}removeItem(e){if(g())try{window.localStorage.removeItem(e);}catch{}}};function x(){return R()?new O:new _}var v=class{constructor(e,t="vaif.auth."){this.adapter=e,this.keyPrefix=t;}key(e){return `${this.keyPrefix}${e}`}async getSession(){try{let e=await this.adapter.getItem(this.key("session"));if(!e)return null;let t=JSON.parse(e);return t.expiresAt&&t.expiresAt<Date.now()?(await this.removeSession(),null):t}catch{return null}}async setSession(e){try{await this.adapter.setItem(this.key("session"),JSON.stringify(e));}catch{}}async removeSession(){try{await this.adapter.removeItem(this.key("session"));}catch{}}async getRefreshToken(){try{return await this.adapter.getItem(this.key("refresh_token"))}catch{return null}}async setRefreshToken(e){try{await this.adapter.setItem(this.key("refresh_token"),e);}catch{}}async removeRefreshToken(){try{await this.adapter.removeItem(this.key("refresh_token"));}catch{}}async clear(){await this.removeSession(),await this.removeRefreshToken();}},b=new _;R()?new O:b;var U={storageKey:"vaif.auth.",autoRefreshToken:true,persistSession:true,detectSessionInUrl:true,flowType:"implicit",debug:false},F=60*1e3,P=class{constructor(e){this.refreshTimer=null;this.listeners=new Set;this.initialized=false;this.initPromise=null;this.currentSession=null;this.broadcastChannel=null;this.config={...U,...e,headers:e.headers||{},storage:e.storage||x()},this.storage=new v(this.config.storage,this.config.storageKey);}async initialize(){return this.initPromise?this.initPromise.then(()=>this.currentSession):(this.initPromise=this._initialize(),await this.initPromise,this.currentSession)}async _initialize(){if(!this.initialized){if(this.config.detectSessionInUrl&&g()){let e=await this._handleUrlSession();if(e){this.currentSession=e,await this._persistSession(e),this._notifyListeners("SIGNED_IN",e),this._setupAutoRefresh(e),this.initialized=true;return}}if(this.config.persistSession){let e=await this.storage.getSession();e&&(this.currentSession=e,this._notifyListeners("INITIAL_SESSION",e),this._setupAutoRefresh(e));}this.initialized=true,g()&&typeof BroadcastChannel<"u"&&(this.broadcastChannel=new BroadcastChannel(`${this.config.storageKey}sync`),this.broadcastChannel.onmessage=e=>{let{type:t,session:r}=e.data;t==="SESSION_UPDATE"&&(this.currentSession=r,this._notifyListeners(r?"TOKEN_REFRESHED":"SIGNED_OUT",r),r?this._setupAutoRefresh(r):this._clearRefreshTimer());});}}async getSession(){return await this.initialize(),this.currentSession}async getUser(){return (await this.getSession())?.user??null}async setSession(e,t){let r=await this._fetchUser(e),n={accessToken:e,refreshToken:t,expiresAt:Date.now()+3600*1e3,expiresIn:3600,tokenType:"bearer",user:r};return this.currentSession=n,await this._persistSession(n),this._notifyListeners("SIGNED_IN",n),this._setupAutoRefresh(n),{session:n,user:r}}async refreshSession(){let e=await this.getSession();if(!e?.refreshToken)return null;try{let t=await this._refreshToken(e.refreshToken),r={...e,accessToken:t.accessToken,refreshToken:t.refreshToken||e.refreshToken,expiresAt:t.expiresAt,expiresIn:t.expiresIn};return this.currentSession=r,await this._persistSession(r),this._notifyListeners("TOKEN_REFRESHED",r),this._setupAutoRefresh(r),r}catch(t){return this._log("Failed to refresh session:",t),await this.signOut(),null}}async signUp(e){let t=await this._fetch("/auth/signup",{method:"POST",body:JSON.stringify({email:e.email,password:e.password,name:e.name,phone:e.phone,metadata:e.metadata,redirectUrl:e.redirectUrl||e.emailRedirectTo})});return k(t)||(this.currentSession=t.session,await this._persistSession(t.session),this._notifyListeners("SIGNED_IN",t.session),this._setupAutoRefresh(t.session)),t}async signInWithPassword(e){let t=await this._fetch("/auth/login",{method:"POST",body:JSON.stringify({email:e.email,password:e.password,mfaCode:e.mfaCode,rememberMe:e.rememberMe})});return k(t)||(this.currentSession=t.session,await this._persistSession(t.session),this._notifyListeners("SIGNED_IN",t.session),this._setupAutoRefresh(t.session)),t}async signInWithOAuth(e){let t=await this._fetch("/auth/oauth/authorize",{method:"POST",body:JSON.stringify({provider:e.provider,redirectUrl:e.redirectTo,scopes:e.scopes,queryParams:e.queryParams,flowType:this.config.flowType})});return g()&&(window.location.href=t.url),t}async signInWithMagicLink(e){return this._fetch("/auth/magic-link/send",{method:"POST",body:JSON.stringify({email:e.email,redirectUrl:e.redirectTo,shouldCreateUser:e.shouldCreateUser})})}async signInWithOTP(e){let t=e.phone?"/auth/phone/send":"/auth/otp/send";return this._fetch(t,{method:"POST",body:JSON.stringify({email:e.email,phone:e.phone,channel:e.channel,shouldCreateUser:e.shouldCreateUser})})}async verifyOTP(e){let t=await this._fetch("/auth/otp/verify",{method:"POST",body:JSON.stringify({email:e.email,phone:e.phone,token:e.token,type:e.type})});return this.currentSession=t.session,await this._persistSession(t.session),this._notifyListeners("SIGNED_IN",t.session),this._setupAutoRefresh(t.session),t}async signInAnonymously(e){let t=await this._fetch("/auth/anonymous",{method:"POST",body:JSON.stringify({metadata:e?.metadata})});return this.currentSession=t.session,await this._persistSession(t.session),this._notifyListeners("SIGNED_IN",t.session),this._setupAutoRefresh(t.session),t}async signInWithSSO(e){let t=await this._fetch("/auth/sso/authorize",{method:"POST",body:JSON.stringify({domain:e.domain,providerId:e.providerId,redirectUrl:e.redirectTo})});return g()&&(window.location.href=t.url),t}async signOut(){try{this.currentSession&&await this._fetch("/auth/logout",{method:"POST"});}catch{}this._clearRefreshTimer(),this.currentSession=null,await this.storage.clear(),this._notifyListeners("SIGNED_OUT",null),this._broadcastSessionUpdate(null);}async signOutAll(){try{await this._fetch("/auth/logout-all",{method:"POST"});}catch{}this._clearRefreshTimer(),this.currentSession=null,await this.storage.clear(),this._notifyListeners("SIGNED_OUT",null);}async resetPasswordForEmail(e){return this._fetch("/auth/forgot-password",{method:"POST",body:JSON.stringify({email:e.email,redirectUrl:e.redirectTo})})}async updatePassword(e){return this._fetch("/users/me/change-password",{method:"POST",body:JSON.stringify({currentPassword:e.currentPassword,newPassword:e.newPassword})})}async setPassword(e){let t=await this._fetch("/auth/reset-password",{method:"POST",body:JSON.stringify({token:e.token,password:e.password})});return this.currentSession=t.session,await this._persistSession(t.session),this._notifyListeners("PASSWORD_RECOVERY",t.session),this._setupAutoRefresh(t.session),t}async updateUser(e){let t=await this._fetch("/users/me",{method:"PATCH",body:JSON.stringify(e)});return this.currentSession&&(this.currentSession={...this.currentSession,user:t.user},await this._persistSession(this.currentSession),this._notifyListeners("USER_UPDATED",this.currentSession)),t.user}async getUserIdentities(){return (await this._fetch("/auth/oauth/providers")).identities}async linkIdentity(e,t){let r=await this._fetch("/auth/oauth/link",{method:"POST",body:JSON.stringify({provider:e,redirectUrl:t?.redirectTo})});return g()&&(window.location.href=r.url),r}async unlinkIdentity(e){return this._fetch(`/auth/oauth/unlink/${e}`,{method:"POST"})}async listMFAFactors(){return (await this._fetch("/auth/mfa/factors")).factors}async enrollMFA(e){return this._fetch("/auth/mfa/setup",{method:"POST",body:JSON.stringify({method:e.type,friendlyName:e.friendlyName})})}async verifyMFA(e){return this._fetch("/auth/mfa/enable",{method:"POST",body:JSON.stringify({factorId:e.factorId,code:e.code})})}async challengeMFA(e){return this._fetch("/auth/mfa/challenge",{method:"POST",body:JSON.stringify({factorId:e.factorId})})}async verifyMFAChallenge(e,t){let r=await this._fetch("/auth/mfa/verify",{method:"POST",body:JSON.stringify({mfaToken:e,code:t})});return this.currentSession=r.session,await this._persistSession(r.session),this._notifyListeners("MFA_CHALLENGE_VERIFIED",r.session),this._setupAutoRefresh(r.session),r}async unenrollMFA(e,t){return this._fetch("/auth/mfa/disable",{method:"POST",body:JSON.stringify({factorId:e,code:t})})}async regenerateBackupCodes(){return this._fetch("/auth/mfa/backup-codes",{method:"POST"})}async listSessions(){return (await this._fetch("/auth/sessions")).sessions}async revokeSession(e){return this._fetch(`/auth/sessions/${e}`,{method:"DELETE"})}async revokeOtherSessions(){return this._fetch("/auth/sessions/revoke-others",{method:"POST"})}async resendEmailVerification(e){return this._fetch("/auth/verify-email/send",{method:"POST",body:JSON.stringify({redirectUrl:e?.redirectTo})})}async verifyEmail(e){let t=await this._fetch("/auth/verify-email/confirm",{method:"POST",body:JSON.stringify({token:e})});return this.currentSession&&(this.currentSession=t.session,await this._persistSession(t.session),this._notifyListeners("USER_UPDATED",t.session)),t}onAuthStateChange(e){return this.listeners.add(e),this.initialized&&e({event:this.currentSession?"INITIAL_SESSION":"SIGNED_OUT",session:this.currentSession}),{unsubscribe:()=>{this.listeners.delete(e);}}}async _fetch(e,t={}){let r=`${this.config.url}${e}`,n={"Content-Type":"application/json",...this.config.headers};this.config.apiKey&&(n["x-vaif-key"]=this.config.apiKey),this.currentSession?.accessToken&&(n.Authorization=`Bearer ${this.currentSession.accessToken}`);let o;try{o=await fetch(r,{...t,headers:n});}catch(a){throw new m(a instanceof Error?a.message:"Network request failed","network_error")}if(!o.ok){let a=await o.json().catch(()=>({}));throw new m(a.message||"Request failed",this._mapErrorCode(o.status,a.code),o.status,a)}return o.json()}async _fetchUser(e){let t=await fetch(`${this.config.url}/auth/me`,{headers:{Authorization:`Bearer ${e}`,...this.config.headers}});if(!t.ok)throw new m("Failed to fetch user","invalid_token",t.status);return (await t.json()).user}async _refreshToken(e){let t=await fetch(`${this.config.url}/auth/refresh`,{method:"POST",headers:{"Content-Type":"application/json",...this.config.headers},body:JSON.stringify({refreshToken:e})});if(!t.ok)throw new m("Failed to refresh token","token_expired",t.status);return t.json()}async _handleUrlSession(){if(!g())return null;let e=new URLSearchParams(window.location.hash.substring(1)),t=new URLSearchParams(window.location.search),r=e.get("access_token")||t.get("access_token"),n=e.get("refresh_token")||t.get("refresh_token"),o=e.get("expires_in")||t.get("expires_in");if(!r)return null;try{let a=await this._fetchUser(r),i={accessToken:r,refreshToken:n||void 0,expiresAt:Date.now()+(o?parseInt(o,10)*1e3:36e5),expiresIn:o?parseInt(o,10):3600,tokenType:"bearer",user:a};return window.history.replaceState(null,"",window.location.pathname),i}catch(a){return this._log("Failed to handle URL session:",a),null}}async _persistSession(e){this.config.persistSession&&(await this.storage.setSession(e),e.refreshToken&&await this.storage.setRefreshToken(e.refreshToken),this._broadcastSessionUpdate(e));}_setupAutoRefresh(e){if(!this.config.autoRefreshToken||!e.refreshToken)return;this._clearRefreshTimer();let t=e.expiresAt,r=Date.now(),n=Math.max(0,t-r-F);this._log(`Setting up auto refresh in ${Math.round(n/1e3)}s`),this.refreshTimer=setTimeout(()=>{this.refreshSession();},n);}_clearRefreshTimer(){this.refreshTimer&&(clearTimeout(this.refreshTimer),this.refreshTimer=null);}_notifyListeners(e,t){let r={event:e,session:t};this.listeners.forEach(n=>{try{n(r);}catch(o){this._log("Error in auth state change listener:",o);}});}_mapErrorCode(e,t){if(t){let r={invalid_credentials:"invalid_credentials",user_not_found:"user_not_found",user_already_exists:"user_already_exists",email_not_verified:"email_not_verified",phone_not_verified:"phone_not_verified",invalid_token:"invalid_token",token_expired:"token_expired",mfa_required:"mfa_required",mfa_invalid:"mfa_invalid",rate_limited:"rate_limited",weak_password:"weak_password",invalid_email:"invalid_email",invalid_phone:"invalid_phone"};if(r[t])return r[t]}switch(e){case 401:return "invalid_credentials";case 403:return "token_expired";case 404:return "user_not_found";case 409:return "user_already_exists";case 429:return "rate_limited";default:return "unknown_error"}}_broadcastSessionUpdate(e){try{this.broadcastChannel?.postMessage({type:"SESSION_UPDATE",session:e});}catch{}}destroy(){this._clearRefreshTimer(),this.broadcastChannel?.close(),this.broadcastChannel=null,this.listeners.clear();}_log(...e){this.config.debug&&console.log("[VaifAuth]",...e);}};function C(s){return new P(s)}var E=react.createContext(null);function j({children:s,config:e,client:t,initialSession:r=null}){let[n]=react.useState(()=>{if(t)return t;if(!e)throw new Error("AuthProvider requires either config or client prop");return C(e)}),[o,a]=react.useState(r),[i,f]=react.useState(!r),[l,h]=react.useState(null);react.useEffect(()=>{let p=true;n.initialize().then(y=>{p&&(a(y),f(false));}).catch(y=>{p&&(h(y),f(false));});let{unsubscribe:S}=n.onAuthStateChange(y=>{p&&(a(y.session),h(null));});return ()=>{p=false,S();}},[n]);let u=react.useMemo(()=>({client:n,session:o,user:o?.user??null,isLoading:i,isAuthenticated:!!o,error:l}),[n,o,i,l]);return jsxRuntime.jsx(E.Provider,{value:u,children:s})}function A(){let s=react.useContext(E);if(!s)throw new Error("useAuth must be used within an AuthProvider");return s}function w(){let{client:s}=A();return s}function Y(){let{client:s,session:e,user:t,isLoading:r,isAuthenticated:n,error:o}=A(),a=react.useCallback(async p=>s.signUp(p),[s]),i=react.useCallback(async p=>s.signInWithPassword(p),[s]),f=react.useCallback(async p=>{await s.signInWithOAuth(p);},[s]),l=react.useCallback(async p=>s.signInWithMagicLink(p),[s]),h=react.useCallback(async()=>s.signOut(),[s]),u=react.useCallback(async()=>s.refreshSession(),[s]);return {session:e,user:t,isLoading:r,isAuthenticated:n,error:o,signUp:a,signInWithPassword:i,signInWithOAuth:f,signInWithMagicLink:l,signOut:h,refreshSession:u,client:s}}function Q(){let{user:s}=A();return s}function X(){let{session:s}=A();return s}function Z(){let{isAuthenticated:s}=A();return s}function ee(){let s=w(),[e,t]=react.useState(false),[r,n]=react.useState(null),o=react.useCallback(async(i,f)=>{t(true),n(null);try{return await s.resetPasswordForEmail({email:i,redirectTo:f})}catch(l){throw n(l),l}finally{t(false);}},[s]),a=react.useCallback(async(i,f)=>{t(true),n(null);try{return await s.updatePassword({currentPassword:i,newPassword:f})}catch(l){throw n(l),l}finally{t(false);}},[s]);return {resetPassword:o,updatePassword:a,isLoading:e,error:r}}function te(){let s=w(),[e,t]=react.useState([]),[r,n]=react.useState(false),[o,a]=react.useState(null),i=react.useCallback(async()=>{n(true);try{let u=await s.listMFAFactors();t(u);}catch(u){a(u);}finally{n(false);}},[s]),f=react.useCallback(async u=>s.enrollMFA({type:"totp",friendlyName:u}),[s]),l=react.useCallback(async(u,p)=>{let S=await s.verifyMFA({factorId:u,code:p});return await i(),S},[s,i]),h=react.useCallback(async(u,p)=>{let S=await s.unenrollMFA(u,p);return await i(),S},[s,i]);return react.useEffect(()=>{i();},[i]),{factors:e,isLoading:r,error:o,enrollTOTP:f,verifyMFA:l,unenroll:h,refresh:i}}function se(){let s=w(),[e,t]=react.useState([]),[r,n]=react.useState(true),[o,a]=react.useState(null),i=react.useCallback(async()=>{n(true);try{let h=await s.getUserIdentities();t(h);}catch(h){a(h);}finally{n(false);}},[s]),f=react.useCallback(async(h,u)=>s.linkIdentity(h,{redirectTo:u}),[s]),l=react.useCallback(async h=>{let u=await s.unlinkIdentity(h);return await i(),u},[s,i]);return react.useEffect(()=>{i();},[i]),{identities:e,isLoading:r,error:o,linkIdentity:f,unlinkIdentity:l,refresh:i}}function re(){let s=w(),[e,t]=react.useState([]),[r,n]=react.useState(true),[o,a]=react.useState(null),i=react.useCallback(async()=>{n(true);try{let h=await s.listSessions();t(h);}catch(h){a(h);}finally{n(false);}},[s]),f=react.useCallback(async h=>{let u=await s.revokeSession(h);return await i(),u},[s,i]),l=react.useCallback(async()=>{let h=await s.revokeOtherSessions();return await i(),h},[s,i]);return react.useEffect(()=>{i();},[i]),{sessions:e,isLoading:r,error:o,revokeSession:f,revokeOtherSessions:l,refresh:i}}function ne(){let s=w(),[e,t]=react.useState(false),[r,n]=react.useState(null);return {signInWithSSO:react.useCallback(async a=>{t(true),n(null);try{return await s.signInWithSSO(a)}catch(i){throw n(i),i}finally{t(false);}},[s]),isLoading:e,error:r}}exports.AuthProvider=j;exports.VaifAuthClient=P;exports.createAuthClient=C;exports.useAuth=Y;exports.useAuthClient=w;exports.useIdentities=se;exports.useIsAuthenticated=Z;exports.useMFA=te;exports.usePassword=ee;exports.useSSO=ne;exports.useSession=X;exports.useSessions=re;exports.useUser=Q;
package/dist/react.mjs CHANGED
@@ -1 +1 @@
1
- import {m}from'./chunk-JF55RF72.mjs';export{l as VaifAuthClient,m as createAuthClient}from'./chunk-JF55RF72.mjs';import {createContext,useState,useEffect,useMemo,useCallback,useContext}from'react';import {jsx}from'react/jsx-runtime';var P=createContext(null);function k({children:t,config:A,client:a,initialSession:f=null}){let[s]=useState(()=>{if(a)return a;if(!A)throw new Error("AuthProvider requires either config or client prop");return m(A)}),[c,d]=useState(f),[e,h]=useState(!f),[o,n]=useState(null);useEffect(()=>{let u=true;s.initialize().then(p=>{u&&(d(p),h(false));}).catch(p=>{u&&(n(p),h(false));});let{unsubscribe:g}=s.onAuthStateChange(p=>{u&&(d(p.session),n(null));});return ()=>{u=false,g();}},[s]);let r=useMemo(()=>({client:s,session:c,user:c?.user??null,isLoading:e,isAuthenticated:!!c,error:o}),[s,c,e,o]);return jsx(P.Provider,{value:r,children:t})}function y(){let t=useContext(P);if(!t)throw new Error("useAuth must be used within an AuthProvider");return t}function S(){let{client:t}=y();return t}function L(){let{client:t,session:A,user:a,isLoading:f,isAuthenticated:s,error:c}=y(),d=useCallback(async u=>t.signUp(u),[t]),e=useCallback(async u=>t.signInWithPassword(u),[t]),h=useCallback(async u=>{await t.signInWithOAuth(u);},[t]),o=useCallback(async u=>t.signInWithMagicLink(u),[t]),n=useCallback(async()=>t.signOut(),[t]),r=useCallback(async()=>t.refreshSession(),[t]);return {session:A,user:a,isLoading:f,isAuthenticated:s,error:c,signUp:d,signInWithPassword:e,signInWithOAuth:h,signInWithMagicLink:o,signOut:n,refreshSession:r,client:t}}function b(){let{user:t}=y();return t}function W(){let{session:t}=y();return t}function U(){let{isAuthenticated:t}=y();return t}function T(){let t=S(),[A,a]=useState(false),[f,s]=useState(null),c=useCallback(async(e,h)=>{a(true),s(null);try{return await t.resetPasswordForEmail({email:e,redirectTo:h})}catch(o){throw s(o),o}finally{a(false);}},[t]),d=useCallback(async(e,h)=>{a(true),s(null);try{return await t.updatePassword({currentPassword:e,newPassword:h})}catch(o){throw s(o),o}finally{a(false);}},[t]);return {resetPassword:c,updatePassword:d,isLoading:A,error:f}}function R(){let t=S(),[A,a]=useState([]),[f,s]=useState(false),[c,d]=useState(null),e=useCallback(async()=>{s(true);try{let r=await t.listMFAFactors();a(r);}catch(r){d(r);}finally{s(false);}},[t]),h=useCallback(async r=>t.enrollMFA({type:"totp",friendlyName:r}),[t]),o=useCallback(async(r,u)=>{let g=await t.verifyMFA({factorId:r,code:u});return await e(),g},[t,e]),n=useCallback(async(r,u)=>{let g=await t.unenrollMFA(r,u);return await e(),g},[t,e]);return useEffect(()=>{e();},[e]),{factors:A,isLoading:f,error:c,enrollTOTP:h,verifyMFA:o,unenroll:n,refresh:e}}function V(){let t=S(),[A,a]=useState([]),[f,s]=useState(true),[c,d]=useState(null),e=useCallback(async()=>{s(true);try{let n=await t.getUserIdentities();a(n);}catch(n){d(n);}finally{s(false);}},[t]),h=useCallback(async(n,r)=>t.linkIdentity(n,{redirectTo:r}),[t]),o=useCallback(async n=>{let r=await t.unlinkIdentity(n);return await e(),r},[t,e]);return useEffect(()=>{e();},[e]),{identities:A,isLoading:f,error:c,linkIdentity:h,unlinkIdentity:o,refresh:e}}function N(){let t=S(),[A,a]=useState([]),[f,s]=useState(true),[c,d]=useState(null),e=useCallback(async()=>{s(true);try{let n=await t.listSessions();a(n);}catch(n){d(n);}finally{s(false);}},[t]),h=useCallback(async n=>{let r=await t.revokeSession(n);return await e(),r},[t,e]),o=useCallback(async()=>{let n=await t.revokeOtherSessions();return await e(),n},[t,e]);return useEffect(()=>{e();},[e]),{sessions:A,isLoading:f,error:c,revokeSession:h,revokeOtherSessions:o,refresh:e}}export{k as AuthProvider,L as useAuth,S as useAuthClient,V as useIdentities,U as useIsAuthenticated,R as useMFA,T as usePassword,W as useSession,N as useSessions,b as useUser};
1
+ import {o}from'./chunk-VKUCT6DW.mjs';export{n as VaifAuthClient,o as createAuthClient}from'./chunk-VKUCT6DW.mjs';import {createContext,useState,useEffect,useMemo,useCallback,useContext}from'react';import {jsx}from'react/jsx-runtime';var C=createContext(null);function M({children:t,config:h,client:o$1,initialSession:g=null}){let[e]=useState(()=>{if(o$1)return o$1;if(!h)throw new Error("AuthProvider requires either config or client prop");return o(h)}),[c,d]=useState(g),[n,A]=useState(!g),[u,s]=useState(null);useEffect(()=>{let l=true;e.initialize().then(p=>{l&&(d(p),A(false));}).catch(p=>{l&&(s(p),A(false));});let{unsubscribe:f}=e.onAuthStateChange(p=>{l&&(d(p.session),s(null));});return ()=>{l=false,f();}},[e]);let r=useMemo(()=>({client:e,session:c,user:c?.user??null,isLoading:n,isAuthenticated:!!c,error:u}),[e,c,n,u]);return jsx(C.Provider,{value:r,children:t})}function y(){let t=useContext(C);if(!t)throw new Error("useAuth must be used within an AuthProvider");return t}function S(){let{client:t}=y();return t}function W(){let{client:t,session:h,user:o,isLoading:g,isAuthenticated:e,error:c}=y(),d=useCallback(async l=>t.signUp(l),[t]),n=useCallback(async l=>t.signInWithPassword(l),[t]),A=useCallback(async l=>{await t.signInWithOAuth(l);},[t]),u=useCallback(async l=>t.signInWithMagicLink(l),[t]),s=useCallback(async()=>t.signOut(),[t]),r=useCallback(async()=>t.refreshSession(),[t]);return {session:h,user:o,isLoading:g,isAuthenticated:e,error:c,signUp:d,signInWithPassword:n,signInWithOAuth:A,signInWithMagicLink:u,signOut:s,refreshSession:r,client:t}}function k(){let{user:t}=y();return t}function b(){let{session:t}=y();return t}function T(){let{isAuthenticated:t}=y();return t}function U(){let t=S(),[h,o]=useState(false),[g,e]=useState(null),c=useCallback(async(n,A)=>{o(true),e(null);try{return await t.resetPasswordForEmail({email:n,redirectTo:A})}catch(u){throw e(u),u}finally{o(false);}},[t]),d=useCallback(async(n,A)=>{o(true),e(null);try{return await t.updatePassword({currentPassword:n,newPassword:A})}catch(u){throw e(u),u}finally{o(false);}},[t]);return {resetPassword:c,updatePassword:d,isLoading:h,error:g}}function R(){let t=S(),[h,o]=useState([]),[g,e]=useState(false),[c,d]=useState(null),n=useCallback(async()=>{e(true);try{let r=await t.listMFAFactors();o(r);}catch(r){d(r);}finally{e(false);}},[t]),A=useCallback(async r=>t.enrollMFA({type:"totp",friendlyName:r}),[t]),u=useCallback(async(r,l)=>{let f=await t.verifyMFA({factorId:r,code:l});return await n(),f},[t,n]),s=useCallback(async(r,l)=>{let f=await t.unenrollMFA(r,l);return await n(),f},[t,n]);return useEffect(()=>{n();},[n]),{factors:h,isLoading:g,error:c,enrollTOTP:A,verifyMFA:u,unenroll:s,refresh:n}}function V(){let t=S(),[h,o]=useState([]),[g,e]=useState(true),[c,d]=useState(null),n=useCallback(async()=>{e(true);try{let s=await t.getUserIdentities();o(s);}catch(s){d(s);}finally{e(false);}},[t]),A=useCallback(async(s,r)=>t.linkIdentity(s,{redirectTo:r}),[t]),u=useCallback(async s=>{let r=await t.unlinkIdentity(s);return await n(),r},[t,n]);return useEffect(()=>{n();},[n]),{identities:h,isLoading:g,error:c,linkIdentity:A,unlinkIdentity:u,refresh:n}}function N(){let t=S(),[h,o]=useState([]),[g,e]=useState(true),[c,d]=useState(null),n=useCallback(async()=>{e(true);try{let s=await t.listSessions();o(s);}catch(s){d(s);}finally{e(false);}},[t]),A=useCallback(async s=>{let r=await t.revokeSession(s);return await n(),r},[t,n]),u=useCallback(async()=>{let s=await t.revokeOtherSessions();return await n(),s},[t,n]);return useEffect(()=>{n();},[n]),{sessions:h,isLoading:g,error:c,revokeSession:A,revokeOtherSessions:u,refresh:n}}function q(){let t=S(),[h,o]=useState(false),[g,e]=useState(null);return {signInWithSSO:useCallback(async d=>{o(true),e(null);try{return await t.signInWithSSO(d)}catch(n){throw e(n),n}finally{o(false);}},[t]),isLoading:h,error:g}}export{M as AuthProvider,W as useAuth,S as useAuthClient,V as useIdentities,T as useIsAuthenticated,R as useMFA,U as usePassword,q as useSSO,b as useSession,N as useSessions,k as useUser};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vaiftech/auth",
3
- "version": "1.0.4",
3
+ "version": "1.1.0",
4
4
  "description": "Comprehensive authentication SDK for VAIF - Session management, OAuth, MFA, and more",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -1 +0,0 @@
1
- function l(i){return "mfaRequired"in i&&i.mfaRequired===true}var a=class i extends Error{constructor(e,t,s,r){super(e),this.name="AuthError",this.code=t,this.status=s,this.details=r;}static isAuthError(e){return e instanceof i}},m=class extends a{constructor(e="Session has expired"){super(e,"session_expired",401),this.name="SessionExpiredError";}},S=class extends a{constructor(e="Invalid email or password"){super(e,"invalid_credentials",401),this.name="InvalidCredentialsError";}};function n(){return typeof window<"u"&&typeof window.document<"u"}function y(){if(!n())return false;try{let i="__vaif_test__";return window.localStorage.setItem(i,i),window.localStorage.removeItem(i),!0}catch{return false}}var u=class{constructor(){this.storage=new Map;}getItem(e){return this.storage.get(e)??null}setItem(e,t){this.storage.set(e,t);}removeItem(e){this.storage.delete(e);}clear(){this.storage.clear();}},c=class{getItem(e){if(!n())return null;try{return window.localStorage.getItem(e)}catch{return null}}setItem(e,t){if(n())try{window.localStorage.setItem(e,t);}catch{}}removeItem(e){if(n())try{window.localStorage.removeItem(e);}catch{}}},p=class{getItem(e){if(!n())return null;try{return window.sessionStorage.getItem(e)}catch{return null}}setItem(e,t){if(n())try{window.sessionStorage.setItem(e,t);}catch{}}removeItem(e){if(n())try{window.sessionStorage.removeItem(e);}catch{}}},g=class{constructor(e){this.options={secure:n()&&window.location.protocol==="https:",sameSite:"lax",path:"/",...e};}getItem(e){if(!n())return null;try{let t=document.cookie.split(";");for(let s of t){let[r,o]=s.trim().split("=");if(r===e)return decodeURIComponent(o)}return null}catch{return null}}setItem(e,t){if(n())try{let s=`${e}=${encodeURIComponent(t)}`;s+=`; path=${this.options.path}`,s+=`; samesite=${this.options.sameSite}`,this.options.secure&&(s+="; secure"),this.options.domain&&(s+=`; domain=${this.options.domain}`),this.options.maxAge&&(s+=`; max-age=${this.options.maxAge}`),document.cookie=s;}catch{}}removeItem(e){if(n())try{document.cookie=`${e}=; path=${this.options.path}; expires=Thu, 01 Jan 1970 00:00:00 GMT`;}catch{}}};function _(){return y()?new c:new u}var d=class{constructor(e,t="vaif.auth."){this.adapter=e,this.keyPrefix=t;}key(e){return `${this.keyPrefix}${e}`}async getSession(){try{let e=await this.adapter.getItem(this.key("session"));if(!e)return null;let t=JSON.parse(e);return t.expiresAt&&t.expiresAt<Date.now()?(await this.removeSession(),null):t}catch{return null}}async setSession(e){try{await this.adapter.setItem(this.key("session"),JSON.stringify(e));}catch{}}async removeSession(){try{await this.adapter.removeItem(this.key("session"));}catch{}}async getRefreshToken(){try{return await this.adapter.getItem(this.key("refresh_token"))}catch{return null}}async setRefreshToken(e){try{await this.adapter.setItem(this.key("refresh_token"),e);}catch{}}async removeRefreshToken(){try{await this.adapter.removeItem(this.key("refresh_token"));}catch{}}async clear(){await this.removeSession(),await this.removeRefreshToken();}},A=new u,P=y()?new c:A,T=n()?new p:A,I=i=>new g(i);var k={storageKey:"vaif.auth.",autoRefreshToken:true,persistSession:true,detectSessionInUrl:true,flowType:"implicit",debug:false},v=60*1e3,f=class{constructor(e){this.refreshTimer=null;this.listeners=new Set;this.initialized=false;this.initPromise=null;this.currentSession=null;this.config={...k,...e,headers:e.headers||{},storage:e.storage||_()},this.storage=new d(this.config.storage,this.config.storageKey);}async initialize(){return this.initPromise?this.initPromise.then(()=>this.currentSession):(this.initPromise=this._initialize(),await this.initPromise,this.currentSession)}async _initialize(){if(!this.initialized){if(this.config.detectSessionInUrl&&n()){let e=await this._handleUrlSession();if(e){this.currentSession=e,await this._persistSession(e),this._notifyListeners("SIGNED_IN",e),this._setupAutoRefresh(e),this.initialized=true;return}}if(this.config.persistSession){let e=await this.storage.getSession();e&&(this.currentSession=e,this._notifyListeners("INITIAL_SESSION",e),this._setupAutoRefresh(e));}this.initialized=true;}}async getSession(){return await this.initialize(),this.currentSession}async getUser(){return (await this.getSession())?.user??null}async setSession(e,t){let s=await this._fetchUser(e),r={accessToken:e,refreshToken:t,expiresAt:Date.now()+3600*1e3,expiresIn:3600,tokenType:"bearer",user:s};return this.currentSession=r,await this._persistSession(r),this._notifyListeners("SIGNED_IN",r),this._setupAutoRefresh(r),{session:r,user:s}}async refreshSession(){let e=await this.getSession();if(!e?.refreshToken)return null;try{let t=await this._refreshToken(e.refreshToken),s={...e,accessToken:t.accessToken,refreshToken:t.refreshToken||e.refreshToken,expiresAt:t.expiresAt,expiresIn:t.expiresIn};return this.currentSession=s,await this._persistSession(s),this._notifyListeners("TOKEN_REFRESHED",s),this._setupAutoRefresh(s),s}catch(t){return this._log("Failed to refresh session:",t),await this.signOut(),null}}async signUp(e){let t=await this._fetch("/auth/signup",{method:"POST",body:JSON.stringify({email:e.email,password:e.password,name:e.name,phone:e.phone,metadata:e.metadata,redirectUrl:e.redirectUrl||e.emailRedirectTo})});return l(t)||(this.currentSession=t.session,await this._persistSession(t.session),this._notifyListeners("SIGNED_IN",t.session),this._setupAutoRefresh(t.session)),t}async signInWithPassword(e){let t=await this._fetch("/auth/login",{method:"POST",body:JSON.stringify({email:e.email,password:e.password,mfaCode:e.mfaCode,rememberMe:e.rememberMe})});return l(t)||(this.currentSession=t.session,await this._persistSession(t.session),this._notifyListeners("SIGNED_IN",t.session),this._setupAutoRefresh(t.session)),t}async signInWithOAuth(e){let t=await this._fetch("/auth/oauth/authorize",{method:"POST",body:JSON.stringify({provider:e.provider,redirectUrl:e.redirectTo,scopes:e.scopes,queryParams:e.queryParams,flowType:this.config.flowType})});return n()&&(window.location.href=t.url),t}async signInWithMagicLink(e){return this._fetch("/auth/magic-link/send",{method:"POST",body:JSON.stringify({email:e.email,redirectUrl:e.redirectTo,shouldCreateUser:e.shouldCreateUser})})}async signInWithOTP(e){let t=e.phone?"/auth/phone/send":"/auth/otp/send";return this._fetch(t,{method:"POST",body:JSON.stringify({email:e.email,phone:e.phone,channel:e.channel,shouldCreateUser:e.shouldCreateUser})})}async verifyOTP(e){let t=await this._fetch("/auth/otp/verify",{method:"POST",body:JSON.stringify({email:e.email,phone:e.phone,token:e.token,type:e.type})});return this.currentSession=t.session,await this._persistSession(t.session),this._notifyListeners("SIGNED_IN",t.session),this._setupAutoRefresh(t.session),t}async signInAnonymously(e){let t=await this._fetch("/auth/anonymous",{method:"POST",body:JSON.stringify({metadata:e?.metadata})});return this.currentSession=t.session,await this._persistSession(t.session),this._notifyListeners("SIGNED_IN",t.session),this._setupAutoRefresh(t.session),t}async signOut(){try{this.currentSession&&await this._fetch("/auth/logout",{method:"POST"});}catch{}this._clearRefreshTimer(),this.currentSession=null,await this.storage.clear(),this._notifyListeners("SIGNED_OUT",null);}async signOutAll(){try{await this._fetch("/auth/logout-all",{method:"POST"});}catch{}this._clearRefreshTimer(),this.currentSession=null,await this.storage.clear(),this._notifyListeners("SIGNED_OUT",null);}async resetPasswordForEmail(e){return this._fetch("/auth/forgot-password",{method:"POST",body:JSON.stringify({email:e.email,redirectUrl:e.redirectTo})})}async updatePassword(e){return this._fetch("/users/me/change-password",{method:"POST",body:JSON.stringify({currentPassword:e.currentPassword,newPassword:e.newPassword})})}async setPassword(e){let t=await this._fetch("/auth/reset-password",{method:"POST",body:JSON.stringify({token:e.token,password:e.password})});return this.currentSession=t.session,await this._persistSession(t.session),this._notifyListeners("PASSWORD_RECOVERY",t.session),this._setupAutoRefresh(t.session),t}async updateUser(e){let t=await this._fetch("/users/me",{method:"PATCH",body:JSON.stringify(e)});return this.currentSession&&(this.currentSession={...this.currentSession,user:t.user},await this._persistSession(this.currentSession),this._notifyListeners("USER_UPDATED",this.currentSession)),t.user}async getUserIdentities(){return (await this._fetch("/auth/oauth/providers")).identities}async linkIdentity(e,t){let s=await this._fetch("/auth/oauth/link",{method:"POST",body:JSON.stringify({provider:e,redirectUrl:t?.redirectTo})});return n()&&(window.location.href=s.url),s}async unlinkIdentity(e){return this._fetch(`/auth/oauth/unlink/${e}`,{method:"POST"})}async listMFAFactors(){return (await this._fetch("/auth/mfa/factors")).factors}async enrollMFA(e){return this._fetch("/auth/mfa/setup",{method:"POST",body:JSON.stringify({method:e.type,friendlyName:e.friendlyName})})}async verifyMFA(e){return this._fetch("/auth/mfa/enable",{method:"POST",body:JSON.stringify({factorId:e.factorId,code:e.code})})}async challengeMFA(e){return this._fetch("/auth/mfa/challenge",{method:"POST",body:JSON.stringify({factorId:e.factorId})})}async verifyMFAChallenge(e,t){let s=await this._fetch("/auth/mfa/verify",{method:"POST",body:JSON.stringify({mfaToken:e,code:t})});return this.currentSession=s.session,await this._persistSession(s.session),this._notifyListeners("MFA_CHALLENGE_VERIFIED",s.session),this._setupAutoRefresh(s.session),s}async unenrollMFA(e,t){return this._fetch("/auth/mfa/disable",{method:"POST",body:JSON.stringify({factorId:e,code:t})})}async regenerateBackupCodes(){return this._fetch("/auth/mfa/backup-codes",{method:"POST"})}async listSessions(){return (await this._fetch("/auth/sessions")).sessions}async revokeSession(e){return this._fetch(`/auth/sessions/${e}`,{method:"DELETE"})}async revokeOtherSessions(){return this._fetch("/auth/sessions/revoke-others",{method:"POST"})}async resendEmailVerification(e){return this._fetch("/auth/verify-email/send",{method:"POST",body:JSON.stringify({redirectUrl:e?.redirectTo})})}async verifyEmail(e){let t=await this._fetch("/auth/verify-email/confirm",{method:"POST",body:JSON.stringify({token:e})});return this.currentSession&&(this.currentSession=t.session,await this._persistSession(t.session),this._notifyListeners("USER_UPDATED",t.session)),t}onAuthStateChange(e){return this.listeners.add(e),this.initialized&&e({event:this.currentSession?"INITIAL_SESSION":"SIGNED_OUT",session:this.currentSession}),{unsubscribe:()=>{this.listeners.delete(e);}}}async _fetch(e,t={}){let s=`${this.config.url}${e}`,r={"Content-Type":"application/json",...this.config.headers};this.config.apiKey&&(r["x-vaif-key"]=this.config.apiKey),this.currentSession?.accessToken&&(r.Authorization=`Bearer ${this.currentSession.accessToken}`);let o=await fetch(s,{...t,headers:r});if(!o.ok){let h=await o.json().catch(()=>({}));throw new a(h.message||"Request failed",this._mapErrorCode(o.status,h.code),o.status,h)}return o.json()}async _fetchUser(e){let t=await fetch(`${this.config.url}/auth/me`,{headers:{Authorization:`Bearer ${e}`,...this.config.headers}});if(!t.ok)throw new a("Failed to fetch user","invalid_token",t.status);return (await t.json()).user}async _refreshToken(e){let t=await fetch(`${this.config.url}/auth/refresh`,{method:"POST",headers:{"Content-Type":"application/json",...this.config.headers},body:JSON.stringify({refreshToken:e})});if(!t.ok)throw new a("Failed to refresh token","token_expired",t.status);return t.json()}async _handleUrlSession(){if(!n())return null;let e=new URLSearchParams(window.location.hash.substring(1)),t=new URLSearchParams(window.location.search),s=e.get("access_token")||t.get("access_token"),r=e.get("refresh_token")||t.get("refresh_token"),o=e.get("expires_in")||t.get("expires_in");if(!s)return null;try{let h=await this._fetchUser(s),w={accessToken:s,refreshToken:r||void 0,expiresAt:Date.now()+(o?parseInt(o,10)*1e3:36e5),expiresIn:o?parseInt(o,10):3600,tokenType:"bearer",user:h};return window.history.replaceState(null,"",window.location.pathname),w}catch(h){return this._log("Failed to handle URL session:",h),null}}async _persistSession(e){this.config.persistSession&&(await this.storage.setSession(e),e.refreshToken&&await this.storage.setRefreshToken(e.refreshToken));}_setupAutoRefresh(e){if(!this.config.autoRefreshToken||!e.refreshToken)return;this._clearRefreshTimer();let t=e.expiresAt,s=Date.now(),r=Math.max(0,t-s-v);this._log(`Setting up auto refresh in ${Math.round(r/1e3)}s`),this.refreshTimer=setTimeout(()=>{this.refreshSession();},r);}_clearRefreshTimer(){this.refreshTimer&&(clearTimeout(this.refreshTimer),this.refreshTimer=null);}_notifyListeners(e,t){let s={event:e,session:t};this.listeners.forEach(r=>{try{r(s);}catch(o){this._log("Error in auth state change listener:",o);}});}_mapErrorCode(e,t){if(t){let s={invalid_credentials:"invalid_credentials",user_not_found:"user_not_found",user_already_exists:"user_already_exists",email_not_verified:"email_not_verified",phone_not_verified:"phone_not_verified",invalid_token:"invalid_token",token_expired:"token_expired",mfa_required:"mfa_required",mfa_invalid:"mfa_invalid",rate_limited:"rate_limited",weak_password:"weak_password",invalid_email:"invalid_email",invalid_phone:"invalid_phone"};if(s[t])return s[t]}switch(e){case 401:return "invalid_credentials";case 403:return "token_expired";case 404:return "user_not_found";case 409:return "user_already_exists";case 429:return "rate_limited";default:return "unknown_error"}}_log(...e){this.config.debug&&console.log("[VaifAuth]",...e);}};function C(i){return new f(i)}export{l as a,a as b,m as c,S as d,n as e,_ as f,d as g,A as h,P as i,T as j,I as k,f as l,C as m};