@umituz/react-native-firebase 1.13.27 → 1.13.29

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.27",
3
+ "version": "1.13.29",
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",
@@ -35,14 +35,23 @@
35
35
  "expo-crypto": ">=13.0.0",
36
36
  "firebase": ">=10.0.0",
37
37
  "react": ">=18.2.0",
38
- "react-native": ">=0.74.0"
38
+ "react-native": ">=0.74.0",
39
+ "zustand": ">=5.0.0"
39
40
  },
40
41
  "devDependencies": {
42
+ "@react-native-async-storage/async-storage": "^2.2.0",
43
+ "@sentry/react-native": "^7.8.0",
44
+ "@sentry/types": "^10.32.1",
45
+ "@types/jest": "^30.0.0",
41
46
  "@types/react": "~19.1.10",
47
+ "@umituz/react-native-sentry": "^1.4.2",
48
+ "expo-apple-authentication": "^8.0.8",
49
+ "expo-crypto": "^15.0.8",
42
50
  "firebase": "^12.6.0",
43
51
  "react": "19.1.0",
44
52
  "react-native": "0.81.5",
45
- "typescript": "~5.9.2"
53
+ "typescript": "~5.9.2",
54
+ "zustand": "^5.0.0"
46
55
  },
47
56
  "publishConfig": {
48
57
  "access": "public"
@@ -167,7 +167,10 @@ export class AppleAuthService {
167
167
  let result = "";
168
168
 
169
169
  for (let i = 0; i < randomBytes.length; i++) {
170
- result += chars.charAt(randomBytes[i] % chars.length);
170
+ const byte = randomBytes[i];
171
+ if (byte !== undefined) {
172
+ result += chars.charAt(byte % chars.length);
173
+ }
171
174
  }
172
175
 
173
176
  return result;
@@ -126,7 +126,10 @@ async function generateNonce(length: number = 32): Promise<string> {
126
126
  let result = "";
127
127
 
128
128
  for (let i = 0; i < randomBytes.length; i++) {
129
- result += chars.charAt(randomBytes[i] % chars.length);
129
+ const byte = randomBytes[i];
130
+ if (byte !== undefined) {
131
+ result += chars.charAt(byte % chars.length);
132
+ }
130
133
  }
131
134
 
132
135
  return result;
@@ -223,7 +226,7 @@ export async function getAppleReauthCredential(): Promise<{
223
226
  */
224
227
  export async function reauthenticateWithApple(user: User): Promise<ReauthenticationResult> {
225
228
  const result = await getAppleReauthCredential();
226
-
229
+
227
230
  if (!result.success || !result.credential) {
228
231
  return {
229
232
  success: false,
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Firebase Auth Store
3
+ * Shared Zustand store for Firebase Auth state
4
+ *
5
+ * CRITICAL: This store ensures only ONE auth listener is created,
6
+ * preventing performance issues from multiple subscriptions.
7
+ */
8
+
9
+ import { create } from "zustand";
10
+ import { onAuthStateChanged, type User, type Auth } from "firebase/auth";
11
+
12
+ declare const __DEV__: boolean;
13
+
14
+ interface AuthState {
15
+ user: User | null;
16
+ loading: boolean;
17
+ initialized: boolean;
18
+ listenerSetup: boolean;
19
+ }
20
+
21
+ interface AuthActions {
22
+ setupListener: (auth: Auth) => void;
23
+ cleanup: () => void;
24
+ }
25
+
26
+ type AuthStore = AuthState & AuthActions;
27
+
28
+ let unsubscribe: (() => void) | null = null;
29
+
30
+ export const useAuthStore = create<AuthStore>((set, get) => ({
31
+ user: null,
32
+ loading: true,
33
+ initialized: false,
34
+ listenerSetup: false,
35
+
36
+ setupListener: (auth: Auth) => {
37
+ const state = get();
38
+
39
+ // Only setup listener once
40
+ if (state.listenerSetup || unsubscribe) {
41
+ return;
42
+ }
43
+
44
+ set({ listenerSetup: true });
45
+
46
+ unsubscribe = onAuthStateChanged(auth, (currentUser: User | null) => {
47
+ if (__DEV__) {
48
+ console.log(
49
+ "[AuthStore] Auth state changed:",
50
+ currentUser?.uid || "null"
51
+ );
52
+ }
53
+ set({
54
+ user: currentUser,
55
+ loading: false,
56
+ initialized: true,
57
+ });
58
+ });
59
+ },
60
+
61
+ cleanup: () => {
62
+ if (unsubscribe) {
63
+ unsubscribe();
64
+ unsubscribe = null;
65
+ }
66
+ set({
67
+ user: null,
68
+ loading: true,
69
+ initialized: false,
70
+ listenerSetup: false,
71
+ });
72
+ },
73
+ }));
@@ -2,15 +2,14 @@
2
2
  * useFirebaseAuth Hook
3
3
  * React hook for Firebase Auth state management
4
4
  *
5
- * Directly uses Firebase Auth's built-in state management via onAuthStateChanged
6
- * Simple, performant, no retry mechanism needed (auth is pre-initialized)
5
+ * Uses shared Zustand store to ensure only ONE auth listener exists.
6
+ * This prevents performance issues from multiple subscriptions.
7
7
  */
8
8
 
9
- import { useEffect, useState, useRef } from "react";
10
- import { onAuthStateChanged, type User } from "firebase/auth";
9
+ import { useEffect } from "react";
10
+ import type { User } from "firebase/auth";
11
11
  import { getFirebaseAuth } from "../../infrastructure/config/FirebaseAuthClient";
12
-
13
- declare const __DEV__: boolean;
12
+ import { useAuthStore } from "../../infrastructure/stores/auth.store";
14
13
 
15
14
  export interface UseFirebaseAuthResult {
16
15
  /** Current authenticated user from Firebase Auth */
@@ -24,7 +23,7 @@ export interface UseFirebaseAuthResult {
24
23
  /**
25
24
  * Hook for Firebase Auth state management
26
25
  *
27
- * Directly uses Firebase Auth's built-in state management.
26
+ * Uses shared store to ensure only one listener is active.
28
27
  * Auth is pre-initialized in appInitializer, so no retry needed.
29
28
  *
30
29
  * @example
@@ -33,41 +32,18 @@ export interface UseFirebaseAuthResult {
33
32
  * ```
34
33
  */
35
34
  export function useFirebaseAuth(): UseFirebaseAuthResult {
36
- const [user, setUser] = useState<User | null>(null);
37
- const [loading, setLoading] = useState(true);
38
- const [initialized, setInitialized] = useState(false);
39
-
40
- const unsubscribeRef = useRef<(() => void) | null>(null);
35
+ const { user, loading, initialized, setupListener } = useAuthStore();
41
36
 
42
37
  useEffect(() => {
43
38
  const auth = getFirebaseAuth();
44
39
 
45
40
  if (!auth) {
46
- // Auth not available (offline mode or error)
47
- setLoading(false);
48
- setUser(null);
49
- setInitialized(false);
50
41
  return;
51
42
  }
52
43
 
53
- // Subscribe to auth state changes
54
- unsubscribeRef.current = onAuthStateChanged(auth, (currentUser: User | null) => {
55
- if (__DEV__) {
56
- console.log('[useFirebaseAuth] Auth state changed:', currentUser?.uid || 'null');
57
- }
58
- setUser(currentUser);
59
- setLoading(false);
60
- setInitialized(true);
61
- });
62
-
63
- // Cleanup on unmount
64
- return () => {
65
- if (unsubscribeRef.current) {
66
- unsubscribeRef.current();
67
- unsubscribeRef.current = null;
68
- }
69
- };
70
- }, []); // Empty deps - subscribe once on mount
44
+ // Setup listener (will only run once due to store check)
45
+ setupListener(auth);
46
+ }, [setupListener]);
71
47
 
72
48
  return {
73
49
  user,
@@ -75,4 +51,3 @@ export function useFirebaseAuth(): UseFirebaseAuthResult {
75
51
  initialized,
76
52
  };
77
53
  }
78
-
@@ -3,10 +3,11 @@
3
3
  */
4
4
 
5
5
  // Mock __DEV__ for tests
6
- export {};
6
+ export { };
7
7
 
8
8
  declare global {
9
- var __DEV__: boolean | undefined;
9
+ var mockFirestore: () => any;
10
+ var mockFirebaseError: (code: string, message: string) => any;
10
11
  }
11
12
 
12
13
  if (typeof (global as any).__DEV__ === 'undefined') {
@@ -16,20 +17,28 @@ if (typeof (global as any).__DEV__ === 'undefined') {
16
17
  // Mock console methods to avoid noise in tests
17
18
  const originalConsole = { ...console };
18
19
 
19
- beforeEach(() => {
20
- // Restore console methods before each test
21
- Object.assign(console, originalConsole);
22
- });
20
+ const globalAny = global as any;
21
+
22
+ /**
23
+ * We check if beforeEach is available before using it
24
+ * This avoids errors when running tsc or in environments without Jest
25
+ */
26
+ if (typeof globalAny.beforeEach !== 'undefined') {
27
+ globalAny.beforeEach(() => {
28
+ // Restore console methods before each test
29
+ Object.assign(console, originalConsole);
30
+ });
31
+ }
23
32
 
24
33
  // Set up global test utilities
25
- global.mockFirestore = () => ({
26
- collection: jest.fn(),
27
- doc: jest.fn(),
28
- runTransaction: jest.fn(),
29
- batch: jest.fn(),
34
+ globalAny.mockFirestore = () => ({
35
+ collection: globalAny.jest?.fn() || (() => ({})),
36
+ doc: globalAny.jest?.fn() || (() => ({})),
37
+ runTransaction: globalAny.jest?.fn() || (() => Promise.resolve()),
38
+ batch: globalAny.jest?.fn() || (() => ({})),
30
39
  });
31
40
 
32
- global.mockFirebaseError = (code: string, message: string) => {
41
+ globalAny.mockFirebaseError = (code: string, message: string) => {
33
42
  const error = new Error(message) as any;
34
43
  error.code = code;
35
44
  return error;