@umituz/react-native-firebase 1.13.54 → 1.13.56

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-firebase",
3
- "version": "1.13.54",
3
+ "version": "1.13.56",
4
4
  "description": "Unified Firebase package for React Native apps - Auth and Firestore services using Firebase JS SDK (no native modules).",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -4,9 +4,10 @@
4
4
  */
5
5
 
6
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";
7
+ import { onAuthStateChanged, type Auth, type User } from "firebase/auth";
8
+ import type { AuthCheckResult } from "../../infrastructure/services/auth-utils.service";
9
9
  import { anonymousAuthService, type AnonymousAuthResult } from "../../infrastructure/services/anonymous-auth.service";
10
+ import { createAuthStateChangeHandler, userToAuthCheckResult } from "./utils/auth-state-change.handler";
10
11
 
11
12
  declare const __DEV__: boolean;
12
13
 
@@ -36,61 +37,32 @@ export interface UseAnonymousAuthResult extends AuthCheckResult {
36
37
  * Hook for anonymous authentication
37
38
  */
38
39
  export function useAnonymousAuth(auth: Auth | null): UseAnonymousAuthResult {
39
- const [authState, setAuthState] = useState<AuthCheckResult>(() =>
40
- checkAuthState(auth),
41
- );
40
+ const [authState, setAuthState] = useState<AuthCheckResult>({
41
+ isAuthenticated: false,
42
+ isAnonymous: false,
43
+ currentUser: null,
44
+ userId: null,
45
+ });
42
46
  const [loading, setLoading] = useState(true);
43
47
  const [error, setError] = useState<Error | null>(null);
44
- const authRef = useRef(auth);
45
48
  const unsubscribeRef = useRef<(() => void) | null>(null);
46
49
 
47
- // Update ref when auth changes
48
- useEffect(() => {
49
- authRef.current = auth;
50
- }, [auth]);
51
-
52
50
  // Clear error helper
53
51
  const clearError = useCallback(() => {
54
52
  setError(null);
55
53
  }, []);
56
54
 
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
- currentUser: null,
67
- userId: null,
68
- });
69
- } else {
70
- const anonymous = user.isAnonymous === true;
71
- setAuthState({
72
- isAuthenticated: true,
73
- isAnonymous: anonymous,
74
- currentUser: user,
75
- userId: user.uid,
76
- });
77
- }
78
- setError(null);
79
- } catch (err) {
80
- const authError = err instanceof Error ? err : new Error('Auth state check failed');
81
- setError(authError);
82
- if (typeof __DEV__ !== "undefined" && __DEV__) {
83
- // eslint-disable-next-line no-console
84
- console.error("[useAnonymousAuth] Auth state change error", authError);
85
- }
86
- } finally {
87
- setLoading(false);
88
- }
89
- }, []);
55
+ // Auth state change handler
56
+ const handleAuthStateChange = useCallback((user: User | null) => {
57
+ const handler = createAuthStateChangeHandler({
58
+ setAuthState,
59
+ setLoading,
60
+ setError,
61
+ });
62
+ handler(user);
63
+ }, [setAuthState, setLoading, setError]);
90
64
 
91
65
  // Setup auth state listener
92
- // IMPORTANT: Do NOT call handleAuthStateChange() immediately!
93
- // Wait for onAuthStateChanged to fire - it will have the correct user from persistence
94
66
  useEffect(() => {
95
67
  // Cleanup previous listener
96
68
  if (unsubscribeRef.current) {
@@ -99,12 +71,7 @@ export function useAnonymousAuth(auth: Auth | null): UseAnonymousAuthResult {
99
71
  }
100
72
 
101
73
  if (!auth) {
102
- setAuthState({
103
- isAuthenticated: false,
104
- isAnonymous: false,
105
- currentUser: null,
106
- userId: null,
107
- });
74
+ setAuthState(userToAuthCheckResult(null));
108
75
  setLoading(false);
109
76
  setError(null);
110
77
  return;
@@ -114,10 +81,9 @@ export function useAnonymousAuth(auth: Auth | null): UseAnonymousAuthResult {
114
81
  setLoading(true);
115
82
 
116
83
  try {
117
- // Listen to auth state changes - this is the ONLY source of truth
118
- // The first callback will have the user restored from persistence (or null)
84
+ // Listen to auth state changes
119
85
  unsubscribeRef.current = onAuthStateChanged(auth, (user) => {
120
- if (typeof __DEV__ !== "undefined" && __DEV__) {
86
+ if (__DEV__) {
121
87
  // eslint-disable-next-line no-console
122
88
  console.log("[useAnonymousAuth] onAuthStateChanged fired", {
123
89
  hasUser: !!user,
@@ -126,14 +92,13 @@ export function useAnonymousAuth(auth: Auth | null): UseAnonymousAuthResult {
126
92
  email: user?.email,
127
93
  });
128
94
  }
129
- // IMPORTANT: Pass the user from the callback, not auth.currentUser!
130
95
  handleAuthStateChange(user);
131
96
  });
132
97
  } catch (err) {
133
98
  const authError = err instanceof Error ? err : new Error('Auth listener setup failed');
134
99
  setError(authError);
135
100
  setLoading(false);
136
- if (typeof __DEV__ !== "undefined" && __DEV__) {
101
+ if (__DEV__) {
137
102
  // eslint-disable-next-line no-console
138
103
  console.error("[useAnonymousAuth] Auth listener setup error", authError);
139
104
  }
@@ -161,12 +126,9 @@ export function useAnonymousAuth(auth: Auth | null): UseAnonymousAuthResult {
161
126
 
162
127
  try {
163
128
  const result = await anonymousAuthService.signInAnonymously(auth);
164
-
165
- // Update auth state after successful sign in
166
- // Pass the user from the result, not from auth.currentUser
167
129
  handleAuthStateChange(result.user);
168
130
 
169
- if (typeof __DEV__ !== "undefined" && __DEV__) {
131
+ if (__DEV__) {
170
132
  // eslint-disable-next-line no-console
171
133
  console.log("[useAnonymousAuth] Successfully signed in anonymously", {
172
134
  uid: result.anonymousUser.uid,
@@ -178,7 +140,7 @@ export function useAnonymousAuth(auth: Auth | null): UseAnonymousAuthResult {
178
140
  } catch (err) {
179
141
  const authError = err instanceof Error ? err : new Error('Anonymous sign in failed');
180
142
  setError(authError);
181
- if (typeof __DEV__ !== "undefined" && __DEV__) {
143
+ if (__DEV__) {
182
144
  // eslint-disable-next-line no-console
183
145
  console.error("[useAnonymousAuth] Sign in error", authError);
184
146
  }
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Auth State Change Handler
3
+ * Single Responsibility: Handle authentication state changes
4
+ */
5
+
6
+ import type { User } from 'firebase/auth';
7
+ import type { AuthCheckResult } from '../../../infrastructure/services/auth-utils.service';
8
+
9
+ declare const __DEV__: boolean;
10
+
11
+ /**
12
+ * Convert Firebase User to AuthCheckResult
13
+ * @param user - Firebase user or null
14
+ * @returns AuthCheckResult
15
+ */
16
+ export function userToAuthCheckResult(user: User | null): AuthCheckResult {
17
+ if (!user) {
18
+ return {
19
+ isAuthenticated: false,
20
+ isAnonymous: false,
21
+ currentUser: null,
22
+ userId: null,
23
+ };
24
+ }
25
+
26
+ return {
27
+ isAuthenticated: true,
28
+ isAnonymous: user.isAnonymous === true,
29
+ currentUser: user,
30
+ userId: user.uid,
31
+ };
32
+ }
33
+
34
+ /**
35
+ * Create auth state change handler callback
36
+ * Returns a function that can be used with onAuthStateChanged
37
+ *
38
+ * @param setAuthState - State setter for auth state
39
+ * @param setLoading - State setter for loading
40
+ * @param setError - State setter for errors
41
+ * @returns Callback function for auth state changes
42
+ */
43
+ export type AuthStateChangeHandler = (user: User | null) => void;
44
+
45
+ export interface CreateAuthStateChangeHandlerParams {
46
+ setAuthState: (state: AuthCheckResult) => void;
47
+ setLoading: (loading: boolean) => void;
48
+ setError: (error: Error | null) => void;
49
+ }
50
+
51
+ export function createAuthStateChangeHandler(
52
+ params: CreateAuthStateChangeHandlerParams
53
+ ): AuthStateChangeHandler {
54
+ const { setAuthState, setLoading, setError } = params;
55
+
56
+ return (user: User | null) => {
57
+ try {
58
+ const authState = userToAuthCheckResult(user);
59
+ setAuthState(authState);
60
+ setError(null);
61
+ } catch (err) {
62
+ const authError =
63
+ err instanceof Error ? err : new Error('Auth state check failed');
64
+ setError(authError);
65
+
66
+ if (__DEV__) {
67
+ // eslint-disable-next-line no-console
68
+ console.error('[AuthStateHandler] Auth state change error', authError);
69
+ }
70
+ } finally {
71
+ setLoading(false);
72
+ }
73
+ };
74
+ }
@@ -6,11 +6,6 @@
6
6
  *
7
7
  * IMPORTANT: This package requires Firebase App to be initialized first.
8
8
  * Use @umituz/react-native-firebase to initialize Firebase App.
9
- *
10
- * SOLID Principles:
11
- * - Single Responsibility: Only manages Firestore initialization
12
- * - Open/Closed: Extensible through configuration, closed for modification
13
- * - Dependency Inversion: Depends on Firebase App from @umituz/react-native-firebase
14
9
  */
15
10
 
16
11
  // eslint-disable-next-line no-console
@@ -29,13 +24,8 @@ class FirestoreClientSingleton {
29
24
  private firestore: Firestore | null = null;
30
25
  private initializationError: string | null = null;
31
26
 
32
- private constructor() {
33
- // Private constructor to enforce singleton pattern
34
- }
27
+ private constructor() {}
35
28
 
36
- /**
37
- * Get singleton instance
38
- */
39
29
  static getInstance(): FirestoreClientSingleton {
40
30
  if (!FirestoreClientSingleton.instance) {
41
31
  FirestoreClientSingleton.instance = new FirestoreClientSingleton();
@@ -43,26 +33,13 @@ class FirestoreClientSingleton {
43
33
  return FirestoreClientSingleton.instance;
44
34
  }
45
35
 
46
- /**
47
- * Initialize Firestore
48
- * Requires Firebase App to be initialized first via @umituz/react-native-firebase
49
- *
50
- * @returns Firestore instance or null if initialization fails
51
- */
52
36
  initialize(): Firestore | null {
53
- if (this.firestore) {
54
- return this.firestore;
55
- }
56
- if (this.initializationError) {
57
- return null;
58
- }
59
- try {
60
- const app = getFirebaseApp(); // Get the core Firebase App
37
+ if (this.firestore) return this.firestore;
38
+ if (this.initializationError) return null;
61
39
 
62
- // Return null if Firebase App is not available (offline mode)
63
- if (!app) {
64
- return null;
65
- }
40
+ try {
41
+ const app = getFirebaseApp();
42
+ if (!app) return null;
66
43
 
67
44
  this.firestore = FirebaseFirestoreInitializer.initialize(app);
68
45
  return this.firestore;
@@ -75,61 +52,22 @@ class FirestoreClientSingleton {
75
52
  }
76
53
  }
77
54
 
78
- /**
79
- * Get Firestore instance
80
- * Auto-initializes if Firebase App is available
81
- * Returns null if config is not available (offline mode - no error)
82
- * @returns Firestore instance or null if not initialized
83
- */
84
55
  getFirestore(): Firestore | null {
85
- // Auto-initialize if not already initialized
86
56
  if (!this.firestore && !this.initializationError) {
87
- try {
88
- // Try to get Firebase App (will auto-initialize if config is available)
89
- const app = getFirebaseApp();
90
- if (typeof __DEV__ !== "undefined" && __DEV__) {
91
- // eslint-disable-next-line no-console
92
- console.log("[FirestoreClient] getFirestore - app:", !!app);
93
- }
94
- if (app) {
95
- this.initialize();
96
- }
97
- } catch (e) {
98
- // Firebase App not available, return null (offline mode)
99
- if (typeof __DEV__ !== "undefined" && __DEV__) {
100
- // eslint-disable-next-line no-console
101
- console.log("[FirestoreClient] getFirestore error:", e);
102
- }
103
- return null;
104
- }
105
- }
106
-
107
- if (typeof __DEV__ !== "undefined" && __DEV__) {
108
- // eslint-disable-next-line no-console
109
- console.log("[FirestoreClient] returning firestore:", !!this.firestore, "constructor:", this.firestore?.constructor?.name);
57
+ const app = getFirebaseApp();
58
+ if (app) this.initialize();
110
59
  }
111
- // Return null if not initialized (offline mode - no error)
112
60
  return this.firestore || null;
113
61
  }
114
62
 
115
- /**
116
- * Check if Firestore is initialized
117
- */
118
63
  isInitialized(): boolean {
119
64
  return this.firestore !== null;
120
65
  }
121
66
 
122
- /**
123
- * Get initialization error if any
124
- */
125
67
  getInitializationError(): string | null {
126
68
  return this.initializationError;
127
69
  }
128
70
 
129
- /**
130
- * Reset Firestore client instance
131
- * Useful for testing
132
- */
133
71
  reset(): void {
134
72
  this.firestore = null;
135
73
  this.initializationError = null;
@@ -138,56 +76,22 @@ class FirestoreClientSingleton {
138
76
 
139
77
  export const firestoreClient = FirestoreClientSingleton.getInstance();
140
78
 
141
- /**
142
- * Initialize Firestore
143
- * Requires Firebase App to be initialized first via @umituz/react-native-firebase
144
- *
145
- * @returns Firestore instance or null if initialization fails
146
- *
147
- * @example
148
- * ```typescript
149
- * import { initializeFirebase } from '@umituz/react-native-firebase';
150
- * import { initializeFirestore } from '@umituz/react-native-firestore';
151
- *
152
- * // Initialize Firebase App first
153
- * const app = initializeFirebase(config);
154
- *
155
- * // Then initialize Firestore
156
- * const db = initializeFirestore();
157
- * ```
158
- */
159
79
  export function initializeFirestore(): Firestore | null {
160
80
  return firestoreClient.initialize();
161
81
  }
162
82
 
163
- /**
164
- * Get Firestore instance
165
- * Auto-initializes if Firebase App is available
166
- * Returns null if config is not available (offline mode - no error)
167
- * @returns Firestore instance or null if not initialized
168
- */
169
83
  export function getFirestore(): Firestore | null {
170
84
  return firestoreClient.getFirestore();
171
85
  }
172
86
 
173
- /**
174
- * Check if Firestore is initialized
175
- */
176
87
  export function isFirestoreInitialized(): boolean {
177
88
  return firestoreClient.isInitialized();
178
89
  }
179
90
 
180
- /**
181
- * Get Firestore initialization error if any
182
- */
183
91
  export function getFirestoreInitializationError(): string | null {
184
92
  return firestoreClient.getInitializationError();
185
93
  }
186
94
 
187
- /**
188
- * Reset Firestore client instance
189
- * Useful for testing
190
- */
191
95
  export function resetFirestoreClient(): void {
192
96
  firestoreClient.reset();
193
97
  }
@@ -1,19 +1,6 @@
1
1
  /**
2
2
  * Query Builder Utility
3
3
  * Single Responsibility: Build Firestore queries with advanced filtering
4
- *
5
- * App-agnostic utility for building Firestore queries.
6
- * Supports:
7
- * - Firestore 'in' operator (up to 10 values)
8
- * - Firestore 'or' operator (for >10 values via chunking)
9
- * - Single value filtering
10
- * - Multiple field filtering
11
- * - Date range filtering
12
- * - Sorting
13
- * - Limiting
14
- *
15
- * This utility is designed to be used across hundreds of apps.
16
- * It provides a consistent interface for Firestore query building.
17
4
  */
18
5
 
19
6
  import {
@@ -49,97 +36,58 @@ export interface QueryBuilderOptions {
49
36
  order?: "asc" | "desc";
50
37
  };
51
38
  limitValue?: number;
52
- /**
53
- * Cursor value for pagination (timestamp in milliseconds)
54
- * Used with startAfter for cursor-based pagination
55
- */
56
39
  cursorValue?: number;
57
40
  }
58
41
 
59
42
  const MAX_IN_OPERATOR_VALUES = 10;
60
43
 
61
44
  /**
62
- * Build Firestore query with advanced filtering support
63
- *
64
- * @param db - Firestore database instance
65
- * @param options - Query builder options
66
- * @returns Firestore Query object
45
+ * Apply date range filters to query
67
46
  */
68
- export function buildQuery(
69
- db: Firestore,
70
- options: QueryBuilderOptions,
71
- ): Query {
72
- const {
73
- collectionName,
74
- baseFilters = [],
75
- dateRange,
76
- sort,
77
- limitValue,
78
- cursorValue,
79
- } = options;
80
-
81
- const collectionRef = collection(db, collectionName);
82
- let q: Query = collectionRef;
83
-
84
- // Apply base filters
85
- for (const filter of baseFilters) {
86
- q = applyFieldFilter(q, filter);
87
- }
47
+ function applyDateRange(q: Query, dateRange: QueryBuilderOptions['dateRange']): Query {
48
+ if (!dateRange) return q;
88
49
 
89
- // Apply date range filters
90
- if (dateRange) {
91
- if (dateRange.startDate) {
92
- q = query(
93
- q,
94
- where(
95
- dateRange.field,
96
- ">=",
97
- Timestamp.fromMillis(dateRange.startDate),
98
- ),
99
- );
100
- }
101
- if (dateRange.endDate) {
102
- q = query(
103
- q,
104
- where(
105
- dateRange.field,
106
- "<=",
107
- Timestamp.fromMillis(dateRange.endDate),
108
- ),
109
- );
110
- }
50
+ if (dateRange.startDate) {
51
+ q = query(q, where(dateRange.field, ">=", Timestamp.fromMillis(dateRange.startDate)));
111
52
  }
112
-
113
- // Apply sorting
114
- if (sort) {
115
- const sortOrder = sort.order || "desc";
116
- q = query(q, orderBy(sort.field, sortOrder));
53
+ if (dateRange.endDate) {
54
+ q = query(q, where(dateRange.field, "<=", Timestamp.fromMillis(dateRange.endDate)));
117
55
  }
56
+ return q;
57
+ }
118
58
 
119
- // Apply cursor for pagination (must come after orderBy)
120
- if (cursorValue !== undefined) {
121
- q = query(q, startAfter(Timestamp.fromMillis(cursorValue)));
122
- }
59
+ /**
60
+ * Apply sorting to query
61
+ */
62
+ function applySort(q: Query, sort?: QueryBuilderOptions['sort']): Query {
63
+ if (!sort) return q;
64
+ const sortOrder = sort.order || "desc";
65
+ return query(q, orderBy(sort.field, sortOrder));
66
+ }
123
67
 
124
- // Apply limit
125
- if (limitValue !== undefined) {
126
- q = query(q, limitQuery(limitValue));
127
- }
68
+ /**
69
+ * Apply cursor for pagination
70
+ */
71
+ function applyCursor(q: Query, cursorValue?: number): Query {
72
+ if (cursorValue === undefined) return q;
73
+ return query(q, startAfter(Timestamp.fromMillis(cursorValue)));
74
+ }
128
75
 
129
- return q;
76
+ /**
77
+ * Apply limit to query
78
+ */
79
+ function applyLimit(q: Query, limitValue?: number): Query {
80
+ if (limitValue === undefined) return q;
81
+ return query(q, limitQuery(limitValue));
130
82
  }
131
83
 
132
84
  /**
133
- * Apply field filter with support for 'in' operator and chunking
134
- * Handles arrays by using 'in' operator (up to 10 values)
135
- * For arrays >10 values, splits into chunks and uses 'or' operator
85
+ * Apply field filter with 'in' operator and chunking support
136
86
  */
137
87
  function applyFieldFilter(q: Query, filter: FieldFilter): Query {
138
88
  const { field, operator, value } = filter;
139
89
 
140
- // Handle 'in' operator with array values
141
90
  if (operator === "in" && Array.isArray(value)) {
142
- // Firestore 'in' operator supports up to 10 values
143
91
  if (value.length <= MAX_IN_OPERATOR_VALUES) {
144
92
  return query(q, where(field, "in", value));
145
93
  }
@@ -154,35 +102,58 @@ function applyFieldFilter(q: Query, filter: FieldFilter): Query {
154
102
  return query(q, or(...orConditions));
155
103
  }
156
104
 
157
- // Standard filter
158
105
  return query(q, where(field, operator, value));
159
106
  }
160
107
 
161
108
  /**
162
- * Helper: Create a field filter for 'in' operator
163
- * Automatically handles chunking if array >10 values
109
+ * Build Firestore query with advanced filtering support
110
+ */
111
+ export function buildQuery(
112
+ db: Firestore,
113
+ options: QueryBuilderOptions,
114
+ ): Query {
115
+ const {
116
+ collectionName,
117
+ baseFilters = [],
118
+ dateRange,
119
+ sort,
120
+ limitValue,
121
+ cursorValue,
122
+ } = options;
123
+
124
+ const collectionRef = collection(db, collectionName);
125
+ let q: Query = collectionRef;
126
+
127
+ // Apply base filters
128
+ for (const filter of baseFilters) {
129
+ q = applyFieldFilter(q, filter);
130
+ }
131
+
132
+ // Apply modifiers in correct order
133
+ q = applyDateRange(q, dateRange);
134
+ q = applySort(q, sort);
135
+ q = applyCursor(q, cursorValue);
136
+ q = applyLimit(q, limitValue);
137
+
138
+ return q;
139
+ }
140
+
141
+ /**
142
+ * Create a field filter for 'in' operator
164
143
  */
165
144
  export function createInFilter(
166
145
  field: string,
167
146
  values: string[] | number[],
168
147
  ): FieldFilter {
169
- return {
170
- field,
171
- operator: "in",
172
- value: values,
173
- };
148
+ return { field, operator: "in", value: values };
174
149
  }
175
150
 
176
151
  /**
177
- * Helper: Create a field filter for equality
152
+ * Create a field filter for equality
178
153
  */
179
154
  export function createEqualFilter(
180
155
  field: string,
181
156
  value: string | number | boolean,
182
157
  ): FieldFilter {
183
- return {
184
- field,
185
- operator: "==",
186
- value,
187
- };
158
+ return { field, operator: "==", value };
188
159
  }