@umituz/react-native-firebase 1.13.159 → 1.13.161

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.159",
3
+ "version": "1.13.161",
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,11 +35,24 @@
35
35
  "@umituz/react-native-design-system": "*",
36
36
  "expo": ">=54.0.0",
37
37
  "expo-apple-authentication": ">=6.0.0",
38
+ "expo-auth-session": ">=5.0.0",
38
39
  "expo-crypto": ">=13.0.0",
40
+ "expo-web-browser": ">=12.0.0",
39
41
  "firebase": ">=10.0.0",
40
42
  "react": ">=19.0.0",
41
43
  "react-native": "0.81.4"
42
44
  },
45
+ "peerDependenciesMeta": {
46
+ "expo-apple-authentication": {
47
+ "optional": true
48
+ },
49
+ "expo-auth-session": {
50
+ "optional": true
51
+ },
52
+ "expo-web-browser": {
53
+ "optional": true
54
+ }
55
+ },
43
56
  "devDependencies": {
44
57
  "@expo/vector-icons": "^15.0.3",
45
58
  "@react-native-async-storage/async-storage": "^2.2.0",
package/src/auth/index.ts CHANGED
@@ -109,6 +109,14 @@ export type {
109
109
  GoogleAuthResult,
110
110
  } from './infrastructure/services/google-auth.types';
111
111
 
112
+ export {
113
+ GoogleOAuthService,
114
+ googleOAuthService,
115
+ } from './infrastructure/services/google-oauth.service';
116
+ export type {
117
+ GoogleOAuthConfig,
118
+ } from './infrastructure/services/google-oauth.service';
119
+
112
120
  export {
113
121
  AppleAuthService,
114
122
  appleAuthService,
@@ -132,6 +140,11 @@ export type {
132
140
  UseSocialAuthResult,
133
141
  } from './presentation/hooks/useSocialAuth';
134
142
 
143
+ export { useGoogleOAuth } from './presentation/hooks/useGoogleOAuth';
144
+ export type {
145
+ UseGoogleOAuthResult,
146
+ } from './presentation/hooks/useGoogleOAuth';
147
+
135
148
  // Password Management
136
149
  export {
137
150
  updateUserPassword,
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * Apple Auth Service
3
3
  * Handles Apple Sign-In with Firebase Authentication
4
+ * This service is optional and requires expo-apple-authentication to be installed
4
5
  */
5
6
 
6
7
  import {
@@ -8,7 +9,6 @@ import {
8
9
  signInWithCredential,
9
10
  type Auth,
10
11
  } from "firebase/auth";
11
- import * as AppleAuthentication from "expo-apple-authentication";
12
12
  import { Platform } from "react-native";
13
13
  import { generateNonce, hashNonce } from "./crypto.util";
14
14
  import type { AppleAuthResult } from "./apple-auth.types";
@@ -17,9 +17,22 @@ import {
17
17
  } from "./base/base-auth.service";
18
18
  import { executeAuthOperation, type Result } from "../../../domain/utils";
19
19
 
20
+ // Conditional import - expo-apple-authentication is optional
21
+ let AppleAuthentication: any = null;
22
+ let isAppleAuthAvailable = false;
23
+
24
+ try {
25
+ AppleAuthentication = require("expo-apple-authentication");
26
+ isAppleAuthAvailable = true;
27
+ } catch (error) {
28
+ // expo-apple-authentication not available - this is fine if not using Apple auth
29
+ console.info("expo-apple-authentication is not installed. Apple authentication will not be available.");
30
+ }
31
+
20
32
  export class AppleAuthService {
21
33
  async isAvailable(): Promise<boolean> {
22
34
  if (Platform.OS !== "ios") return false;
35
+ if (!isAppleAuthAvailable || !AppleAuthentication?.isAvailableAsync) return false;
23
36
  try {
24
37
  return await AppleAuthentication.isAvailableAsync();
25
38
  } catch {
@@ -43,6 +56,14 @@ export class AppleAuthService {
43
56
  }
44
57
 
45
58
  async signIn(auth: Auth): Promise<AppleAuthResult> {
59
+ if (!isAppleAuthAvailable) {
60
+ return {
61
+ success: false,
62
+ error: "expo-apple-authentication is not available. Please install expo-apple-authentication.",
63
+ code: "unavailable"
64
+ };
65
+ }
66
+
46
67
  const isAvailable = await this.isAvailable();
47
68
  if (!isAvailable) {
48
69
  return {
@@ -0,0 +1,125 @@
1
+ /**
2
+ * useGoogleOAuth Hook
3
+ * Handles Google OAuth flow using expo-auth-session and Firebase auth
4
+ * This hook is optional and requires expo-auth-session to be installed
5
+ */
6
+
7
+ import { useState, useCallback, useEffect } from "react";
8
+ import { googleOAuthService } from "../../infrastructure/services/google-oauth.service";
9
+ import { getFirebaseAuth } from "../../infrastructure/config/FirebaseAuthClient";
10
+ import type { GoogleOAuthConfig } from "../../infrastructure/services/google-oauth.service";
11
+
12
+ export interface UseGoogleOAuthResult {
13
+ signInWithGoogle: () => Promise<SocialAuthResult>;
14
+ googleLoading: boolean;
15
+ googleConfigured: boolean;
16
+ googleAvailable: boolean;
17
+ googleError: string | null;
18
+ }
19
+
20
+ export interface SocialAuthResult {
21
+ success: boolean;
22
+ isNewUser?: boolean;
23
+ error?: string;
24
+ }
25
+
26
+ /**
27
+ * Hook for Google OAuth authentication
28
+ * Requires expo-auth-session and expo-web-browser to be installed
29
+ */
30
+ export function useGoogleOAuth(config?: GoogleOAuthConfig): UseGoogleOAuthResult {
31
+ const [isLoading, setIsLoading] = useState(false);
32
+ const [googleError, setGoogleError] = useState<string | null>(null);
33
+
34
+ const googleAvailable = googleOAuthService.isAvailable();
35
+ const googleConfigured = googleOAuthService.isConfigured(config);
36
+
37
+ // Create auth request - this will return null values if expo-auth-session is not available
38
+ const authRequest = googleOAuthService.createAuthRequest(config);
39
+ const { request, response, promptAsync } = authRequest;
40
+
41
+ // Handle OAuth response
42
+ useEffect(() => {
43
+ if (!googleAvailable || !response) return;
44
+
45
+ const handleResponse = async () => {
46
+ if (response.type === "success" && response.authentication?.idToken) {
47
+ setIsLoading(true);
48
+ setGoogleError(null);
49
+
50
+ try {
51
+ const auth = getFirebaseAuth();
52
+ if (!auth) {
53
+ setGoogleError("Firebase Auth not initialized");
54
+ return;
55
+ }
56
+
57
+ await googleOAuthService.signInWithOAuth(
58
+ auth,
59
+ config,
60
+ async () => response
61
+ );
62
+ } catch (error) {
63
+ setGoogleError(
64
+ error instanceof Error ? error.message : "Firebase sign-in failed"
65
+ );
66
+ } finally {
67
+ setIsLoading(false);
68
+ }
69
+ } else if (response.type === "error") {
70
+ setGoogleError("Google authentication failed");
71
+ setIsLoading(false);
72
+ }
73
+ };
74
+
75
+ handleResponse();
76
+ }, [response, googleAvailable, config]);
77
+
78
+ const signInWithGoogle = useCallback(async (): Promise<SocialAuthResult> => {
79
+ if (!googleAvailable) {
80
+ const error = "expo-auth-session is not available. Please install expo-auth-session and expo-web-browser.";
81
+ setGoogleError(error);
82
+ return { success: false, error };
83
+ }
84
+
85
+ if (!googleConfigured) {
86
+ const error = "Google Sign-In is not configured. Please provide valid client IDs.";
87
+ setGoogleError(error);
88
+ return { success: false, error };
89
+ }
90
+
91
+ if (!request || !promptAsync) {
92
+ const error = "Google Sign-In not ready";
93
+ setGoogleError(error);
94
+ return { success: false, error };
95
+ }
96
+
97
+ setIsLoading(true);
98
+ setGoogleError(null);
99
+
100
+ try {
101
+ const auth = getFirebaseAuth();
102
+ if (!auth) {
103
+ const error = "Firebase Auth not initialized";
104
+ setGoogleError(error);
105
+ return { success: false, error };
106
+ }
107
+
108
+ return await googleOAuthService.signInWithOAuth(auth, config, promptAsync);
109
+ } catch (error) {
110
+ const errorMessage = error instanceof Error ? error.message : "Google sign-in failed";
111
+ setGoogleError(errorMessage);
112
+ return { success: false, error: errorMessage };
113
+ } finally {
114
+ setIsLoading(false);
115
+ }
116
+ }, [googleAvailable, googleConfigured, request, promptAsync, config]);
117
+
118
+ return {
119
+ signInWithGoogle,
120
+ googleLoading: isLoading,
121
+ googleConfigured,
122
+ googleAvailable,
123
+ googleError,
124
+ };
125
+ }
@@ -2,11 +2,14 @@
2
2
  * Base Repository - Core Firestore Operations
3
3
  *
4
4
  * Provides base functionality for all Firestore repositories.
5
- * Handles initialization checks, error handling, and quota tracking.
5
+ * Standard pattern: users/{userId}/{collectionName}
6
+ *
7
+ * Subclasses must provide collectionName via constructor.
8
+ * This class handles all path resolution, eliminating need for FirestorePathResolver.
6
9
  */
7
10
 
8
- import type { Firestore } from 'firebase/firestore';
9
- import { getFirestore } from 'firebase/firestore';
11
+ import type { Firestore, CollectionReference, DocumentReference, DocumentData } from 'firebase/firestore';
12
+ import { getFirestore, collection, doc } from 'firebase/firestore';
10
13
  import { isQuotaError as checkQuotaError } from '../../utils/quota-error-detector.util';
11
14
 
12
15
  /**
@@ -23,6 +26,11 @@ export enum RepositoryState {
23
26
  */
24
27
  export abstract class BaseRepository {
25
28
  protected state: RepositoryState = RepositoryState.ACTIVE;
29
+ protected readonly collectionName: string;
30
+
31
+ constructor(collectionName: string) {
32
+ this.collectionName = collectionName;
33
+ }
26
34
 
27
35
  /**
28
36
  * Get the Firestore instance
@@ -35,6 +43,33 @@ export abstract class BaseRepository {
35
43
  return getFirestore();
36
44
  }
37
45
 
46
+ /**
47
+ * Get user collection reference
48
+ * Pattern: users/{userId}/{collectionName}
49
+ *
50
+ * @param userId User identifier
51
+ * @returns CollectionReference or null if db not initialized
52
+ */
53
+ protected getUserCollection(userId: string): CollectionReference<DocumentData> | null {
54
+ const db = this.getDb();
55
+ if (!db) return null;
56
+ return collection(db, 'users', userId, this.collectionName);
57
+ }
58
+
59
+ /**
60
+ * Get document reference
61
+ * Pattern: users/{userId}/{collectionName}/{documentId}
62
+ *
63
+ * @param userId User identifier
64
+ * @param documentId Document identifier
65
+ * @returns DocumentReference or null if db not initialized
66
+ */
67
+ protected getDocRef(userId: string, documentId: string): DocumentReference<DocumentData> | null {
68
+ const db = this.getDb();
69
+ if (!db) return null;
70
+ return doc(db, 'users', userId, this.collectionName, documentId);
71
+ }
72
+
38
73
  /**
39
74
  * Check if Firestore is initialized
40
75
  */
@@ -5,25 +5,25 @@ import { collection, doc } from "firebase/firestore";
5
5
  * Resolves Firestore paths for user collections
6
6
  * Standard pattern: users/{userId}/{collectionName}
7
7
  *
8
- * Uses lazy initialization to support Firebase initialization after module load
8
+ * Stateless design: db is passed to methods, not stored in constructor
9
+ * This eliminates initialization timing issues and makes testing easier
9
10
  * This class is designed to be used across hundreds of apps.
10
11
  * All user data MUST be under users/{userId}/ for consistency.
11
12
  */
12
13
  export class FirestorePathResolver {
13
14
  constructor(
14
15
  private readonly collectionName: string,
15
- private readonly getDb: () => Firestore | null,
16
16
  ) { }
17
17
 
18
18
  /**
19
19
  * Get collection reference for a user
20
20
  * Pattern: users/{userId}/{collectionName}
21
21
  *
22
+ * @param db Firestore instance
22
23
  * @param userId User identifier
23
24
  * @returns CollectionReference or null if db not initialized
24
25
  */
25
- getUserCollection(userId: string): CollectionReference<DocumentData> | null {
26
- const db = this.getDb();
26
+ getUserCollection(db: Firestore | null, userId: string): CollectionReference<DocumentData> | null {
27
27
  if (!db) return null;
28
28
  return collection(db, "users", userId, this.collectionName);
29
29
  }
@@ -32,12 +32,12 @@ export class FirestorePathResolver {
32
32
  * Get document reference for a specific item
33
33
  * Pattern: users/{userId}/{collectionName}/{documentId}
34
34
  *
35
+ * @param db Firestore instance
35
36
  * @param userId User identifier
36
37
  * @param documentId Document identifier
37
38
  * @returns DocumentReference or null if db not initialized
38
39
  */
39
- getDocRef(userId: string, documentId: string): DocumentReference<DocumentData> | null {
40
- const db = this.getDb();
40
+ getDocRef(db: Firestore | null, userId: string, documentId: string): DocumentReference<DocumentData> | null {
41
41
  if (!db) return null;
42
42
  return doc(db, "users", userId, this.collectionName, documentId);
43
43
  }