@umituz/react-native-auth 3.5.4 → 3.5.5

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": "3.5.4",
3
+ "version": "3.5.5",
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",
@@ -1,95 +1,43 @@
1
1
  /**
2
2
  * Unified Auth Initialization
3
- * Single function to initialize all auth services
4
- *
5
- * Combines:
6
- * - AuthService (email/password auth)
7
- * - Auth Listener (state management)
8
- * - User Document Service (Firestore)
9
- * - Anonymous-to-authenticated conversion detection
3
+ * Initializes Firebase auth with user document sync and conversion tracking
10
4
  */
11
5
 
12
6
  import type { Auth, User } from "firebase/auth";
13
7
  import { getFirebaseAuth } from "@umituz/react-native-firebase";
14
8
  import { initializeAuthService } from "./AuthService";
15
- import { configureUserDocumentService, ensureUserDocument } from "./UserDocumentService";
16
- import type { UserDocumentConfig } from "./UserDocumentService";
9
+ import { configureUserDocumentService } from "./UserDocumentService";
17
10
  import { collectDeviceExtras } from "@umituz/react-native-design-system";
18
11
  import { initializeAuthListener } from "../../presentation/stores/initializeAuthListener";
12
+ import { createAuthStateHandler } from "../utils/authStateHandler";
13
+ import type { ConversionState } from "../utils/authConversionDetector";
19
14
  import type { AuthConfig } from "../../domain/value-objects/AuthConfig";
20
-
21
15
  import type { IStorageProvider } from "../types/Storage.types";
22
16
 
23
- /**
24
- * Unified auth initialization options
25
- */
26
17
  export interface InitializeAuthOptions {
27
- /** User document collection name (default: "users") */
28
18
  userCollection?: string;
29
-
30
- /** Additional fields to store with user documents */
31
19
  extraFields?: Record<string, unknown>;
32
-
33
- /** Callback to collect device/app info for user documents */
34
20
  collectExtras?: () => Promise<Record<string, unknown>>;
35
-
36
- /** Storage provider for persisting auth state (e.g. anonymous mode) */
37
21
  storageProvider?: IStorageProvider;
38
-
39
- /** Enable auto anonymous sign-in (default: true) */
40
22
  autoAnonymousSignIn?: boolean;
41
-
42
- /**
43
- * Callback when user converts from anonymous to authenticated
44
- * Use this to migrate user data (e.g., call Cloud Function)
45
- *
46
- * @param anonymousUserId - The previous anonymous user ID
47
- * @param authenticatedUserId - The new authenticated user ID
48
- */
49
- onUserConverted?: (
50
- anonymousUserId: string,
51
- authenticatedUserId: string
52
- ) => void | Promise<void>;
53
-
54
- /**
55
- * Callback when auth state changes (after user document is ensured)
56
- * Called for every auth state change including initial load
57
- */
23
+ onUserConverted?: (anonymousId: string, authenticatedId: string) => void | Promise<void>;
58
24
  onAuthStateChange?: (user: User | null) => void | Promise<void>;
59
-
60
- /** Auth configuration (password rules, etc.) */
61
25
  authConfig?: Partial<AuthConfig>;
62
26
  }
63
27
 
64
28
  let isInitialized = false;
65
-
66
- // Track previous user for conversion detection
67
- let previousUserId: string | null = null;
68
- let wasAnonymous = false;
29
+ const conversionState: { current: ConversionState } = {
30
+ current: { previousUserId: null, wasAnonymous: false },
31
+ };
69
32
 
70
33
  /**
71
- * Initialize all auth services with a single call
72
- *
73
- * @example
74
- * ```typescript
75
- * import { initializeAuth } from '@umituz/react-native-auth';
76
- * import { migrateUserData } from '@domains/migration';
77
- *
78
- * await initializeAuth({
79
- * userCollection: 'users',
80
- * autoAnonymousSignIn: true,
81
- * onUserConverted: async (anonymousId, authId) => {
82
- * await migrateUserData(anonymousId, authId);
83
- * },
84
- * });
85
- * ```
34
+ * Initialize auth services
86
35
  */
87
36
  export async function initializeAuth(
88
37
  options: InitializeAuthOptions = {}
89
38
  ): Promise<{ success: boolean; auth: Auth | null }> {
90
39
  if (isInitialized) {
91
- const auth = getFirebaseAuth();
92
- return { success: true, auth };
40
+ return { success: true, auth: getFirebaseAuth() };
93
41
  }
94
42
 
95
43
  const {
@@ -103,94 +51,40 @@ export async function initializeAuth(
103
51
  authConfig,
104
52
  } = options;
105
53
 
106
- // 1. Configure user document service
107
- const userDocConfig: UserDocumentConfig = {
108
- collectionName: userCollection,
109
- };
110
- if (extraFields) userDocConfig.extraFields = extraFields;
111
- // Use provided collectExtras or default to design system's implementation
112
- userDocConfig.collectExtras = collectExtras || collectDeviceExtras;
113
-
114
- configureUserDocumentService(userDocConfig);
115
-
116
- // 2. Get Firebase Auth
117
54
  const auth = getFirebaseAuth();
118
- if (!auth) {
119
- return { success: false, auth: null };
120
- }
55
+ if (!auth) return { success: false, auth: null };
56
+
57
+ configureUserDocumentService({
58
+ collectionName: userCollection,
59
+ extraFields,
60
+ collectExtras: collectExtras || collectDeviceExtras,
61
+ });
121
62
 
122
- // 3. Initialize AuthService (for email/password auth)
123
63
  try {
124
64
  await initializeAuthService(auth, authConfig, storageProvider);
125
65
  } catch {
126
- // AuthService initialization failed, but we can continue
127
- // Email/password auth won't work, but social/anonymous will
66
+ // Continue without email/password auth
128
67
  }
129
68
 
130
- // 4. Initialize Auth Listener (for state management)
69
+ const handleAuthStateChange = createAuthStateHandler(conversionState, {
70
+ onUserConverted,
71
+ onAuthStateChange,
72
+ });
73
+
131
74
  initializeAuthListener({
132
75
  autoAnonymousSignIn,
133
- onAuthStateChange: (user) => {
134
- void (async () => {
135
- if (!user) {
136
- // User signed out
137
- previousUserId = null;
138
- wasAnonymous = false;
139
- await onAuthStateChange?.(null);
140
- return;
141
- }
142
-
143
- const currentUserId = user.uid;
144
- const isCurrentlyAnonymous = user.isAnonymous ?? false;
145
-
146
- // Detect anonymous-to-authenticated conversion
147
- // This happens in two scenarios:
148
- // 1. linkWithCredential: same UID, but isAnonymous changes from true to false
149
- // 2. New account after anonymous: different UID (legacy flow)
150
- const isConversionSameUser = previousUserId === currentUserId && wasAnonymous && !isCurrentlyAnonymous;
151
- const isConversionNewUser = previousUserId && previousUserId !== currentUserId && wasAnonymous && !isCurrentlyAnonymous;
152
- const isConversion = isConversionSameUser || isConversionNewUser;
153
-
154
- if (isConversion && onUserConverted) {
155
- try {
156
- await onUserConverted(previousUserId!, currentUserId);
157
- } catch {
158
- // Migration failed but don't block user flow
159
- }
160
- }
161
-
162
- // Create/update user document in Firestore with conversion info
163
- const extras = isConversion
164
- ? { previousAnonymousUserId: previousUserId! }
165
- : undefined;
166
- await ensureUserDocument(user, extras);
167
-
168
- // Update tracking state
169
- previousUserId = currentUserId;
170
- wasAnonymous = isCurrentlyAnonymous;
171
-
172
- // Call app's custom callback
173
- await onAuthStateChange?.(user);
174
- })();
175
- },
76
+ onAuthStateChange: (user) => void handleAuthStateChange(user),
176
77
  });
177
78
 
178
79
  isInitialized = true;
179
80
  return { success: true, auth };
180
81
  }
181
82
 
182
- /**
183
- * Check if auth is initialized
184
- */
185
83
  export function isAuthInitialized(): boolean {
186
84
  return isInitialized;
187
85
  }
188
86
 
189
- /**
190
- * Reset auth initialization state (for testing)
191
- */
192
87
  export function resetAuthInitialization(): void {
193
88
  isInitialized = false;
194
- previousUserId = null;
195
- wasAnonymous = false;
89
+ conversionState.current = { previousUserId: null, wasAnonymous: false };
196
90
  }
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Auth Conversion Detector
3
+ * Detects anonymous-to-authenticated user conversions
4
+ */
5
+
6
+ export interface ConversionState {
7
+ previousUserId: string | null;
8
+ wasAnonymous: boolean;
9
+ }
10
+
11
+ export interface ConversionResult {
12
+ isConversion: boolean;
13
+ isSameUser: boolean;
14
+ isNewUser: boolean;
15
+ }
16
+
17
+ /**
18
+ * Detects if current auth state represents a conversion
19
+ * from anonymous to authenticated user
20
+ */
21
+ export function detectConversion(
22
+ state: ConversionState,
23
+ currentUserId: string,
24
+ isCurrentlyAnonymous: boolean
25
+ ): ConversionResult {
26
+ const { previousUserId, wasAnonymous } = state;
27
+
28
+ // Same UID, anonymous flag changed (linkWithCredential)
29
+ const isSameUser =
30
+ previousUserId === currentUserId && wasAnonymous && !isCurrentlyAnonymous;
31
+
32
+ // Different UID after anonymous (legacy flow)
33
+ const isNewUser =
34
+ !!previousUserId &&
35
+ previousUserId !== currentUserId &&
36
+ wasAnonymous &&
37
+ !isCurrentlyAnonymous;
38
+
39
+ return {
40
+ isConversion: isSameUser || isNewUser,
41
+ isSameUser,
42
+ isNewUser,
43
+ };
44
+ }
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Auth State Handler
3
+ * Handles auth state changes with conversion detection
4
+ */
5
+
6
+ import type { User } from "firebase/auth";
7
+ import { ensureUserDocument } from "../services/UserDocumentService";
8
+ import { detectConversion, type ConversionState } from "./authConversionDetector";
9
+
10
+ export interface AuthStateHandlerOptions {
11
+ onUserConverted?: (anonymousId: string, authenticatedId: string) => void | Promise<void>;
12
+ onAuthStateChange?: (user: User | null) => void | Promise<void>;
13
+ }
14
+
15
+ /**
16
+ * Creates auth state change handler with conversion detection
17
+ */
18
+ export function createAuthStateHandler(
19
+ state: { current: ConversionState },
20
+ options: AuthStateHandlerOptions
21
+ ) {
22
+ return async (user: User | null): Promise<void> => {
23
+ const { onUserConverted, onAuthStateChange } = options;
24
+
25
+ if (!user) {
26
+ state.current = { previousUserId: null, wasAnonymous: false };
27
+ await onAuthStateChange?.(null);
28
+ return;
29
+ }
30
+
31
+ const currentUserId = user.uid;
32
+ const isCurrentlyAnonymous = user.isAnonymous ?? false;
33
+
34
+ const conversion = detectConversion(state.current, currentUserId, isCurrentlyAnonymous);
35
+
36
+ if (conversion.isConversion && onUserConverted && state.current.previousUserId) {
37
+ try {
38
+ await onUserConverted(state.current.previousUserId, currentUserId);
39
+ } catch {
40
+ // Migration failed but don't block user flow
41
+ }
42
+ }
43
+
44
+ const extras = conversion.isConversion && state.current.previousUserId
45
+ ? { previousAnonymousUserId: state.current.previousUserId }
46
+ : undefined;
47
+
48
+ await ensureUserDocument(user, extras);
49
+
50
+ state.current = {
51
+ previousUserId: currentUserId,
52
+ wasAnonymous: isCurrentlyAnonymous,
53
+ };
54
+
55
+ await onAuthStateChange?.(user);
56
+ };
57
+ }