@umituz/react-native-firebase 1.13.159 → 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 +14 -1
- package/src/auth/index.ts +13 -0
- package/src/auth/infrastructure/services/apple-auth.service.ts +22 -1
- package/src/auth/presentation/hooks/useGoogleOAuth.ts +125 -0
- package/src/firestore/infrastructure/repositories/BaseRepository.ts +38 -3
- package/src/firestore/utils/path-resolver.ts +6 -6
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-firebase",
|
|
3
|
-
"version": "1.13.
|
|
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,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
|
-
*
|
|
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
|
-
*
|
|
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
|
}
|