@umituz/react-native-auth 4.2.16 → 4.2.18

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-auth",
3
- "version": "4.2.16",
3
+ "version": "4.2.18",
4
4
  "description": "Authentication service for React Native apps - Secure, type-safe, and production-ready. Provider-agnostic design with dependency injection, configurable validation, and comprehensive error handling.",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -21,6 +21,8 @@ export class AnonymousModeService {
21
21
  this.isAnonymousMode = value === "true";
22
22
  return this.isAnonymousMode;
23
23
  } catch {
24
+ // On error, reset to false to maintain consistency
25
+ this.isAnonymousMode = false;
24
26
  return false;
25
27
  }
26
28
  }
@@ -40,14 +42,24 @@ export class AnonymousModeService {
40
42
  this.isAnonymousMode = false;
41
43
  return true;
42
44
  } catch {
43
- this.isAnonymousMode = false;
45
+ // Don't update memory state if storage operation failed
46
+ // This maintains consistency between storage and memory
44
47
  return false;
45
48
  }
46
49
  }
47
50
 
48
51
  async enable(storageProvider: IStorageProvider): Promise<void> {
52
+ // Save to storage first, then update memory to maintain consistency
53
+ const previousState = this.isAnonymousMode;
49
54
  this.isAnonymousMode = true;
50
- await this.save(storageProvider);
55
+ const saveSuccess = await this.save(storageProvider);
56
+
57
+ if (!saveSuccess) {
58
+ // Rollback on failure
59
+ this.isAnonymousMode = previousState;
60
+ throw new Error('Failed to save anonymous mode state');
61
+ }
62
+
51
63
  emitAnonymousModeEnabled();
52
64
  }
53
65
 
@@ -16,6 +16,7 @@ export class AuthService {
16
16
  private anonymousModeService: AnonymousModeService;
17
17
  private storageProvider?: IStorageProvider;
18
18
  private initialized: boolean = false;
19
+ private initializationPromise: Promise<void> | null = null;
19
20
  private config: AuthConfig;
20
21
 
21
22
  constructor(config: Partial<AuthConfig> = {}, storageProvider?: IStorageProvider) {
@@ -30,14 +31,28 @@ export class AuthService {
30
31
  }
31
32
 
32
33
  async initialize(): Promise<void> {
34
+ // Return existing promise if initialization is in progress
35
+ if (this.initializationPromise) {
36
+ return this.initializationPromise;
37
+ }
38
+
33
39
  if (this.initialized) return;
34
40
 
35
- this.repository = new AuthRepository(this.config);
41
+ // Create and store initialization promise to prevent concurrent initialization
42
+ this.initializationPromise = (async () => {
43
+ this.repository = new AuthRepository(this.config);
44
+
45
+ if (this.storageProvider) {
46
+ await this.anonymousModeService.load(this.storageProvider);
47
+ }
48
+ this.initialized = true;
49
+ })();
36
50
 
37
- if (this.storageProvider) {
38
- await this.anonymousModeService.load(this.storageProvider);
51
+ try {
52
+ await this.initializationPromise;
53
+ } finally {
54
+ this.initializationPromise = null;
39
55
  }
40
- this.initialized = true;
41
56
  }
42
57
 
43
58
  isInitialized(): boolean {
@@ -65,7 +80,12 @@ export class AuthService {
65
80
 
66
81
  private async clearAnonymousModeIfNeeded(): Promise<void> {
67
82
  if (this.anonymousModeService.getIsAnonymousMode() && this.storageProvider) {
68
- await this.anonymousModeService.clear(this.storageProvider);
83
+ const success = await this.anonymousModeService.clear(this.storageProvider);
84
+ if (!success) {
85
+ console.warn('[AuthService] Failed to clear anonymous mode from storage');
86
+ // Force clear in memory to maintain consistency
87
+ this.anonymousModeService.setAnonymousMode(false);
88
+ }
69
89
  }
70
90
  }
71
91
 
@@ -45,13 +45,26 @@ export function createAuthStateHandler(
45
45
  ? { previousAnonymousUserId: state.current.previousUserId }
46
46
  : undefined;
47
47
 
48
- await ensureUserDocument(user, extras);
48
+ try {
49
+ await ensureUserDocument(user, extras);
50
+ } catch (error) {
51
+ console.error('[AuthStateHandler] Failed to ensure user document:', error);
52
+ // Continue execution - don't let user document creation failure block auth flow
53
+ }
49
54
 
50
55
  state.current = {
51
56
  previousUserId: currentUserId,
52
57
  wasAnonymous: isCurrentlyAnonymous,
53
58
  };
54
59
 
55
- await onAuthStateChange?.(user);
60
+ // Call user callback with error handling
61
+ if (onAuthStateChange) {
62
+ try {
63
+ await onAuthStateChange(user);
64
+ } catch (error) {
65
+ console.error('[AuthStateHandler] User callback error:', error);
66
+ // Don't propagate user callback errors
67
+ }
68
+ }
56
69
  };
57
70
  }
@@ -18,7 +18,7 @@ export function handleAuthStateChange(
18
18
  store: Store,
19
19
  auth: Auth,
20
20
  autoAnonymousSignIn: boolean,
21
- onAuthStateChange?: (user: User | null) => void
21
+ onAuthStateChange?: (user: User | null) => void | Promise<void>
22
22
  ): void {
23
23
  try {
24
24
  if (!user && autoAnonymousSignIn) {
@@ -37,7 +37,20 @@ export function handleAuthStateChange(
37
37
  store.setIsAnonymous(false);
38
38
  }
39
39
 
40
- onAuthStateChange?.(user);
40
+ // Call user callback with proper error handling for async callbacks
41
+ if (onAuthStateChange) {
42
+ try {
43
+ const result = onAuthStateChange(user);
44
+ // If callback returns a promise, catch rejections
45
+ if (result && typeof result.then === 'function') {
46
+ result.catch((error) => {
47
+ console.error("[AuthListener] User callback promise rejected:", error);
48
+ });
49
+ }
50
+ } catch (error) {
51
+ console.error("[AuthListener] User callback error:", error);
52
+ }
53
+ }
41
54
  } catch (error) {
42
55
  console.error("[AuthListener] Error handling auth state change:", error);
43
56
  // Ensure we don't leave the app in a bad state
@@ -95,7 +95,11 @@ export function incrementRefCount(): number {
95
95
  * Uses cleanupInProgress flag to prevent concurrent cleanup attempts
96
96
  */
97
97
  export function decrementRefCount(): { shouldCleanup: boolean; count: number } {
98
- state.refCount--;
98
+ // Prevent refCount from going negative
99
+ if (state.refCount > 0) {
100
+ state.refCount--;
101
+ }
102
+
99
103
  const shouldCleanup =
100
104
  state.refCount <= 0 &&
101
105
  state.unsubscribe !== null &&
@@ -132,7 +136,11 @@ export function completeAnonymousSignIn(): void {
132
136
  */
133
137
  export function resetListenerState(): void {
134
138
  if (state.unsubscribe) {
135
- state.unsubscribe();
139
+ try {
140
+ state.unsubscribe();
141
+ } catch (error) {
142
+ console.error('[ListenerState] Error during unsubscribe:', error);
143
+ }
136
144
  }
137
145
  state.initialized = false;
138
146
  state.refCount = 0;
@@ -19,7 +19,7 @@ export function setupAuthListener(
19
19
  auth: Auth,
20
20
  store: Store,
21
21
  autoAnonymousSignIn: boolean,
22
- onAuthStateChange?: (user: User | null) => void
22
+ onAuthStateChange?: (user: User | null) => void | Promise<void>
23
23
  ): void {
24
24
  const service = getAuthService();
25
25
 
@@ -58,6 +58,6 @@ export function setupAuthListener(
58
58
  store.setLoading(false);
59
59
  store.setInitialized(true);
60
60
  store.setError("Failed to initialize authentication listener");
61
- throw error;
61
+ // Don't re-throw - app state is already cleaned up and consistent
62
62
  }
63
63
  }
@@ -106,7 +106,7 @@ export function useLoginForm(config?: UseLoginFormConfig): UseLoginFormResult {
106
106
  const localizationKey = getAuthErrorLocalizationKey(err);
107
107
  setLocalError(getErrorMessage(localizationKey));
108
108
  }
109
- }, [fields, signIn, translations, getErrorMessage, clearErrors, updateField]);
109
+ }, [fields, signIn, translations, getErrorMessage, clearErrors]);
110
110
 
111
111
  const handleContinueAnonymously = useCallback(async () => {
112
112
  try {
@@ -61,7 +61,8 @@ export const useAuthModalStore = createStore<AuthModalState, AuthModalActions>({
61
61
  },
62
62
 
63
63
  hideAuthModal: () => {
64
- set({ isVisible: false });
64
+ // Clear pending callback to prevent memory leaks
65
+ set({ isVisible: false, pendingCallback: null });
65
66
  },
66
67
 
67
68
  setMode: (mode: AuthModalMode) => {
@@ -71,7 +72,18 @@ export const useAuthModalStore = createStore<AuthModalState, AuthModalActions>({
71
72
  executePendingCallback: () => {
72
73
  const state = get();
73
74
  if (state.pendingCallback) {
74
- void state.pendingCallback();
75
+ // Wrap in try-catch to handle promise rejections
76
+ try {
77
+ const result = state.pendingCallback();
78
+ // If it's a promise, catch rejections
79
+ if (result && typeof result.then === 'function') {
80
+ result.catch((error) => {
81
+ console.error('[AuthModalStore] Pending callback error:', error);
82
+ });
83
+ }
84
+ } catch (error) {
85
+ console.error('[AuthModalStore] Pending callback error:', error);
86
+ }
75
87
  set({ pendingCallback: null });
76
88
  }
77
89
  },
@@ -48,7 +48,13 @@ export const useAuthStore = createStore<AuthState, AuthActions>({
48
48
  initialized: state.initialized ?? false,
49
49
  };
50
50
  }
51
- return { ...initialAuthState, ...state };
51
+ // Only restore persisted fields (isAnonymous, initialized)
52
+ // Never restore runtime state (user, firebaseUser, loading, error)
53
+ return {
54
+ ...initialAuthState,
55
+ isAnonymous: state.isAnonymous ?? false,
56
+ initialized: state.initialized ?? false,
57
+ };
52
58
  },
53
59
  actions: (set, get) => ({
54
60
  setFirebaseUser: (firebaseUser) => {
@@ -65,6 +65,6 @@ export const initialAuthState: AuthState = {
65
65
  export interface AuthListenerOptions {
66
66
  /** Enable auto anonymous sign-in when no user is logged in */
67
67
  autoAnonymousSignIn?: boolean;
68
- /** Callback when auth state changes */
69
- onAuthStateChange?: (user: User | null) => void;
68
+ /** Callback when auth state changes (can be async) */
69
+ onAuthStateChange?: (user: User | null) => void | Promise<void>;
70
70
  }