@umituz/react-native-firebase 1.13.158 → 1.13.160

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.158",
3
+ "version": "1.13.160",
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,173 @@
1
+ /**
2
+ * Google OAuth Service
3
+ * Handles Google OAuth flow using expo-auth-session
4
+ * This service is optional and requires expo-auth-session to be installed
5
+ */
6
+
7
+ import type { Auth } from "firebase/auth";
8
+ import type { GoogleAuthResult } from "./google-auth.types";
9
+ import { googleAuthService } from "./google-auth.service";
10
+
11
+ // Conditional imports - these packages are optional
12
+ let ExpoAuthSession: any = null;
13
+ let ExpoWebBrowser: any = null;
14
+ let isExpoAuthAvailable = false;
15
+
16
+ try {
17
+ ExpoAuthSession = require("expo-auth-session/providers/google");
18
+ ExpoWebBrowser = require("expo-web-browser");
19
+ isExpoAuthAvailable = true;
20
+
21
+ // Complete auth session if available
22
+ if (ExpoWebBrowser?.maybeCompleteAuthSession) {
23
+ ExpoWebBrowser.maybeCompleteAuthSession();
24
+ }
25
+ } catch (error) {
26
+ // expo-auth-session not available - this is fine if not using Google OAuth
27
+ console.info("expo-auth-session is not installed. Google OAuth will not be available.");
28
+ }
29
+
30
+ export interface GoogleOAuthConfig {
31
+ iosClientId?: string;
32
+ webClientId?: string;
33
+ androidClientId?: string;
34
+ }
35
+
36
+ export interface GoogleOAuthHookResult {
37
+ request: any;
38
+ response: any;
39
+ promptAsync: (() => Promise<any>) | null;
40
+ }
41
+
42
+ const PLACEHOLDER_CLIENT_ID = "000000000000-placeholder.apps.googleusercontent.com";
43
+
44
+ function validateGoogleConfig(config?: GoogleOAuthConfig): boolean {
45
+ if (!config) return false;
46
+ return !!(
47
+ (config.iosClientId && config.iosClientId !== PLACEHOLDER_CLIENT_ID) ||
48
+ (config.webClientId && config.webClientId !== PLACEHOLDER_CLIENT_ID) ||
49
+ (config.androidClientId && config.androidClientId !== PLACEHOLDER_CLIENT_ID)
50
+ );
51
+ }
52
+
53
+ /**
54
+ * Google OAuth Service
55
+ * Provides OAuth flow using expo-auth-session
56
+ */
57
+ export class GoogleOAuthService {
58
+ /**
59
+ * Check if expo-auth-session is available
60
+ */
61
+ isAvailable(): boolean {
62
+ return isExpoAuthAvailable;
63
+ }
64
+
65
+ /**
66
+ * Check if Google OAuth is configured
67
+ */
68
+ isConfigured(config?: GoogleOAuthConfig): boolean {
69
+ return validateGoogleConfig(config);
70
+ }
71
+
72
+ /**
73
+ * Create auth request hook
74
+ * This mimics the behavior of expo-auth-session's useAuthRequest hook
75
+ */
76
+ createAuthRequest(config?: GoogleOAuthConfig): GoogleOAuthHookResult {
77
+ if (!isExpoAuthAvailable || !ExpoAuthSession?.useAuthRequest) {
78
+ return {
79
+ request: null,
80
+ response: null,
81
+ promptAsync: null,
82
+ };
83
+ }
84
+
85
+ try {
86
+ const [request, response, promptAsync] = ExpoAuthSession.useAuthRequest({
87
+ iosClientId: config?.iosClientId || PLACEHOLDER_CLIENT_ID,
88
+ webClientId: config?.webClientId || PLACEHOLDER_CLIENT_ID,
89
+ androidClientId: config?.androidClientId || PLACEHOLDER_CLIENT_ID,
90
+ });
91
+
92
+ return {
93
+ request: request ?? null,
94
+ response: response ?? null,
95
+ promptAsync: promptAsync ?? null,
96
+ };
97
+ } catch (error) {
98
+ console.error("Failed to create Google auth request:", error);
99
+ return {
100
+ request: null,
101
+ response: null,
102
+ promptAsync: null,
103
+ };
104
+ }
105
+ }
106
+
107
+ /**
108
+ * Sign in with Google using OAuth flow
109
+ */
110
+ async signInWithOAuth(
111
+ auth: Auth,
112
+ config?: GoogleOAuthConfig,
113
+ promptAsync?: () => Promise<any>
114
+ ): Promise<GoogleAuthResult> {
115
+ if (!isExpoAuthAvailable) {
116
+ return {
117
+ success: false,
118
+ error: "expo-auth-session is not available. Please install expo-auth-session and expo-web-browser.",
119
+ code: "unavailable",
120
+ };
121
+ }
122
+
123
+ if (!this.isConfigured(config)) {
124
+ return {
125
+ success: false,
126
+ error: "Google Sign-In is not configured. Please provide valid client IDs.",
127
+ code: "not-configured",
128
+ };
129
+ }
130
+
131
+ if (!promptAsync) {
132
+ return {
133
+ success: false,
134
+ error: "Google Sign-In not ready. No promptAsync function provided.",
135
+ code: "not-ready",
136
+ };
137
+ }
138
+
139
+ try {
140
+ const result = await promptAsync();
141
+
142
+ if (result.type === "success" && result.authentication?.idToken) {
143
+ // Use the existing google auth service to sign in with the token
144
+ return await googleAuthService.signInWithIdToken(
145
+ auth,
146
+ result.authentication.idToken
147
+ );
148
+ }
149
+
150
+ if (result.type === "cancel") {
151
+ return {
152
+ success: false,
153
+ error: "Google Sign-In was cancelled",
154
+ code: "cancelled",
155
+ };
156
+ }
157
+
158
+ return {
159
+ success: false,
160
+ error: "Google Sign-In failed",
161
+ code: "failed",
162
+ };
163
+ } catch (error) {
164
+ return {
165
+ success: false,
166
+ error: error instanceof Error ? error.message : "Google sign-in failed",
167
+ code: "error",
168
+ };
169
+ }
170
+ }
171
+ }
172
+
173
+ export const googleOAuthService = new GoogleOAuthService();
@@ -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
  */
@@ -4,38 +4,41 @@ import { collection, doc } from "firebase/firestore";
4
4
  /**
5
5
  * Resolves Firestore paths for user collections
6
6
  * Standard pattern: users/{userId}/{collectionName}
7
- *
7
+ *
8
+ * Stateless design: db is passed to methods, not stored in constructor
9
+ * This eliminates initialization timing issues and makes testing easier
8
10
  * This class is designed to be used across hundreds of apps.
9
11
  * All user data MUST be under users/{userId}/ for consistency.
10
12
  */
11
13
  export class FirestorePathResolver {
12
14
  constructor(
13
15
  private readonly collectionName: string,
14
- private readonly db: Firestore | null,
15
16
  ) { }
16
17
 
17
18
  /**
18
19
  * Get collection reference for a user
19
20
  * Pattern: users/{userId}/{collectionName}
20
- *
21
+ *
22
+ * @param db Firestore instance
21
23
  * @param userId User identifier
22
24
  * @returns CollectionReference or null if db not initialized
23
25
  */
24
- getUserCollection(userId: string): CollectionReference<DocumentData> | null {
25
- if (!this.db) return null;
26
- return collection(this.db, "users", userId, this.collectionName);
26
+ getUserCollection(db: Firestore | null, userId: string): CollectionReference<DocumentData> | null {
27
+ if (!db) return null;
28
+ return collection(db, "users", userId, this.collectionName);
27
29
  }
28
30
 
29
31
  /**
30
32
  * Get document reference for a specific item
31
33
  * Pattern: users/{userId}/{collectionName}/{documentId}
32
- *
34
+ *
35
+ * @param db Firestore instance
33
36
  * @param userId User identifier
34
37
  * @param documentId Document identifier
35
38
  * @returns DocumentReference or null if db not initialized
36
39
  */
37
- getDocRef(userId: string, documentId: string): DocumentReference<DocumentData> | null {
38
- if (!this.db) return null;
39
- return doc(this.db, "users", userId, this.collectionName, documentId);
40
+ getDocRef(db: Firestore | null, userId: string, documentId: string): DocumentReference<DocumentData> | null {
41
+ if (!db) return null;
42
+ return doc(db, "users", userId, this.collectionName, documentId);
40
43
  }
41
44
  }