@umituz/react-native-auth 4.3.81 → 4.3.82

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.3.81",
3
+ "version": "4.3.82",
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",
@@ -54,16 +54,26 @@ export class AnonymousModeService {
54
54
  }
55
55
 
56
56
  async enable(storageProvider: IStorageProvider): Promise<void> {
57
- // Save to storage first, then update memory to maintain consistency.
58
- // This prevents TOCTOU: memory is never set to true unless storage confirms the write.
59
- const saveSuccess = await this.save(storageProvider, true);
57
+ const previousState = this.isAnonymousMode;
60
58
 
61
- if (!saveSuccess) {
62
- throw new Error('Failed to save anonymous mode state');
63
- }
59
+ try {
60
+ // Save to storage first, then update memory to maintain consistency.
61
+ // This prevents TOCTOU: memory is never set to true unless storage confirms the write.
62
+ const saveSuccess = await this.save(storageProvider, true);
63
+
64
+ if (!saveSuccess) {
65
+ // Rollback to previous state on failure
66
+ this.isAnonymousMode = previousState;
67
+ throw new Error('Failed to save anonymous mode state');
68
+ }
64
69
 
65
- this.isAnonymousMode = true;
66
- emitAnonymousModeEnabled();
70
+ this.isAnonymousMode = true;
71
+ emitAnonymousModeEnabled();
72
+ } catch (error) {
73
+ // Ensure state is rolled back on any error
74
+ this.isAnonymousMode = previousState;
75
+ throw error;
76
+ }
67
77
  }
68
78
 
69
79
  getIsAnonymousMode(): boolean {
@@ -38,8 +38,8 @@ export class AuthService {
38
38
 
39
39
  if (this.initialized) return;
40
40
 
41
- // Create and store initialization promise to prevent concurrent initialization
42
- this.initializationPromise = (async () => {
41
+ // Store reference to current promise to prevent race condition
42
+ const initPromise = (async () => {
43
43
  this.repository = new AuthRepository(this.config);
44
44
 
45
45
  if (this.storageProvider) {
@@ -48,10 +48,15 @@ export class AuthService {
48
48
  this.initialized = true;
49
49
  })();
50
50
 
51
+ this.initializationPromise = initPromise;
52
+
51
53
  try {
52
- await this.initializationPromise;
54
+ await initPromise;
53
55
  } finally {
54
- this.initializationPromise = null;
56
+ // Only null out if it's still the same promise (prevents race condition)
57
+ if (this.initializationPromise === initPromise) {
58
+ this.initializationPromise = null;
59
+ }
55
60
  }
56
61
  }
57
62
 
@@ -123,11 +123,12 @@ export function resetListenerState(): void {
123
123
  console.error('[ListenerState] Error during unsubscribe:', error);
124
124
  }
125
125
  }
126
+ // Always set to null even if unsubscribe fails to prevent retry loops
127
+ state.unsubscribe = null;
126
128
  }
127
129
  state.initialized = false;
128
130
  state.refCount = 0;
129
131
  state.initializationInProgress = false;
130
132
  state.anonymousSignInInProgress = false;
131
133
  state.cleanupInProgress = false;
132
- state.unsubscribe = null;
133
134
  }
@@ -59,7 +59,8 @@ export const useAuthHandlers = (appInfo: AuthHandlersAppInfo, translations?: Aut
59
59
  if (__DEV__) {
60
60
  console.error("[useAuthHandlers] Failed to open app store:", error);
61
61
  }
62
- Alert.alert(translations?.common || "", translations?.failedToOpenAppStore || "");
62
+ const errorMessage = error instanceof Error ? error.message : String(error);
63
+ Alert.alert(translations?.common || "", translations?.failedToOpenAppStore || errorMessage);
63
64
  }
64
65
  }, [appInfo.appStoreUrl, translations]);
65
66
 
@@ -70,9 +71,10 @@ export const useAuthHandlers = (appInfo: AuthHandlersAppInfo, translations?: Aut
70
71
  if (__DEV__) {
71
72
  console.error("[useAuthHandlers] Sign out failed:", error);
72
73
  }
74
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
73
75
  AlertService.createErrorAlert(
74
76
  translations?.common || "",
75
- translations?.unknown || ""
77
+ translations?.unknown || errorMessage
76
78
  );
77
79
  }
78
80
  }, [signOut, translations]);
@@ -38,18 +38,39 @@ export const AuthNavigator: React.FC<AuthNavigatorProps> = ({
38
38
  >(undefined);
39
39
 
40
40
  useEffect(() => {
41
+ let isMounted = true;
42
+ const abortController = new AbortController();
43
+
41
44
  const checkInitialRoute = async () => {
42
- const result = await storageRepository.getString(SHOW_REGISTER_KEY, "false");
43
- const value = unwrap(result, "false");
44
- if (value === "true") {
45
- setInitialRouteName("Register");
46
- void storageRepository.removeItem(SHOW_REGISTER_KEY);
47
- } else {
48
- setInitialRouteName("Login");
45
+ try {
46
+ const result = await storageRepository.getString(SHOW_REGISTER_KEY, "false");
47
+ // Check if component is still mounted before updating state
48
+ if (!isMounted || abortController.signal.aborted) return;
49
+
50
+ const value = unwrap(result, "false");
51
+ if (value === "true") {
52
+ setInitialRouteName("Register");
53
+ void storageRepository.removeItem(SHOW_REGISTER_KEY);
54
+ } else {
55
+ setInitialRouteName("Login");
56
+ }
57
+ } catch (error) {
58
+ // Silently fail - will default to Login screen
59
+ if (__DEV__) {
60
+ console.error('[AuthNavigator] Failed to check initial route:', error);
61
+ }
62
+ if (isMounted && !abortController.signal.aborted) {
63
+ setInitialRouteName("Login");
64
+ }
49
65
  }
50
66
  };
51
67
 
52
68
  void checkInitialRoute();
69
+
70
+ return () => {
71
+ isMounted = false;
72
+ abortController.abort();
73
+ };
53
74
  }, []);
54
75
 
55
76
  // Memoize nested translation objects to prevent screen wrapper recreation