@pixygon/auth 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,1037 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ AuthContext: () => AuthContext,
24
+ AuthProvider: () => AuthProvider,
25
+ createAuthApiClient: () => createAuthApiClient,
26
+ createTokenStorage: () => createTokenStorage,
27
+ useAuth: () => useAuth,
28
+ useAuthContext: () => useAuthContext,
29
+ useAuthError: () => useAuthError,
30
+ useAuthStatus: () => useAuthStatus,
31
+ useProfileSync: () => useProfileSync,
32
+ useRequireAuth: () => useRequireAuth,
33
+ useToken: () => useToken,
34
+ useUser: () => useUser
35
+ });
36
+ module.exports = __toCommonJS(index_exports);
37
+
38
+ // src/providers/AuthProvider.tsx
39
+ var import_react = require("react");
40
+
41
+ // src/utils/storage.ts
42
+ var getKeys = (appId) => ({
43
+ ACCESS_TOKEN: `${appId}_access_token`,
44
+ REFRESH_TOKEN: `${appId}_refresh_token`,
45
+ USER: `${appId}_user`,
46
+ EXPIRES_AT: `${appId}_expires_at`
47
+ });
48
+ var defaultStorage = {
49
+ getItem: (key) => {
50
+ if (typeof window === "undefined") return null;
51
+ return localStorage.getItem(key);
52
+ },
53
+ setItem: (key, value) => {
54
+ if (typeof window === "undefined") return;
55
+ localStorage.setItem(key, value);
56
+ },
57
+ removeItem: (key) => {
58
+ if (typeof window === "undefined") return;
59
+ localStorage.removeItem(key);
60
+ }
61
+ };
62
+ function createTokenStorage(appId, storage = defaultStorage) {
63
+ const keys = getKeys(appId);
64
+ return {
65
+ /**
66
+ * Get the stored access token
67
+ */
68
+ getAccessToken: async () => {
69
+ const token = await storage.getItem(keys.ACCESS_TOKEN);
70
+ return token;
71
+ },
72
+ /**
73
+ * Get the stored refresh token
74
+ */
75
+ getRefreshToken: async () => {
76
+ const token = await storage.getItem(keys.REFRESH_TOKEN);
77
+ return token;
78
+ },
79
+ /**
80
+ * Get the stored user
81
+ */
82
+ getUser: async () => {
83
+ const userJson = await storage.getItem(keys.USER);
84
+ if (!userJson) return null;
85
+ try {
86
+ return JSON.parse(userJson);
87
+ } catch {
88
+ return null;
89
+ }
90
+ },
91
+ /**
92
+ * Get token expiration time
93
+ */
94
+ getExpiresAt: async () => {
95
+ const expiresAt = await storage.getItem(keys.EXPIRES_AT);
96
+ return expiresAt ? parseInt(expiresAt, 10) : null;
97
+ },
98
+ /**
99
+ * Check if access token is expired
100
+ */
101
+ isTokenExpired: async () => {
102
+ const expiresAt = await storage.getItem(keys.EXPIRES_AT);
103
+ if (!expiresAt) return true;
104
+ return Date.now() >= parseInt(expiresAt, 10);
105
+ },
106
+ /**
107
+ * Check if token will expire within threshold (in seconds)
108
+ */
109
+ willExpireSoon: async (thresholdSeconds = 300) => {
110
+ const expiresAt = await storage.getItem(keys.EXPIRES_AT);
111
+ if (!expiresAt) return true;
112
+ const expiryTime = parseInt(expiresAt, 10);
113
+ const thresholdMs = thresholdSeconds * 1e3;
114
+ return Date.now() >= expiryTime - thresholdMs;
115
+ },
116
+ /**
117
+ * Store tokens and user data
118
+ */
119
+ setTokens: async (accessToken, refreshToken, expiresIn, user) => {
120
+ await storage.setItem(keys.ACCESS_TOKEN, accessToken);
121
+ if (refreshToken) {
122
+ await storage.setItem(keys.REFRESH_TOKEN, refreshToken);
123
+ }
124
+ if (expiresIn) {
125
+ const expiresAt = Date.now() + expiresIn * 1e3;
126
+ await storage.setItem(keys.EXPIRES_AT, expiresAt.toString());
127
+ }
128
+ await storage.setItem(keys.USER, JSON.stringify(user));
129
+ },
130
+ /**
131
+ * Update tokens after refresh
132
+ */
133
+ updateTokens: async (tokens) => {
134
+ await storage.setItem(keys.ACCESS_TOKEN, tokens.accessToken);
135
+ await storage.setItem(keys.REFRESH_TOKEN, tokens.refreshToken);
136
+ const expiresAt = Date.now() + tokens.expiresIn * 1e3;
137
+ await storage.setItem(keys.EXPIRES_AT, expiresAt.toString());
138
+ },
139
+ /**
140
+ * Update user data
141
+ */
142
+ updateUser: async (user) => {
143
+ await storage.setItem(keys.USER, JSON.stringify(user));
144
+ },
145
+ /**
146
+ * Clear all stored auth data
147
+ */
148
+ clear: async () => {
149
+ await storage.removeItem(keys.ACCESS_TOKEN);
150
+ await storage.removeItem(keys.REFRESH_TOKEN);
151
+ await storage.removeItem(keys.USER);
152
+ await storage.removeItem(keys.EXPIRES_AT);
153
+ },
154
+ /**
155
+ * Get all stored auth data
156
+ */
157
+ getAll: async () => {
158
+ const [accessToken, refreshToken, user, expiresAt] = await Promise.all([
159
+ storage.getItem(keys.ACCESS_TOKEN),
160
+ storage.getItem(keys.REFRESH_TOKEN),
161
+ storage.getItem(keys.USER),
162
+ storage.getItem(keys.EXPIRES_AT)
163
+ ]);
164
+ return {
165
+ accessToken,
166
+ refreshToken,
167
+ user: user ? JSON.parse(user) : null,
168
+ expiresAt: expiresAt ? parseInt(expiresAt, 10) : null
169
+ };
170
+ }
171
+ };
172
+ }
173
+
174
+ // src/api/client.ts
175
+ function parseErrorCode(status, message) {
176
+ if (message?.toLowerCase().includes("not found")) return "USER_NOT_FOUND";
177
+ if (message?.toLowerCase().includes("exists")) return "USER_EXISTS";
178
+ if (message?.toLowerCase().includes("verified")) return "EMAIL_NOT_VERIFIED";
179
+ if (message?.toLowerCase().includes("invalid") && message?.toLowerCase().includes("code")) {
180
+ return "INVALID_VERIFICATION_CODE";
181
+ }
182
+ if (message?.toLowerCase().includes("expired") && message?.toLowerCase().includes("code")) {
183
+ return "EXPIRED_VERIFICATION_CODE";
184
+ }
185
+ switch (status) {
186
+ case 401:
187
+ return "INVALID_CREDENTIALS";
188
+ case 403:
189
+ return "TOKEN_INVALID";
190
+ case 404:
191
+ return "USER_NOT_FOUND";
192
+ case 409:
193
+ return "USER_EXISTS";
194
+ case 500:
195
+ return "SERVER_ERROR";
196
+ default:
197
+ return "UNKNOWN_ERROR";
198
+ }
199
+ }
200
+ function createAuthError(status, message, details) {
201
+ return {
202
+ code: parseErrorCode(status, message),
203
+ message: message || "An unexpected error occurred",
204
+ details
205
+ };
206
+ }
207
+ function createAuthApiClient(config, tokenStorage) {
208
+ let currentAccessToken = null;
209
+ let isRefreshing = false;
210
+ let refreshPromise = null;
211
+ const log = (...args) => {
212
+ if (config.debug) {
213
+ console.log("[PixygonAuth]", ...args);
214
+ }
215
+ };
216
+ async function request(endpoint, options = {}) {
217
+ const url = `${config.baseUrl}${endpoint}`;
218
+ const headers = {
219
+ "Content-Type": "application/json",
220
+ ...options.headers
221
+ };
222
+ if (currentAccessToken) {
223
+ headers["Authorization"] = `Bearer ${currentAccessToken}`;
224
+ }
225
+ log(`Request: ${options.method || "GET"} ${endpoint}`);
226
+ try {
227
+ const response = await fetch(url, {
228
+ ...options,
229
+ headers
230
+ });
231
+ if (response.status === 401 || response.status === 403) {
232
+ if (currentAccessToken && !isRefreshing) {
233
+ log("Token expired, attempting refresh...");
234
+ try {
235
+ await refreshTokens();
236
+ headers["Authorization"] = `Bearer ${currentAccessToken}`;
237
+ const retryResponse = await fetch(url, { ...options, headers });
238
+ if (!retryResponse.ok) {
239
+ const errorData = await retryResponse.json().catch(() => ({}));
240
+ throw createAuthError(retryResponse.status, errorData.message);
241
+ }
242
+ return retryResponse.json();
243
+ } catch (refreshError) {
244
+ log("Token refresh failed", refreshError);
245
+ throw createAuthError(401, "Session expired. Please log in again.");
246
+ }
247
+ }
248
+ throw createAuthError(response.status, "Authentication required");
249
+ }
250
+ if (!response.ok) {
251
+ const errorData = await response.json().catch(() => ({}));
252
+ throw createAuthError(response.status, errorData.message, errorData);
253
+ }
254
+ const data = await response.json();
255
+ return data;
256
+ } catch (error) {
257
+ if (error.code) {
258
+ throw error;
259
+ }
260
+ log("Network error:", error);
261
+ throw {
262
+ code: "NETWORK_ERROR",
263
+ message: "Unable to connect to the server",
264
+ details: { originalError: error }
265
+ };
266
+ }
267
+ }
268
+ async function refreshTokens() {
269
+ if (isRefreshing && refreshPromise) {
270
+ return refreshPromise;
271
+ }
272
+ isRefreshing = true;
273
+ refreshPromise = (async () => {
274
+ try {
275
+ const refreshToken = await tokenStorage.getRefreshToken();
276
+ if (!refreshToken) {
277
+ throw new Error("No refresh token available");
278
+ }
279
+ const response = await fetch(`${config.baseUrl}/auth/refresh`, {
280
+ method: "POST",
281
+ headers: { "Content-Type": "application/json" },
282
+ body: JSON.stringify({ refreshToken })
283
+ });
284
+ if (!response.ok) {
285
+ throw new Error("Refresh failed");
286
+ }
287
+ const data = await response.json();
288
+ currentAccessToken = data.token;
289
+ await tokenStorage.updateTokens({
290
+ accessToken: data.token,
291
+ refreshToken: data.refreshToken,
292
+ expiresIn: data.expiresIn,
293
+ refreshExpiresIn: data.expiresIn * 24
294
+ // Approximate
295
+ });
296
+ config.onTokenRefresh?.({
297
+ accessToken: data.token,
298
+ refreshToken: data.refreshToken,
299
+ expiresIn: data.expiresIn,
300
+ refreshExpiresIn: data.expiresIn * 24
301
+ });
302
+ log("Token refreshed successfully");
303
+ } finally {
304
+ isRefreshing = false;
305
+ refreshPromise = null;
306
+ }
307
+ })();
308
+ return refreshPromise;
309
+ }
310
+ return {
311
+ // ========================================================================
312
+ // Auth Endpoints
313
+ // ========================================================================
314
+ async login(data) {
315
+ const response = await request("/auth/login", {
316
+ method: "POST",
317
+ body: JSON.stringify(data)
318
+ });
319
+ currentAccessToken = response.token;
320
+ await tokenStorage.setTokens(
321
+ response.token,
322
+ response.refreshToken || null,
323
+ response.expiresIn || null,
324
+ response.user
325
+ );
326
+ config.onLogin?.(response.user);
327
+ log("Login successful:", response.user.userName);
328
+ return response;
329
+ },
330
+ async register(data) {
331
+ const response = await request("/auth/register", {
332
+ method: "POST",
333
+ body: JSON.stringify(data)
334
+ });
335
+ log("Registration successful:", response.user.userName);
336
+ return response;
337
+ },
338
+ async verify(data) {
339
+ const response = await request("/auth/verify", {
340
+ method: "POST",
341
+ body: JSON.stringify(data)
342
+ });
343
+ currentAccessToken = response.token;
344
+ await tokenStorage.setTokens(
345
+ response.token,
346
+ response.refreshToken || null,
347
+ null,
348
+ response.user
349
+ );
350
+ config.onLogin?.(response.user);
351
+ log("Verification successful:", response.user.userName);
352
+ return response;
353
+ },
354
+ async resendVerification(data) {
355
+ const response = await request("/auth/resendVerificationEmail", {
356
+ method: "POST",
357
+ body: JSON.stringify(data)
358
+ });
359
+ log("Verification email resent");
360
+ return response;
361
+ },
362
+ async forgotPassword(data) {
363
+ const response = await request("/auth/forgotPassword", {
364
+ method: "POST",
365
+ body: JSON.stringify(data)
366
+ });
367
+ log("Password reset email sent");
368
+ return response;
369
+ },
370
+ async recoverPassword(data) {
371
+ const response = await request("/auth/recoverPassword", {
372
+ method: "POST",
373
+ body: JSON.stringify(data)
374
+ });
375
+ log("Password recovered successfully");
376
+ return response;
377
+ },
378
+ async refreshToken(data) {
379
+ const response = await request("/auth/refresh", {
380
+ method: "POST",
381
+ body: JSON.stringify(data)
382
+ });
383
+ currentAccessToken = response.token;
384
+ await tokenStorage.updateTokens({
385
+ accessToken: response.token,
386
+ refreshToken: response.refreshToken,
387
+ expiresIn: response.expiresIn,
388
+ refreshExpiresIn: response.expiresIn * 24
389
+ });
390
+ return response;
391
+ },
392
+ // ========================================================================
393
+ // User Endpoints
394
+ // ========================================================================
395
+ async getMe() {
396
+ const response = await request("/users/me");
397
+ await tokenStorage.updateUser(response);
398
+ return response;
399
+ },
400
+ async updateProfile(data) {
401
+ const response = await request("/users/me", {
402
+ method: "PATCH",
403
+ body: JSON.stringify(data)
404
+ });
405
+ await tokenStorage.updateUser(response);
406
+ return response;
407
+ },
408
+ // ========================================================================
409
+ // Utilities
410
+ // ========================================================================
411
+ setAccessToken(token) {
412
+ currentAccessToken = token;
413
+ },
414
+ getAccessToken() {
415
+ return currentAccessToken;
416
+ },
417
+ request
418
+ };
419
+ }
420
+
421
+ // src/providers/AuthProvider.tsx
422
+ var import_jsx_runtime = require("react/jsx-runtime");
423
+ var AuthContext = (0, import_react.createContext)(null);
424
+ var defaultConfig = {
425
+ autoRefresh: true,
426
+ refreshThreshold: 300,
427
+ // 5 minutes before expiry
428
+ debug: false
429
+ };
430
+ function AuthProvider({ config: userConfig, children }) {
431
+ const config = (0, import_react.useMemo)(() => ({ ...defaultConfig, ...userConfig }), [userConfig]);
432
+ const tokenStorage = (0, import_react.useMemo)(
433
+ () => createTokenStorage(config.appId, config.storage),
434
+ [config.appId, config.storage]
435
+ );
436
+ const apiClient = (0, import_react.useMemo)(
437
+ () => createAuthApiClient(config, tokenStorage),
438
+ [config, tokenStorage]
439
+ );
440
+ const [state, setState] = (0, import_react.useState)({
441
+ user: null,
442
+ accessToken: null,
443
+ refreshToken: null,
444
+ status: "idle",
445
+ isLoading: true,
446
+ error: null
447
+ });
448
+ const log = (0, import_react.useCallback)(
449
+ (...args) => {
450
+ if (config.debug) {
451
+ console.log("[PixygonAuth]", ...args);
452
+ }
453
+ },
454
+ [config.debug]
455
+ );
456
+ (0, import_react.useEffect)(() => {
457
+ let mounted = true;
458
+ async function initializeAuth() {
459
+ log("Initializing auth...");
460
+ try {
461
+ const stored = await tokenStorage.getAll();
462
+ if (!stored.accessToken || !stored.user) {
463
+ log("No stored auth found");
464
+ if (mounted) {
465
+ setState((prev) => ({
466
+ ...prev,
467
+ status: "unauthenticated",
468
+ isLoading: false
469
+ }));
470
+ }
471
+ return;
472
+ }
473
+ const isExpired = await tokenStorage.isTokenExpired();
474
+ if (isExpired && stored.refreshToken) {
475
+ log("Token expired, attempting refresh...");
476
+ try {
477
+ const response = await apiClient.refreshToken({
478
+ refreshToken: stored.refreshToken
479
+ });
480
+ if (mounted) {
481
+ setState({
482
+ user: stored.user,
483
+ accessToken: response.token,
484
+ refreshToken: response.refreshToken,
485
+ status: "authenticated",
486
+ isLoading: false,
487
+ error: null
488
+ });
489
+ apiClient.setAccessToken(response.token);
490
+ }
491
+ } catch (error) {
492
+ log("Token refresh failed:", error);
493
+ await tokenStorage.clear();
494
+ if (mounted) {
495
+ setState({
496
+ user: null,
497
+ accessToken: null,
498
+ refreshToken: null,
499
+ status: "unauthenticated",
500
+ isLoading: false,
501
+ error: null
502
+ });
503
+ }
504
+ }
505
+ } else if (!isExpired) {
506
+ log("Restoring auth from storage");
507
+ apiClient.setAccessToken(stored.accessToken);
508
+ if (mounted) {
509
+ setState({
510
+ user: stored.user,
511
+ accessToken: stored.accessToken,
512
+ refreshToken: stored.refreshToken,
513
+ status: stored.user.isVerified ? "authenticated" : "verifying",
514
+ isLoading: false,
515
+ error: null
516
+ });
517
+ }
518
+ } else {
519
+ log("Token expired and no refresh token available");
520
+ await tokenStorage.clear();
521
+ if (mounted) {
522
+ setState({
523
+ user: null,
524
+ accessToken: null,
525
+ refreshToken: null,
526
+ status: "unauthenticated",
527
+ isLoading: false,
528
+ error: null
529
+ });
530
+ }
531
+ }
532
+ } catch (error) {
533
+ log("Auth initialization error:", error);
534
+ if (mounted) {
535
+ setState((prev) => ({
536
+ ...prev,
537
+ status: "unauthenticated",
538
+ isLoading: false,
539
+ error: {
540
+ code: "UNKNOWN_ERROR",
541
+ message: "Failed to initialize authentication"
542
+ }
543
+ }));
544
+ }
545
+ }
546
+ }
547
+ initializeAuth();
548
+ return () => {
549
+ mounted = false;
550
+ };
551
+ }, [apiClient, tokenStorage, log]);
552
+ (0, import_react.useEffect)(() => {
553
+ if (!config.autoRefresh || state.status !== "authenticated") {
554
+ return;
555
+ }
556
+ const checkAndRefresh = async () => {
557
+ const willExpire = await tokenStorage.willExpireSoon(config.refreshThreshold || 300);
558
+ if (willExpire && state.refreshToken) {
559
+ log("Token expiring soon, refreshing...");
560
+ try {
561
+ const response = await apiClient.refreshToken({
562
+ refreshToken: state.refreshToken
563
+ });
564
+ setState((prev) => ({
565
+ ...prev,
566
+ accessToken: response.token,
567
+ refreshToken: response.refreshToken
568
+ }));
569
+ apiClient.setAccessToken(response.token);
570
+ } catch (error) {
571
+ log("Auto-refresh failed:", error);
572
+ }
573
+ }
574
+ };
575
+ const interval = setInterval(checkAndRefresh, 6e4);
576
+ return () => clearInterval(interval);
577
+ }, [
578
+ config.autoRefresh,
579
+ config.refreshThreshold,
580
+ state.status,
581
+ state.refreshToken,
582
+ apiClient,
583
+ tokenStorage,
584
+ log
585
+ ]);
586
+ const login = (0, import_react.useCallback)(
587
+ async (credentials) => {
588
+ setState((prev) => ({ ...prev, isLoading: true, error: null }));
589
+ try {
590
+ const response = await apiClient.login(credentials);
591
+ const newStatus = response.user.isVerified ? "authenticated" : "verifying";
592
+ setState({
593
+ user: response.user,
594
+ accessToken: response.token,
595
+ refreshToken: response.refreshToken || null,
596
+ status: newStatus,
597
+ isLoading: false,
598
+ error: null
599
+ });
600
+ return response;
601
+ } catch (error) {
602
+ const authError = error;
603
+ setState((prev) => ({
604
+ ...prev,
605
+ isLoading: false,
606
+ error: authError
607
+ }));
608
+ config.onError?.(authError);
609
+ throw error;
610
+ }
611
+ },
612
+ [apiClient, config]
613
+ );
614
+ const register = (0, import_react.useCallback)(
615
+ async (data) => {
616
+ setState((prev) => ({ ...prev, isLoading: true, error: null }));
617
+ try {
618
+ const response = await apiClient.register(data);
619
+ setState((prev) => ({
620
+ ...prev,
621
+ isLoading: false
622
+ }));
623
+ return response;
624
+ } catch (error) {
625
+ const authError = error;
626
+ setState((prev) => ({
627
+ ...prev,
628
+ isLoading: false,
629
+ error: authError
630
+ }));
631
+ config.onError?.(authError);
632
+ throw error;
633
+ }
634
+ },
635
+ [apiClient, config]
636
+ );
637
+ const verify = (0, import_react.useCallback)(
638
+ async (data) => {
639
+ setState((prev) => ({ ...prev, isLoading: true, error: null }));
640
+ try {
641
+ const response = await apiClient.verify(data);
642
+ setState({
643
+ user: response.user,
644
+ accessToken: response.token,
645
+ refreshToken: response.refreshToken || null,
646
+ status: "authenticated",
647
+ isLoading: false,
648
+ error: null
649
+ });
650
+ return response;
651
+ } catch (error) {
652
+ const authError = error;
653
+ setState((prev) => ({
654
+ ...prev,
655
+ isLoading: false,
656
+ error: authError
657
+ }));
658
+ config.onError?.(authError);
659
+ throw error;
660
+ }
661
+ },
662
+ [apiClient, config]
663
+ );
664
+ const resendVerification = (0, import_react.useCallback)(
665
+ async (data) => {
666
+ return apiClient.resendVerification(data);
667
+ },
668
+ [apiClient]
669
+ );
670
+ const forgotPassword = (0, import_react.useCallback)(
671
+ async (data) => {
672
+ return apiClient.forgotPassword(data);
673
+ },
674
+ [apiClient]
675
+ );
676
+ const recoverPassword = (0, import_react.useCallback)(
677
+ async (data) => {
678
+ return apiClient.recoverPassword(data);
679
+ },
680
+ [apiClient]
681
+ );
682
+ const logout = (0, import_react.useCallback)(async () => {
683
+ log("Logging out...");
684
+ await tokenStorage.clear();
685
+ apiClient.setAccessToken(null);
686
+ setState({
687
+ user: null,
688
+ accessToken: null,
689
+ refreshToken: null,
690
+ status: "unauthenticated",
691
+ isLoading: false,
692
+ error: null
693
+ });
694
+ config.onLogout?.();
695
+ }, [tokenStorage, apiClient, config, log]);
696
+ const refreshTokens = (0, import_react.useCallback)(async () => {
697
+ if (!state.refreshToken) {
698
+ throw new Error("No refresh token available");
699
+ }
700
+ const response = await apiClient.refreshToken({
701
+ refreshToken: state.refreshToken
702
+ });
703
+ setState((prev) => ({
704
+ ...prev,
705
+ accessToken: response.token,
706
+ refreshToken: response.refreshToken
707
+ }));
708
+ }, [apiClient, state.refreshToken]);
709
+ const getAccessToken = (0, import_react.useCallback)(() => state.accessToken, [state.accessToken]);
710
+ const hasRole = (0, import_react.useCallback)(
711
+ (role) => {
712
+ if (!state.user) return false;
713
+ const roles = Array.isArray(role) ? role : [role];
714
+ return roles.includes(state.user.role);
715
+ },
716
+ [state.user]
717
+ );
718
+ const contextValue = (0, import_react.useMemo)(
719
+ () => ({
720
+ // State
721
+ ...state,
722
+ isAuthenticated: state.status === "authenticated",
723
+ isVerified: state.user?.isVerified ?? false,
724
+ // Actions
725
+ login,
726
+ register,
727
+ logout,
728
+ verify,
729
+ resendVerification,
730
+ forgotPassword,
731
+ recoverPassword,
732
+ refreshTokens,
733
+ // Utilities
734
+ getAccessToken,
735
+ hasRole,
736
+ // Config
737
+ config
738
+ }),
739
+ [
740
+ state,
741
+ login,
742
+ register,
743
+ logout,
744
+ verify,
745
+ resendVerification,
746
+ forgotPassword,
747
+ recoverPassword,
748
+ refreshTokens,
749
+ getAccessToken,
750
+ hasRole,
751
+ config
752
+ ]
753
+ );
754
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AuthContext.Provider, { value: contextValue, children });
755
+ }
756
+ function useAuthContext() {
757
+ const context = (0, import_react.useContext)(AuthContext);
758
+ if (!context) {
759
+ throw new Error("useAuthContext must be used within an AuthProvider");
760
+ }
761
+ return context;
762
+ }
763
+
764
+ // src/hooks/index.ts
765
+ var import_react3 = require("react");
766
+
767
+ // src/hooks/useProfileSync.ts
768
+ var import_react2 = require("react");
769
+ function useProfileSync() {
770
+ const { user, config, getAccessToken } = useAuthContext();
771
+ const [profile, setProfile] = (0, import_react2.useState)(null);
772
+ const [isLoading, setIsLoading] = (0, import_react2.useState)(true);
773
+ const [isSyncing, setIsSyncing] = (0, import_react2.useState)(false);
774
+ const [error, setError] = (0, import_react2.useState)(null);
775
+ const fetchProfile = (0, import_react2.useCallback)(async () => {
776
+ const token = getAccessToken();
777
+ if (!token || !user) {
778
+ setProfile(null);
779
+ setIsLoading(false);
780
+ return;
781
+ }
782
+ try {
783
+ const response = await fetch(`${config.baseUrl}/users/profile`, {
784
+ headers: {
785
+ "Authorization": `Bearer ${token}`,
786
+ "Content-Type": "application/json",
787
+ "X-App-Id": config.appId
788
+ }
789
+ });
790
+ if (!response.ok) {
791
+ throw new Error("Failed to fetch profile");
792
+ }
793
+ const data = await response.json();
794
+ setProfile({
795
+ ...user,
796
+ ...data.user,
797
+ lastSyncedAt: (/* @__PURE__ */ new Date()).toISOString()
798
+ });
799
+ setError(null);
800
+ } catch (err) {
801
+ setError(err instanceof Error ? err : new Error("Failed to fetch profile"));
802
+ setProfile(user ? { ...user, lastSyncedAt: void 0 } : null);
803
+ } finally {
804
+ setIsLoading(false);
805
+ }
806
+ }, [config.baseUrl, config.appId, getAccessToken, user]);
807
+ (0, import_react2.useEffect)(() => {
808
+ if (user) {
809
+ fetchProfile();
810
+ } else {
811
+ setProfile(null);
812
+ setIsLoading(false);
813
+ }
814
+ }, [user, fetchProfile]);
815
+ const syncProfile = (0, import_react2.useCallback)(async () => {
816
+ if (!user) return;
817
+ setIsSyncing(true);
818
+ await fetchProfile();
819
+ setIsSyncing(false);
820
+ }, [user, fetchProfile]);
821
+ const updateProfile = (0, import_react2.useCallback)(async (updates) => {
822
+ const token = getAccessToken();
823
+ if (!token || !profile) {
824
+ throw new Error("Not authenticated");
825
+ }
826
+ setIsSyncing(true);
827
+ try {
828
+ const response = await fetch(`${config.baseUrl}/users/profile`, {
829
+ method: "PATCH",
830
+ headers: {
831
+ "Authorization": `Bearer ${token}`,
832
+ "Content-Type": "application/json",
833
+ "X-App-Id": config.appId
834
+ },
835
+ body: JSON.stringify(updates)
836
+ });
837
+ if (!response.ok) {
838
+ const errorData = await response.json().catch(() => ({}));
839
+ throw new Error(errorData.message || "Failed to update profile");
840
+ }
841
+ const data = await response.json();
842
+ setProfile({
843
+ ...profile,
844
+ ...data.user,
845
+ lastSyncedAt: (/* @__PURE__ */ new Date()).toISOString()
846
+ });
847
+ setError(null);
848
+ } catch (err) {
849
+ const error2 = err instanceof Error ? err : new Error("Failed to update profile");
850
+ setError(error2);
851
+ throw error2;
852
+ } finally {
853
+ setIsSyncing(false);
854
+ }
855
+ }, [config.baseUrl, config.appId, getAccessToken, profile]);
856
+ const updatePreferences = (0, import_react2.useCallback)(async (preferences) => {
857
+ await updateProfile({
858
+ preferences: {
859
+ ...profile?.preferences,
860
+ ...preferences
861
+ }
862
+ });
863
+ }, [updateProfile, profile?.preferences]);
864
+ const linkApp = (0, import_react2.useCallback)(async (appId) => {
865
+ const token = getAccessToken();
866
+ if (!token) {
867
+ throw new Error("Not authenticated");
868
+ }
869
+ setIsSyncing(true);
870
+ try {
871
+ const response = await fetch(`${config.baseUrl}/users/link-app`, {
872
+ method: "POST",
873
+ headers: {
874
+ "Authorization": `Bearer ${token}`,
875
+ "Content-Type": "application/json",
876
+ "X-App-Id": config.appId
877
+ },
878
+ body: JSON.stringify({ appId })
879
+ });
880
+ if (!response.ok) {
881
+ const errorData = await response.json().catch(() => ({}));
882
+ throw new Error(errorData.message || "Failed to link app");
883
+ }
884
+ await fetchProfile();
885
+ setError(null);
886
+ } catch (err) {
887
+ const error2 = err instanceof Error ? err : new Error("Failed to link app");
888
+ setError(error2);
889
+ throw error2;
890
+ } finally {
891
+ setIsSyncing(false);
892
+ }
893
+ }, [config.baseUrl, config.appId, getAccessToken, fetchProfile]);
894
+ return {
895
+ profile,
896
+ isLoading,
897
+ isSyncing,
898
+ error,
899
+ updateProfile,
900
+ syncProfile,
901
+ updatePreferences,
902
+ linkApp
903
+ };
904
+ }
905
+
906
+ // src/hooks/index.ts
907
+ function useAuth() {
908
+ const context = useAuthContext();
909
+ return (0, import_react3.useMemo)(
910
+ () => ({
911
+ // State
912
+ user: context.user,
913
+ status: context.status,
914
+ isLoading: context.isLoading,
915
+ isAuthenticated: context.isAuthenticated,
916
+ isVerified: context.isVerified,
917
+ error: context.error,
918
+ // Actions
919
+ login: context.login,
920
+ register: context.register,
921
+ logout: context.logout,
922
+ verify: context.verify,
923
+ resendVerification: context.resendVerification,
924
+ forgotPassword: context.forgotPassword,
925
+ recoverPassword: context.recoverPassword,
926
+ // Utilities
927
+ hasRole: context.hasRole
928
+ }),
929
+ [context]
930
+ );
931
+ }
932
+ function useUser() {
933
+ const { user, isAuthenticated, isVerified, hasRole } = useAuthContext();
934
+ return (0, import_react3.useMemo)(() => {
935
+ const dailyTokens = user?.dailyTokens ?? 0;
936
+ const purchasedTokens = user?.purchasedTokens ?? 0;
937
+ const bonusTokens = user?.bonusTokens ?? 0;
938
+ const subscriptionTokens = user?.subscriptionTokens ?? 0;
939
+ return {
940
+ user,
941
+ isAuthenticated,
942
+ isVerified,
943
+ role: user?.role ?? null,
944
+ hasRole,
945
+ // Shortcuts
946
+ userId: user?._id ?? null,
947
+ userName: user?.userName ?? null,
948
+ email: user?.email ?? null,
949
+ profilePicture: user?.profilePicture ?? null,
950
+ subscriptionTier: user?.subscriptionTier ?? null,
951
+ // Tokens
952
+ dailyTokens,
953
+ purchasedTokens,
954
+ bonusTokens,
955
+ subscriptionTokens,
956
+ totalTokens: dailyTokens + purchasedTokens + bonusTokens + subscriptionTokens
957
+ };
958
+ }, [user, isAuthenticated, isVerified, hasRole]);
959
+ }
960
+ function useToken() {
961
+ const { accessToken, refreshToken, getAccessToken, refreshTokens, isAuthenticated } = useAuthContext();
962
+ return (0, import_react3.useMemo)(
963
+ () => ({
964
+ accessToken,
965
+ refreshToken,
966
+ getAccessToken,
967
+ refreshTokens,
968
+ isAuthenticated
969
+ }),
970
+ [accessToken, refreshToken, getAccessToken, refreshTokens, isAuthenticated]
971
+ );
972
+ }
973
+ function useAuthStatus() {
974
+ const { status, isLoading } = useAuthContext();
975
+ return (0, import_react3.useMemo)(
976
+ () => ({
977
+ status,
978
+ isIdle: status === "idle",
979
+ isLoading,
980
+ isAuthenticated: status === "authenticated",
981
+ isUnauthenticated: status === "unauthenticated",
982
+ isVerifying: status === "verifying"
983
+ }),
984
+ [status, isLoading]
985
+ );
986
+ }
987
+ function useRequireAuth(options = {}) {
988
+ const { roles, onUnauthorized } = options;
989
+ const { user, isAuthenticated, isLoading, hasRole } = useAuthContext();
990
+ const isAuthorized = (0, import_react3.useMemo)(() => {
991
+ if (!isAuthenticated) return false;
992
+ if (roles && roles.length > 0 && !hasRole(roles)) return false;
993
+ return true;
994
+ }, [isAuthenticated, roles, hasRole]);
995
+ (0, import_react3.useEffect)(() => {
996
+ if (!isLoading && !isAuthorized && onUnauthorized) {
997
+ onUnauthorized();
998
+ }
999
+ }, [isLoading, isAuthorized, onUnauthorized]);
1000
+ return (0, import_react3.useMemo)(
1001
+ () => ({
1002
+ isAuthorized,
1003
+ isLoading,
1004
+ user
1005
+ }),
1006
+ [isAuthorized, isLoading, user]
1007
+ );
1008
+ }
1009
+ function useAuthError() {
1010
+ const context = useAuthContext();
1011
+ return (0, import_react3.useMemo)(
1012
+ () => ({
1013
+ error: context.error,
1014
+ hasError: !!context.error,
1015
+ errorMessage: context.error?.message ?? null,
1016
+ errorCode: context.error?.code ?? null,
1017
+ clearError: () => {
1018
+ }
1019
+ }),
1020
+ [context.error]
1021
+ );
1022
+ }
1023
+ // Annotate the CommonJS export names for ESM import in node:
1024
+ 0 && (module.exports = {
1025
+ AuthContext,
1026
+ AuthProvider,
1027
+ createAuthApiClient,
1028
+ createTokenStorage,
1029
+ useAuth,
1030
+ useAuthContext,
1031
+ useAuthError,
1032
+ useAuthStatus,
1033
+ useProfileSync,
1034
+ useRequireAuth,
1035
+ useToken,
1036
+ useUser
1037
+ });