@umituz/react-native-firebase 1.13.2 → 1.13.3

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.
@@ -0,0 +1,201 @@
1
+ /**
2
+ * useAnonymousAuth Hook
3
+ * React hook for anonymous authentication state
4
+ */
5
+
6
+ import { useState, useEffect, useCallback, useRef } from "react";
7
+ import { onAuthStateChanged, type Auth } from "firebase/auth";
8
+ import { checkAuthState, type AuthCheckResult } from "../../infrastructure/services/auth-utils.service";
9
+ import { anonymousAuthService, type AnonymousAuthResult } from "../../infrastructure/services/anonymous-auth.service";
10
+
11
+ declare const __DEV__: boolean;
12
+
13
+ export interface UseAnonymousAuthResult extends AuthCheckResult {
14
+ /**
15
+ * Sign in anonymously
16
+ */
17
+ signInAnonymously: () => Promise<AnonymousAuthResult>;
18
+
19
+ /**
20
+ * Loading state
21
+ */
22
+ readonly loading: boolean;
23
+
24
+ /**
25
+ * Error state
26
+ */
27
+ readonly error: Error | null;
28
+
29
+ /**
30
+ * Clear error
31
+ */
32
+ clearError: () => void;
33
+ }
34
+
35
+ /**
36
+ * Hook for anonymous authentication
37
+ */
38
+ export function useAnonymousAuth(auth: Auth | null): UseAnonymousAuthResult {
39
+ const [authState, setAuthState] = useState<AuthCheckResult>(() =>
40
+ checkAuthState(auth),
41
+ );
42
+ const [loading, setLoading] = useState(true);
43
+ const [error, setError] = useState<Error | null>(null);
44
+ const authRef = useRef(auth);
45
+ const unsubscribeRef = useRef<(() => void) | null>(null);
46
+
47
+ // Update ref when auth changes
48
+ useEffect(() => {
49
+ authRef.current = auth;
50
+ }, [auth]);
51
+
52
+ // Clear error helper
53
+ const clearError = useCallback(() => {
54
+ setError(null);
55
+ }, []);
56
+
57
+ // Auth state change handler - accepts user from onAuthStateChanged callback
58
+ const handleAuthStateChange = useCallback((user: import("firebase/auth").User | null) => {
59
+ try {
60
+ // Use the user from the callback, NOT auth.currentUser
61
+ // This ensures we have the correct user from Firebase's persistence
62
+ if (!user) {
63
+ setAuthState({
64
+ isAuthenticated: false,
65
+ isAnonymous: false,
66
+ isGuest: false,
67
+ currentUser: null,
68
+ userId: null,
69
+ });
70
+ } else {
71
+ const anonymous = user.isAnonymous === true;
72
+ setAuthState({
73
+ isAuthenticated: true,
74
+ isAnonymous: anonymous,
75
+ isGuest: anonymous,
76
+ currentUser: user,
77
+ userId: user.uid,
78
+ });
79
+ }
80
+ setError(null);
81
+ } catch (err) {
82
+ const authError = err instanceof Error ? err : new Error('Auth state check failed');
83
+ setError(authError);
84
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
85
+ // eslint-disable-next-line no-console
86
+ console.error("[useAnonymousAuth] Auth state change error", authError);
87
+ }
88
+ } finally {
89
+ setLoading(false);
90
+ }
91
+ }, []);
92
+
93
+ // Setup auth state listener
94
+ // IMPORTANT: Do NOT call handleAuthStateChange() immediately!
95
+ // Wait for onAuthStateChanged to fire - it will have the correct user from persistence
96
+ useEffect(() => {
97
+ // Cleanup previous listener
98
+ if (unsubscribeRef.current) {
99
+ unsubscribeRef.current();
100
+ unsubscribeRef.current = null;
101
+ }
102
+
103
+ if (!auth) {
104
+ setAuthState({
105
+ isAuthenticated: false,
106
+ isAnonymous: false,
107
+ isGuest: false,
108
+ currentUser: null,
109
+ userId: null,
110
+ });
111
+ setLoading(false);
112
+ setError(null);
113
+ return;
114
+ }
115
+
116
+ // Keep loading true until onAuthStateChanged fires
117
+ setLoading(true);
118
+
119
+ try {
120
+ // Listen to auth state changes - this is the ONLY source of truth
121
+ // The first callback will have the user restored from persistence (or null)
122
+ unsubscribeRef.current = onAuthStateChanged(auth, (user) => {
123
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
124
+ // eslint-disable-next-line no-console
125
+ console.log("[useAnonymousAuth] onAuthStateChanged fired", {
126
+ hasUser: !!user,
127
+ uid: user?.uid,
128
+ isAnonymous: user?.isAnonymous,
129
+ email: user?.email,
130
+ });
131
+ }
132
+ // IMPORTANT: Pass the user from the callback, not auth.currentUser!
133
+ handleAuthStateChange(user);
134
+ });
135
+ } catch (err) {
136
+ const authError = err instanceof Error ? err : new Error('Auth listener setup failed');
137
+ setError(authError);
138
+ setLoading(false);
139
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
140
+ // eslint-disable-next-line no-console
141
+ console.error("[useAnonymousAuth] Auth listener setup error", authError);
142
+ }
143
+ }
144
+
145
+ // Cleanup function
146
+ return () => {
147
+ if (unsubscribeRef.current) {
148
+ unsubscribeRef.current();
149
+ unsubscribeRef.current = null;
150
+ }
151
+ };
152
+ }, [auth, handleAuthStateChange]);
153
+
154
+ // Sign in anonymously
155
+ const signInAnonymously = useCallback(async (): Promise<AnonymousAuthResult> => {
156
+ if (!auth) {
157
+ const authError = new Error("Firebase Auth not initialized");
158
+ setError(authError);
159
+ throw authError;
160
+ }
161
+
162
+ setLoading(true);
163
+ setError(null);
164
+
165
+ try {
166
+ const result = await anonymousAuthService.signInAnonymously(auth);
167
+
168
+ // Update auth state after successful sign in
169
+ // Pass the user from the result, not from auth.currentUser
170
+ handleAuthStateChange(result.user);
171
+
172
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
173
+ // eslint-disable-next-line no-console
174
+ console.log("[useAnonymousAuth] Successfully signed in anonymously", {
175
+ uid: result.anonymousUser.uid,
176
+ wasAlreadySignedIn: result.wasAlreadySignedIn,
177
+ });
178
+ }
179
+
180
+ return result;
181
+ } catch (err) {
182
+ const authError = err instanceof Error ? err : new Error('Anonymous sign in failed');
183
+ setError(authError);
184
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
185
+ // eslint-disable-next-line no-console
186
+ console.error("[useAnonymousAuth] Sign in error", authError);
187
+ }
188
+ throw authError;
189
+ } finally {
190
+ setLoading(false);
191
+ }
192
+ }, [auth, handleAuthStateChange]);
193
+
194
+ return {
195
+ ...authState,
196
+ signInAnonymously,
197
+ loading,
198
+ error,
199
+ clearError,
200
+ };
201
+ }
@@ -0,0 +1,84 @@
1
+ /**
2
+ * useFirebaseAuth Hook
3
+ * React hook for Firebase Auth state management
4
+ *
5
+ * Directly uses Firebase Auth's built-in state management via onAuthStateChanged
6
+ */
7
+
8
+ import { useEffect, useState } from "react";
9
+ import { onAuthStateChanged, type User } from "firebase/auth";
10
+ import { getFirebaseAuth, isFirebaseAuthInitialized } from "../../infrastructure/config/FirebaseAuthClient";
11
+
12
+ export interface UseFirebaseAuthResult {
13
+ /** Current authenticated user from Firebase Auth */
14
+ user: User | null;
15
+ /** Whether auth state is loading (initial check) */
16
+ loading: boolean;
17
+ /** Whether Firebase Auth is initialized */
18
+ initialized: boolean;
19
+ }
20
+
21
+ /**
22
+ * Hook for Firebase Auth state management
23
+ *
24
+ * Directly uses Firebase Auth's built-in state management.
25
+ * No additional state management layer needed.
26
+ *
27
+ * @example
28
+ * ```typescript
29
+ * const { user, loading } = useFirebaseAuth();
30
+ * ```
31
+ */
32
+ export function useFirebaseAuth(): UseFirebaseAuthResult {
33
+ const [user, setUser] = useState<User | null>(null);
34
+ const [loading, setLoading] = useState(true);
35
+ const [initialized, setInitialized] = useState(false);
36
+
37
+ useEffect(() => {
38
+ // Check if Firebase Auth is initialized
39
+ const isInitialized = isFirebaseAuthInitialized();
40
+ setInitialized(isInitialized);
41
+
42
+ if (!isInitialized) {
43
+ setLoading(false);
44
+ setUser(null);
45
+ return;
46
+ }
47
+
48
+ try {
49
+ const auth = getFirebaseAuth();
50
+
51
+ if (!auth) {
52
+ setUser(null);
53
+ setLoading(false);
54
+ return;
55
+ }
56
+
57
+ // Subscribe to auth state changes
58
+ const unsubscribe = onAuthStateChanged(auth, (currentUser: User | null) => {
59
+ setUser(currentUser);
60
+ setLoading(false);
61
+ });
62
+
63
+ // Set initial state
64
+ setUser(auth.currentUser);
65
+ setLoading(false);
66
+
67
+ return () => {
68
+ unsubscribe();
69
+ };
70
+ } catch (error) {
71
+ // Firebase Auth not initialized or error
72
+ setUser(null);
73
+ setLoading(false);
74
+ return () => {};
75
+ }
76
+ }, []);
77
+
78
+ return {
79
+ user,
80
+ loading,
81
+ initialized,
82
+ };
83
+ }
84
+
@@ -0,0 +1,162 @@
1
+ /**
2
+ * useSocialAuth Hook
3
+ * Provides Google and Apple Sign-In functionality
4
+ *
5
+ * Note: This hook handles the Firebase authentication part.
6
+ * The OAuth flow (expo-auth-session for Google) should be set up in the consuming app.
7
+ */
8
+
9
+ import { useState, useCallback, useEffect } from "react";
10
+ import { getFirebaseAuth } from "../../infrastructure/config/FirebaseAuthClient";
11
+ import {
12
+ googleAuthService,
13
+ type GoogleAuthConfig,
14
+ } from "../../infrastructure/services/google-auth.service";
15
+ import { appleAuthService } from "../../infrastructure/services/apple-auth.service";
16
+
17
+ /**
18
+ * Social auth configuration
19
+ */
20
+ export interface SocialAuthConfig {
21
+ google?: GoogleAuthConfig;
22
+ apple?: { enabled: boolean };
23
+ }
24
+
25
+ /**
26
+ * Social auth result
27
+ */
28
+ export interface SocialAuthResult {
29
+ success: boolean;
30
+ isNewUser?: boolean;
31
+ error?: string;
32
+ }
33
+
34
+ /**
35
+ * Hook result
36
+ */
37
+ export interface UseSocialAuthResult {
38
+ /** Sign in with Google using ID token (call after OAuth flow) */
39
+ signInWithGoogleToken: (idToken: string) => Promise<SocialAuthResult>;
40
+ /** Sign in with Apple (handles full flow) */
41
+ signInWithApple: () => Promise<SocialAuthResult>;
42
+ /** Whether Google is loading */
43
+ googleLoading: boolean;
44
+ /** Whether Apple is loading */
45
+ appleLoading: boolean;
46
+ /** Whether Google is configured */
47
+ googleConfigured: boolean;
48
+ /** Whether Apple is available */
49
+ appleAvailable: boolean;
50
+ }
51
+
52
+ /**
53
+ * Hook for social authentication
54
+ *
55
+ * Usage:
56
+ * 1. For Google: Set up expo-auth-session in your app, get idToken, then call signInWithGoogleToken
57
+ * 2. For Apple: Just call signInWithApple (handles complete flow)
58
+ */
59
+ export function useSocialAuth(config?: SocialAuthConfig): UseSocialAuthResult {
60
+ const [googleLoading, setGoogleLoading] = useState(false);
61
+ const [appleLoading, setAppleLoading] = useState(false);
62
+ const [appleAvailable, setAppleAvailable] = useState(false);
63
+
64
+ // Configure Google Auth
65
+ const googleConfig = config?.google;
66
+ const googleConfigured = !!(
67
+ googleConfig?.webClientId ||
68
+ googleConfig?.iosClientId ||
69
+ googleConfig?.androidClientId
70
+ );
71
+
72
+ // Configure Google service on mount
73
+ useEffect(() => {
74
+ if (googleConfig) {
75
+ googleAuthService.configure(googleConfig);
76
+ }
77
+ }, [googleConfig]);
78
+
79
+ // Check Apple availability on mount
80
+ useEffect(() => {
81
+ const checkApple = async () => {
82
+ const available = await appleAuthService.isAvailable();
83
+ setAppleAvailable(available && (config?.apple?.enabled ?? false));
84
+ };
85
+ checkApple();
86
+ }, [config?.apple?.enabled]);
87
+
88
+ // Sign in with Google using ID token
89
+ const signInWithGoogleToken = useCallback(
90
+ async (idToken: string): Promise<SocialAuthResult> => {
91
+ if (!googleConfigured) {
92
+ return { success: false, error: "Google Sign-In is not configured" };
93
+ }
94
+
95
+ setGoogleLoading(true);
96
+ try {
97
+ const auth = getFirebaseAuth();
98
+ if (!auth) {
99
+ return { success: false, error: "Firebase Auth not initialized" };
100
+ }
101
+
102
+ const signInResult = await googleAuthService.signInWithIdToken(
103
+ auth,
104
+ idToken,
105
+ );
106
+
107
+ return {
108
+ success: signInResult.success,
109
+ isNewUser: signInResult.isNewUser,
110
+ error: signInResult.error,
111
+ };
112
+ } catch (error) {
113
+ return {
114
+ success: false,
115
+ error: error instanceof Error ? error.message : "Google sign-in failed",
116
+ };
117
+ } finally {
118
+ setGoogleLoading(false);
119
+ }
120
+ },
121
+ [googleConfigured],
122
+ );
123
+
124
+ // Sign in with Apple
125
+ const signInWithApple = useCallback(async (): Promise<SocialAuthResult> => {
126
+ if (!appleAvailable) {
127
+ return { success: false, error: "Apple Sign-In is not available" };
128
+ }
129
+
130
+ setAppleLoading(true);
131
+ try {
132
+ const auth = getFirebaseAuth();
133
+ if (!auth) {
134
+ return { success: false, error: "Firebase Auth not initialized" };
135
+ }
136
+
137
+ const result = await appleAuthService.signIn(auth);
138
+
139
+ return {
140
+ success: result.success,
141
+ isNewUser: result.isNewUser,
142
+ error: result.error,
143
+ };
144
+ } catch (error) {
145
+ return {
146
+ success: false,
147
+ error: error instanceof Error ? error.message : "Apple sign-in failed",
148
+ };
149
+ } finally {
150
+ setAppleLoading(false);
151
+ }
152
+ }, [appleAvailable]);
153
+
154
+ return {
155
+ signInWithGoogleToken,
156
+ signInWithApple,
157
+ googleLoading,
158
+ appleLoading,
159
+ googleConfigured,
160
+ appleAvailable,
161
+ };
162
+ }
package/src/index.ts CHANGED
@@ -4,11 +4,13 @@
4
4
  * Domain-Driven Design (DDD) Architecture
5
5
  *
6
6
  * This package provides Firebase App initialization and core services:
7
+ * - Auth
7
8
  * - Analytics
8
9
  * - Crashlytics
9
10
  *
10
11
  * Usage:
11
12
  * import { initializeFirebase, getFirebaseApp } from '@umituz/react-native-firebase';
13
+ * import { useFirebaseAuth } from '@umituz/react-native-firebase';
12
14
  * import { firebaseAnalyticsService } from '@umituz/react-native-firebase';
13
15
  */
14
16
 
@@ -43,6 +45,12 @@ export type {
43
45
  ServiceInitializationResult,
44
46
  } from './infrastructure/config/FirebaseClient';
45
47
 
48
+ // =============================================================================
49
+ // AUTH MODULE
50
+ // =============================================================================
51
+
52
+ export * from './auth';
53
+
46
54
  // =============================================================================
47
55
  // ANALYTICS MODULE
48
56
  // =============================================================================