@umituz/react-native-firebase 1.0.0
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/LICENSE +22 -0
- package/README.md +215 -0
- package/package.json +59 -0
- package/src/application/ports/IFirebaseClient.ts +51 -0
- package/src/domain/errors/FirebaseError.ts +93 -0
- package/src/domain/value-objects/FirebaseConfig.ts +54 -0
- package/src/index.ts +61 -0
- package/src/infrastructure/config/FirebaseClient.ts +242 -0
- package/src/infrastructure/config/initializers/FirebaseAppInitializer.ts +46 -0
- package/src/infrastructure/config/initializers/FirebaseAuthInitializer.ts +68 -0
- package/src/infrastructure/config/initializers/FirebaseFirestoreInitializer.ts +54 -0
- package/src/infrastructure/config/validators/FirebaseConfigValidator.ts +51 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Ümit UZ
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
package/README.md
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
# @umituz/react-native-firebase
|
|
2
|
+
|
|
3
|
+
Domain-Driven Design Firebase client for React Native apps with type-safe operations and singleton pattern.
|
|
4
|
+
|
|
5
|
+
Built with **SOLID**, **DRY**, and **KISS** principles.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @umituz/react-native-firebase
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Peer Dependencies
|
|
14
|
+
|
|
15
|
+
- `firebase` >= 11.0.0
|
|
16
|
+
- `react` >= 18.2.0
|
|
17
|
+
- `react-native` >= 0.74.0
|
|
18
|
+
- `@react-native-async-storage/async-storage` >= 1.21.0
|
|
19
|
+
|
|
20
|
+
## Features
|
|
21
|
+
|
|
22
|
+
- ✅ Domain-Driven Design (DDD) architecture
|
|
23
|
+
- ✅ SOLID principles (Single Responsibility, Open/Closed, etc.)
|
|
24
|
+
- ✅ DRY (Don't Repeat Yourself)
|
|
25
|
+
- ✅ KISS (Keep It Simple, Stupid)
|
|
26
|
+
- ✅ Singleton pattern for single client instance
|
|
27
|
+
- ✅ Type-safe Firebase operations
|
|
28
|
+
- ✅ Platform-specific initialization (Web vs Native)
|
|
29
|
+
- ✅ **Security**: No .env reading - configuration must be provided by app
|
|
30
|
+
- ✅ Works with Expo and React Native CLI
|
|
31
|
+
|
|
32
|
+
## Important: Configuration
|
|
33
|
+
|
|
34
|
+
**This package does NOT read from .env files for security reasons.** You must provide configuration from your application code.
|
|
35
|
+
|
|
36
|
+
### Why?
|
|
37
|
+
|
|
38
|
+
- **Security**: Prevents accidental exposure of credentials
|
|
39
|
+
- **Flexibility**: Works with any configuration source (Constants, config files, etc.)
|
|
40
|
+
- **Multi-app support**: Same package can be used across hundreds of apps with different configs
|
|
41
|
+
|
|
42
|
+
## Usage
|
|
43
|
+
|
|
44
|
+
### 1. Initialize Firebase Client
|
|
45
|
+
|
|
46
|
+
Initialize the client early in your app (e.g., in `App.tsx` or `index.js`):
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
import { initializeFirebase } from '@umituz/react-native-firebase';
|
|
50
|
+
import Constants from 'expo-constants';
|
|
51
|
+
|
|
52
|
+
// Get configuration from your app's config source
|
|
53
|
+
const config = {
|
|
54
|
+
apiKey: Constants.expoConfig?.extra?.firebaseApiKey || process.env.EXPO_PUBLIC_FIREBASE_API_KEY!,
|
|
55
|
+
authDomain: Constants.expoConfig?.extra?.firebaseAuthDomain || process.env.EXPO_PUBLIC_FIREBASE_AUTH_DOMAIN!,
|
|
56
|
+
projectId: Constants.expoConfig?.extra?.firebaseProjectId || process.env.EXPO_PUBLIC_FIREBASE_PROJECT_ID!,
|
|
57
|
+
storageBucket: Constants.expoConfig?.extra?.firebaseStorageBucket,
|
|
58
|
+
messagingSenderId: Constants.expoConfig?.extra?.firebaseMessagingSenderId,
|
|
59
|
+
appId: Constants.expoConfig?.extra?.firebaseAppId,
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
// Initialize
|
|
63
|
+
const app = initializeFirebase(config);
|
|
64
|
+
|
|
65
|
+
if (!app) {
|
|
66
|
+
console.error('Failed to initialize Firebase');
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### 2. Use Firebase Services
|
|
71
|
+
|
|
72
|
+
#### Direct Access
|
|
73
|
+
|
|
74
|
+
```typescript
|
|
75
|
+
import { getFirebaseApp, getFirebaseAuth, getFirestore } from '@umituz/react-native-firebase';
|
|
76
|
+
|
|
77
|
+
// Get instances
|
|
78
|
+
const app = getFirebaseApp();
|
|
79
|
+
const auth = getFirebaseAuth();
|
|
80
|
+
const db = getFirestore();
|
|
81
|
+
|
|
82
|
+
// Use Firebase features
|
|
83
|
+
import { signInWithEmailAndPassword } from 'firebase/auth';
|
|
84
|
+
import { collection, getDocs } from 'firebase/firestore';
|
|
85
|
+
|
|
86
|
+
// Sign in
|
|
87
|
+
await signInWithEmailAndPassword(auth, email, password);
|
|
88
|
+
|
|
89
|
+
// Query Firestore
|
|
90
|
+
const querySnapshot = await getDocs(collection(db, 'users'));
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### 3. Error Handling
|
|
94
|
+
|
|
95
|
+
```typescript
|
|
96
|
+
import {
|
|
97
|
+
getFirebaseApp,
|
|
98
|
+
FirebaseInitializationError,
|
|
99
|
+
FirebaseConfigurationError,
|
|
100
|
+
} from '@umituz/react-native-firebase';
|
|
101
|
+
|
|
102
|
+
try {
|
|
103
|
+
const app = getFirebaseApp();
|
|
104
|
+
// Use app
|
|
105
|
+
} catch (error) {
|
|
106
|
+
if (error instanceof FirebaseInitializationError) {
|
|
107
|
+
console.error('Firebase not initialized:', error.message);
|
|
108
|
+
} else if (error instanceof FirebaseConfigurationError) {
|
|
109
|
+
console.error('Invalid configuration:', error.message);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### 4. Check Initialization Status
|
|
115
|
+
|
|
116
|
+
```typescript
|
|
117
|
+
import {
|
|
118
|
+
isFirebaseInitialized,
|
|
119
|
+
getFirebaseInitializationError,
|
|
120
|
+
} from '@umituz/react-native-firebase';
|
|
121
|
+
|
|
122
|
+
if (isFirebaseInitialized()) {
|
|
123
|
+
console.log('Firebase is ready');
|
|
124
|
+
} else {
|
|
125
|
+
const error = getFirebaseInitializationError();
|
|
126
|
+
console.error('Initialization error:', error);
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## Architecture
|
|
131
|
+
|
|
132
|
+
### SOLID Principles
|
|
133
|
+
|
|
134
|
+
- **Single Responsibility**: Each class has one clear purpose
|
|
135
|
+
- `FirebaseConfigValidator`: Only validates configuration
|
|
136
|
+
- `FirebaseAppInitializer`: Only initializes Firebase App
|
|
137
|
+
- `FirebaseAuthInitializer`: Only initializes Auth
|
|
138
|
+
- `FirebaseFirestoreInitializer`: Only initializes Firestore
|
|
139
|
+
- `FirebaseClient`: Only orchestrates initialization
|
|
140
|
+
|
|
141
|
+
- **Open/Closed**: Extensible through configuration, closed for modification
|
|
142
|
+
|
|
143
|
+
- **Dependency Inversion**: Depends on abstractions (interfaces), not concrete implementations
|
|
144
|
+
|
|
145
|
+
### DRY (Don't Repeat Yourself)
|
|
146
|
+
|
|
147
|
+
- Shared initialization logic extracted to specialized classes
|
|
148
|
+
- No code duplication across platforms
|
|
149
|
+
|
|
150
|
+
### KISS (Keep It Simple, Stupid)
|
|
151
|
+
|
|
152
|
+
- Simple, focused classes
|
|
153
|
+
- Clear responsibilities
|
|
154
|
+
- Easy to understand and maintain
|
|
155
|
+
|
|
156
|
+
## API
|
|
157
|
+
|
|
158
|
+
### Functions
|
|
159
|
+
|
|
160
|
+
- `initializeFirebase(config)`: Initialize Firebase client with configuration
|
|
161
|
+
- `getFirebaseApp()`: Get Firebase app instance (throws if not initialized)
|
|
162
|
+
- `getFirebaseAuth()`: Get Firebase Auth instance (throws if not initialized)
|
|
163
|
+
- `getFirestore()`: Get Firestore instance (throws if not initialized)
|
|
164
|
+
- `isFirebaseInitialized()`: Check if client is initialized
|
|
165
|
+
- `getFirebaseInitializationError()`: Get initialization error if any
|
|
166
|
+
- `resetFirebaseClient()`: Reset client instance (useful for testing)
|
|
167
|
+
|
|
168
|
+
### Types
|
|
169
|
+
|
|
170
|
+
- `FirebaseConfig`: Configuration interface
|
|
171
|
+
- `FirebaseApp`: Firebase app type
|
|
172
|
+
- `Auth`: Firebase Auth type
|
|
173
|
+
- `Firestore`: Firestore type
|
|
174
|
+
|
|
175
|
+
### Errors
|
|
176
|
+
|
|
177
|
+
- `FirebaseError`: Base error class
|
|
178
|
+
- `FirebaseInitializationError`: Initialization errors
|
|
179
|
+
- `FirebaseConfigurationError`: Configuration errors
|
|
180
|
+
- `FirebaseAuthError`: Authentication errors
|
|
181
|
+
- `FirebaseFirestoreError`: Firestore errors
|
|
182
|
+
|
|
183
|
+
## Integration with Expo
|
|
184
|
+
|
|
185
|
+
For Expo apps, configure in `app.config.js`:
|
|
186
|
+
|
|
187
|
+
```javascript
|
|
188
|
+
module.exports = () => {
|
|
189
|
+
return {
|
|
190
|
+
expo: {
|
|
191
|
+
// ... other config
|
|
192
|
+
extra: {
|
|
193
|
+
firebaseApiKey: process.env.EXPO_PUBLIC_FIREBASE_API_KEY,
|
|
194
|
+
firebaseAuthDomain: process.env.EXPO_PUBLIC_FIREBASE_AUTH_DOMAIN,
|
|
195
|
+
firebaseProjectId: process.env.EXPO_PUBLIC_FIREBASE_PROJECT_ID,
|
|
196
|
+
firebaseStorageBucket: process.env.EXPO_PUBLIC_FIREBASE_STORAGE_BUCKET,
|
|
197
|
+
firebaseMessagingSenderId: process.env.EXPO_PUBLIC_FIREBASE_MESSAGING_SENDER_ID,
|
|
198
|
+
firebaseAppId: process.env.EXPO_PUBLIC_FIREBASE_APP_ID,
|
|
199
|
+
},
|
|
200
|
+
},
|
|
201
|
+
};
|
|
202
|
+
};
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
## Security Best Practices
|
|
206
|
+
|
|
207
|
+
1. **Never commit credentials**: Use environment variables or secure config files
|
|
208
|
+
2. **Use proper Firebase security rules**: Configure Firestore and Storage rules
|
|
209
|
+
3. **Implement RLS**: Use Firebase security rules for data protection
|
|
210
|
+
4. **Clear user data on logout**: Always clear user data on logout (GDPR compliance)
|
|
211
|
+
|
|
212
|
+
## License
|
|
213
|
+
|
|
214
|
+
MIT
|
|
215
|
+
|
package/package.json
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@umituz/react-native-firebase",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Domain-Driven Design Firebase client for React Native apps with type-safe operations and singleton pattern",
|
|
5
|
+
"main": "./src/index.ts",
|
|
6
|
+
"types": "./src/index.ts",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"typecheck": "tsc --noEmit",
|
|
9
|
+
"lint": "tsc --noEmit",
|
|
10
|
+
"version:patch": "npm version patch -m 'chore: release v%s'",
|
|
11
|
+
"version:minor": "npm version minor -m 'chore: release v%s'",
|
|
12
|
+
"version:major": "npm version major -m 'chore: release v%s'"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"react-native",
|
|
16
|
+
"firebase",
|
|
17
|
+
"firestore",
|
|
18
|
+
"authentication",
|
|
19
|
+
"analytics",
|
|
20
|
+
"crashlytics",
|
|
21
|
+
"ddd",
|
|
22
|
+
"domain-driven-design",
|
|
23
|
+
"type-safe",
|
|
24
|
+
"singleton",
|
|
25
|
+
"solid",
|
|
26
|
+
"dry",
|
|
27
|
+
"kiss"
|
|
28
|
+
],
|
|
29
|
+
"author": "Ümit UZ <umit@umituz.com>",
|
|
30
|
+
"license": "MIT",
|
|
31
|
+
"repository": {
|
|
32
|
+
"type": "git",
|
|
33
|
+
"url": "git+https://github.com/umituz/react-native-firebase.git"
|
|
34
|
+
},
|
|
35
|
+
"peerDependencies": {
|
|
36
|
+
"firebase": ">=11.0.0",
|
|
37
|
+
"@react-native-async-storage/async-storage": ">=1.21.0",
|
|
38
|
+
"react": ">=18.2.0",
|
|
39
|
+
"react-native": ">=0.74.0"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"firebase": "^11.10.0",
|
|
43
|
+
"@react-native-async-storage/async-storage": "^1.24.0",
|
|
44
|
+
"@types/react": "^18.2.45",
|
|
45
|
+
"@types/react-native": "^0.73.0",
|
|
46
|
+
"react": "^18.2.0",
|
|
47
|
+
"react-native": "^0.74.0",
|
|
48
|
+
"typescript": "^5.3.3"
|
|
49
|
+
},
|
|
50
|
+
"publishConfig": {
|
|
51
|
+
"access": "public"
|
|
52
|
+
},
|
|
53
|
+
"files": [
|
|
54
|
+
"src",
|
|
55
|
+
"README.md",
|
|
56
|
+
"LICENSE"
|
|
57
|
+
]
|
|
58
|
+
}
|
|
59
|
+
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Firebase Client Port (Interface)
|
|
3
|
+
*
|
|
4
|
+
* Domain-Driven Design: Application layer port for Firebase client
|
|
5
|
+
* Defines the contract for Firebase client operations
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { FirebaseApp } from 'firebase/app';
|
|
9
|
+
import type { Auth } from 'firebase/auth';
|
|
10
|
+
import type { Firestore } from 'firebase/firestore';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Firebase Client Interface
|
|
14
|
+
* Defines the contract for Firebase client operations
|
|
15
|
+
*/
|
|
16
|
+
export interface IFirebaseClient {
|
|
17
|
+
/**
|
|
18
|
+
* Get the Firebase app instance
|
|
19
|
+
* @throws {FirebaseInitializationError} If client is not initialized
|
|
20
|
+
*/
|
|
21
|
+
getApp(): FirebaseApp;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Get the Firebase Auth instance
|
|
25
|
+
* @throws {FirebaseInitializationError} If client is not initialized
|
|
26
|
+
*/
|
|
27
|
+
getAuth(): Auth;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Get the Firestore instance
|
|
31
|
+
* @throws {FirebaseInitializationError} If client is not initialized
|
|
32
|
+
*/
|
|
33
|
+
getFirestore(): Firestore;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Check if client is initialized
|
|
37
|
+
*/
|
|
38
|
+
isInitialized(): boolean;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Get initialization error if any
|
|
42
|
+
*/
|
|
43
|
+
getInitializationError(): string | null;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Reset the client instance
|
|
47
|
+
* Useful for testing
|
|
48
|
+
*/
|
|
49
|
+
reset(): void;
|
|
50
|
+
}
|
|
51
|
+
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Firebase Domain Errors
|
|
3
|
+
*
|
|
4
|
+
* Domain-Driven Design: Error types for Firebase operations
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Base Firebase Error
|
|
9
|
+
*/
|
|
10
|
+
export class FirebaseError extends Error {
|
|
11
|
+
constructor(
|
|
12
|
+
message: string,
|
|
13
|
+
public readonly code?: string,
|
|
14
|
+
public readonly originalError?: unknown
|
|
15
|
+
) {
|
|
16
|
+
super(message);
|
|
17
|
+
this.name = 'FirebaseError';
|
|
18
|
+
Object.setPrototypeOf(this, FirebaseError.prototype);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Initialization Error
|
|
24
|
+
* Thrown when Firebase client fails to initialize
|
|
25
|
+
*/
|
|
26
|
+
export class FirebaseInitializationError extends FirebaseError {
|
|
27
|
+
constructor(message: string, originalError?: unknown) {
|
|
28
|
+
super(message, 'INITIALIZATION_ERROR', originalError);
|
|
29
|
+
this.name = 'FirebaseInitializationError';
|
|
30
|
+
Object.setPrototypeOf(this, FirebaseInitializationError.prototype);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Configuration Error
|
|
36
|
+
* Thrown when required configuration is missing or invalid
|
|
37
|
+
*/
|
|
38
|
+
export class FirebaseConfigurationError extends FirebaseError {
|
|
39
|
+
constructor(message: string, originalError?: unknown) {
|
|
40
|
+
super(message, 'CONFIGURATION_ERROR', originalError);
|
|
41
|
+
this.name = 'FirebaseConfigurationError';
|
|
42
|
+
Object.setPrototypeOf(this, FirebaseConfigurationError.prototype);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Analytics Error
|
|
48
|
+
* Thrown when analytics operations fail
|
|
49
|
+
*/
|
|
50
|
+
export class FirebaseAnalyticsError extends FirebaseError {
|
|
51
|
+
constructor(message: string, originalError?: unknown) {
|
|
52
|
+
super(message, 'ANALYTICS_ERROR', originalError);
|
|
53
|
+
this.name = 'FirebaseAnalyticsError';
|
|
54
|
+
Object.setPrototypeOf(this, FirebaseAnalyticsError.prototype);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Crashlytics Error
|
|
60
|
+
* Thrown when crashlytics operations fail
|
|
61
|
+
*/
|
|
62
|
+
export class FirebaseCrashlyticsError extends FirebaseError {
|
|
63
|
+
constructor(message: string, originalError?: unknown) {
|
|
64
|
+
super(message, 'CRASHLYTICS_ERROR', originalError);
|
|
65
|
+
this.name = 'FirebaseCrashlyticsError';
|
|
66
|
+
Object.setPrototypeOf(this, FirebaseCrashlyticsError.prototype);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Auth Error
|
|
72
|
+
* Thrown when authentication operations fail
|
|
73
|
+
*/
|
|
74
|
+
export class FirebaseAuthError extends FirebaseError {
|
|
75
|
+
constructor(message: string, originalError?: unknown) {
|
|
76
|
+
super(message, 'AUTH_ERROR', originalError);
|
|
77
|
+
this.name = 'FirebaseAuthError';
|
|
78
|
+
Object.setPrototypeOf(this, FirebaseAuthError.prototype);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Firestore Error
|
|
84
|
+
* Thrown when Firestore operations fail
|
|
85
|
+
*/
|
|
86
|
+
export class FirebaseFirestoreError extends FirebaseError {
|
|
87
|
+
constructor(message: string, originalError?: unknown) {
|
|
88
|
+
super(message, 'FIRESTORE_ERROR', originalError);
|
|
89
|
+
this.name = 'FirebaseFirestoreError';
|
|
90
|
+
Object.setPrototypeOf(this, FirebaseFirestoreError.prototype);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Firebase Configuration Value Object
|
|
3
|
+
*
|
|
4
|
+
* Domain-Driven Design: Value object for Firebase configuration
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Firebase Configuration
|
|
9
|
+
* Required configuration for initializing Firebase client
|
|
10
|
+
*/
|
|
11
|
+
export interface FirebaseConfig {
|
|
12
|
+
/**
|
|
13
|
+
* Firebase API Key
|
|
14
|
+
*/
|
|
15
|
+
apiKey: string;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Firebase Auth Domain
|
|
19
|
+
*/
|
|
20
|
+
authDomain: string;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Firebase Project ID
|
|
24
|
+
*/
|
|
25
|
+
projectId: string;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Firebase Storage Bucket
|
|
29
|
+
*/
|
|
30
|
+
storageBucket?: string;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Firebase Messaging Sender ID
|
|
34
|
+
*/
|
|
35
|
+
messagingSenderId?: string;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Firebase App ID
|
|
39
|
+
*/
|
|
40
|
+
appId?: string;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Optional: Custom storage adapter for Auth persistence
|
|
44
|
+
* If not provided, AsyncStorage will be used for React Native
|
|
45
|
+
*/
|
|
46
|
+
authStorage?: {
|
|
47
|
+
getItem: (key: string) => Promise<string | null>;
|
|
48
|
+
setItem: (key: string, value: string) => Promise<void>;
|
|
49
|
+
removeItem: (key: string) => Promise<void>;
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Validation moved to FirebaseConfigValidator class (SOLID: Single Responsibility)
|
|
54
|
+
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* React Native Firebase - Public API
|
|
3
|
+
*
|
|
4
|
+
* Domain-Driven Design (DDD) Architecture
|
|
5
|
+
*
|
|
6
|
+
* This is the SINGLE SOURCE OF TRUTH for all Firebase operations.
|
|
7
|
+
* ALL imports from the Firebase package MUST go through this file.
|
|
8
|
+
*
|
|
9
|
+
* Architecture:
|
|
10
|
+
* - domain: Entities, value objects, errors (business logic)
|
|
11
|
+
* - application: Ports (interfaces)
|
|
12
|
+
* - infrastructure: Firebase client implementation
|
|
13
|
+
* - presentation: Hooks (React integration)
|
|
14
|
+
*
|
|
15
|
+
* Usage:
|
|
16
|
+
* import { initializeFirebase, getFirebaseApp, useFirebase } from '@umituz/react-native-firebase';
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
// =============================================================================
|
|
20
|
+
// DOMAIN LAYER - Business Logic
|
|
21
|
+
// =============================================================================
|
|
22
|
+
|
|
23
|
+
export {
|
|
24
|
+
FirebaseError,
|
|
25
|
+
FirebaseInitializationError,
|
|
26
|
+
FirebaseConfigurationError,
|
|
27
|
+
FirebaseAnalyticsError,
|
|
28
|
+
FirebaseCrashlyticsError,
|
|
29
|
+
FirebaseAuthError,
|
|
30
|
+
FirebaseFirestoreError,
|
|
31
|
+
} from './domain/errors/FirebaseError';
|
|
32
|
+
|
|
33
|
+
export type { FirebaseConfig } from './domain/value-objects/FirebaseConfig';
|
|
34
|
+
|
|
35
|
+
// =============================================================================
|
|
36
|
+
// APPLICATION LAYER - Ports
|
|
37
|
+
// =============================================================================
|
|
38
|
+
|
|
39
|
+
export type { IFirebaseClient } from './application/ports/IFirebaseClient';
|
|
40
|
+
|
|
41
|
+
// =============================================================================
|
|
42
|
+
// INFRASTRUCTURE LAYER - Implementation
|
|
43
|
+
// =============================================================================
|
|
44
|
+
|
|
45
|
+
export {
|
|
46
|
+
initializeFirebase,
|
|
47
|
+
getFirebaseApp,
|
|
48
|
+
getFirebaseAuth,
|
|
49
|
+
getFirestore,
|
|
50
|
+
isFirebaseInitialized,
|
|
51
|
+
getFirebaseInitializationError,
|
|
52
|
+
resetFirebaseClient,
|
|
53
|
+
firebaseClient,
|
|
54
|
+
} from './infrastructure/config/FirebaseClient';
|
|
55
|
+
|
|
56
|
+
export type {
|
|
57
|
+
FirebaseApp,
|
|
58
|
+
Auth,
|
|
59
|
+
Firestore,
|
|
60
|
+
} from './infrastructure/config/FirebaseClient';
|
|
61
|
+
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Firebase Client - Infrastructure Layer
|
|
3
|
+
*
|
|
4
|
+
* Domain-Driven Design: Infrastructure implementation of Firebase client
|
|
5
|
+
* Singleton pattern for managing Firebase client instance
|
|
6
|
+
*
|
|
7
|
+
* IMPORTANT: This package does NOT read from .env files.
|
|
8
|
+
* Configuration must be provided by the application.
|
|
9
|
+
*
|
|
10
|
+
* SOLID Principles:
|
|
11
|
+
* - Single Responsibility: Only orchestrates initialization, delegates to specialized classes
|
|
12
|
+
* - Open/Closed: Extensible through configuration, closed for modification
|
|
13
|
+
* - Dependency Inversion: Depends on abstractions (interfaces), not concrete implementations
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import type { FirebaseApp } from 'firebase/app';
|
|
17
|
+
import type { Auth } from 'firebase/auth';
|
|
18
|
+
import type { Firestore } from 'firebase/firestore';
|
|
19
|
+
import type { FirebaseConfig } from '../../domain/value-objects/FirebaseConfig';
|
|
20
|
+
import { FirebaseInitializationError } from '../../domain/errors/FirebaseError';
|
|
21
|
+
import type { IFirebaseClient } from '../../application/ports/IFirebaseClient';
|
|
22
|
+
import { FirebaseConfigValidator } from './validators/FirebaseConfigValidator';
|
|
23
|
+
import { FirebaseAppInitializer } from './initializers/FirebaseAppInitializer';
|
|
24
|
+
import { FirebaseAuthInitializer } from './initializers/FirebaseAuthInitializer';
|
|
25
|
+
import { FirebaseFirestoreInitializer } from './initializers/FirebaseFirestoreInitializer';
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Firebase Client Singleton
|
|
29
|
+
* Orchestrates Firebase initialization using specialized initializers
|
|
30
|
+
*/
|
|
31
|
+
class FirebaseClientSingleton implements IFirebaseClient {
|
|
32
|
+
private static instance: FirebaseClientSingleton | null = null;
|
|
33
|
+
private app: FirebaseApp | null = null;
|
|
34
|
+
private auth: Auth | null = null;
|
|
35
|
+
private db: Firestore | null = null;
|
|
36
|
+
private initializationError: string | null = null;
|
|
37
|
+
|
|
38
|
+
private constructor() {
|
|
39
|
+
// Private constructor to enforce singleton pattern
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Get singleton instance
|
|
44
|
+
*/
|
|
45
|
+
static getInstance(): FirebaseClientSingleton {
|
|
46
|
+
if (!FirebaseClientSingleton.instance) {
|
|
47
|
+
FirebaseClientSingleton.instance = new FirebaseClientSingleton();
|
|
48
|
+
}
|
|
49
|
+
return FirebaseClientSingleton.instance;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Initialize Firebase client with configuration
|
|
54
|
+
* Configuration must be provided by the application (not from .env)
|
|
55
|
+
*
|
|
56
|
+
* @param config - Firebase configuration
|
|
57
|
+
* @returns Firebase app instance or null if initialization fails
|
|
58
|
+
*/
|
|
59
|
+
initialize(config: FirebaseConfig): FirebaseApp | null {
|
|
60
|
+
// Return existing instance if already initialized
|
|
61
|
+
if (this.app) {
|
|
62
|
+
return this.app;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Don't retry if initialization already failed
|
|
66
|
+
if (this.initializationError) {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
try {
|
|
71
|
+
// Validate configuration
|
|
72
|
+
FirebaseConfigValidator.validate(config);
|
|
73
|
+
|
|
74
|
+
// Initialize Firebase App
|
|
75
|
+
this.app = FirebaseAppInitializer.initialize(config);
|
|
76
|
+
|
|
77
|
+
// Initialize Auth
|
|
78
|
+
this.auth = FirebaseAuthInitializer.initialize(this.app, config);
|
|
79
|
+
|
|
80
|
+
// Initialize Firestore
|
|
81
|
+
this.db = FirebaseFirestoreInitializer.initialize(this.app);
|
|
82
|
+
|
|
83
|
+
return this.app;
|
|
84
|
+
} catch (error) {
|
|
85
|
+
this.initializationError =
|
|
86
|
+
error instanceof Error
|
|
87
|
+
? error.message
|
|
88
|
+
: 'Failed to initialize Firebase client';
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Get the Firebase app instance
|
|
95
|
+
* @throws {FirebaseInitializationError} If client is not initialized
|
|
96
|
+
*/
|
|
97
|
+
getApp(): FirebaseApp {
|
|
98
|
+
if (!this.app) {
|
|
99
|
+
const errorMsg =
|
|
100
|
+
this.initializationError ||
|
|
101
|
+
'Firebase client not initialized. Call initialize() first with configuration.';
|
|
102
|
+
throw new FirebaseInitializationError(errorMsg);
|
|
103
|
+
}
|
|
104
|
+
return this.app;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Get the Firebase Auth instance
|
|
109
|
+
* @throws {FirebaseInitializationError} If client is not initialized
|
|
110
|
+
*/
|
|
111
|
+
getAuth(): Auth {
|
|
112
|
+
if (!this.auth) {
|
|
113
|
+
const errorMsg =
|
|
114
|
+
this.initializationError ||
|
|
115
|
+
'Firebase client not initialized. Call initialize() first with configuration.';
|
|
116
|
+
throw new FirebaseInitializationError(errorMsg);
|
|
117
|
+
}
|
|
118
|
+
return this.auth;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Get the Firestore instance
|
|
123
|
+
* @throws {FirebaseInitializationError} If client is not initialized
|
|
124
|
+
*/
|
|
125
|
+
getFirestore(): Firestore {
|
|
126
|
+
if (!this.db) {
|
|
127
|
+
const errorMsg =
|
|
128
|
+
this.initializationError ||
|
|
129
|
+
'Firebase client not initialized. Call initialize() first with configuration.';
|
|
130
|
+
throw new FirebaseInitializationError(errorMsg);
|
|
131
|
+
}
|
|
132
|
+
return this.db;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Check if client is initialized
|
|
137
|
+
*/
|
|
138
|
+
isInitialized(): boolean {
|
|
139
|
+
return this.app !== null;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Get initialization error if any
|
|
144
|
+
*/
|
|
145
|
+
getInitializationError(): string | null {
|
|
146
|
+
return this.initializationError;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Reset the client instance
|
|
151
|
+
* Useful for testing
|
|
152
|
+
*/
|
|
153
|
+
reset(): void {
|
|
154
|
+
this.app = null;
|
|
155
|
+
this.auth = null;
|
|
156
|
+
this.db = null;
|
|
157
|
+
this.initializationError = null;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Singleton instance
|
|
163
|
+
*/
|
|
164
|
+
export const firebaseClient = FirebaseClientSingleton.getInstance();
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Initialize Firebase client
|
|
168
|
+
* This is the main entry point for applications
|
|
169
|
+
*
|
|
170
|
+
* @param config - Firebase configuration (must be provided by app, not from .env)
|
|
171
|
+
* @returns Firebase app instance or null if initialization fails
|
|
172
|
+
*
|
|
173
|
+
* @example
|
|
174
|
+
* ```typescript
|
|
175
|
+
* import { initializeFirebase } from '@umituz/react-native-firebase';
|
|
176
|
+
*
|
|
177
|
+
* const config = {
|
|
178
|
+
* apiKey: 'your-api-key',
|
|
179
|
+
* authDomain: 'your-project.firebaseapp.com',
|
|
180
|
+
* projectId: 'your-project-id',
|
|
181
|
+
* };
|
|
182
|
+
*
|
|
183
|
+
* const app = initializeFirebase(config);
|
|
184
|
+
* ```
|
|
185
|
+
*/
|
|
186
|
+
export function initializeFirebase(
|
|
187
|
+
config: FirebaseConfig
|
|
188
|
+
): FirebaseApp | null {
|
|
189
|
+
return firebaseClient.initialize(config);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Get Firebase app instance
|
|
194
|
+
* @throws {FirebaseInitializationError} If client is not initialized
|
|
195
|
+
*/
|
|
196
|
+
export function getFirebaseApp(): FirebaseApp {
|
|
197
|
+
return firebaseClient.getApp();
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Get Firebase Auth instance
|
|
202
|
+
* @throws {FirebaseInitializationError} If client is not initialized
|
|
203
|
+
*/
|
|
204
|
+
export function getFirebaseAuth(): Auth {
|
|
205
|
+
return firebaseClient.getAuth();
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Get Firestore instance
|
|
210
|
+
* @throws {FirebaseInitializationError} If client is not initialized
|
|
211
|
+
*/
|
|
212
|
+
export function getFirestore(): Firestore {
|
|
213
|
+
return firebaseClient.getFirestore();
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Check if Firebase client is initialized
|
|
218
|
+
*/
|
|
219
|
+
export function isFirebaseInitialized(): boolean {
|
|
220
|
+
return firebaseClient.isInitialized();
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Get initialization error if any
|
|
225
|
+
*/
|
|
226
|
+
export function getFirebaseInitializationError(): string | null {
|
|
227
|
+
return firebaseClient.getInitializationError();
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Reset Firebase client instance
|
|
232
|
+
* Useful for testing
|
|
233
|
+
*/
|
|
234
|
+
export function resetFirebaseClient(): void {
|
|
235
|
+
firebaseClient.reset();
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Export types
|
|
239
|
+
export type { FirebaseApp } from 'firebase/app';
|
|
240
|
+
export type { Auth } from 'firebase/auth';
|
|
241
|
+
export type { Firestore } from 'firebase/firestore';
|
|
242
|
+
export type { FirebaseConfig } from '../../domain/value-objects/FirebaseConfig';
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Firebase App Initializer
|
|
3
|
+
*
|
|
4
|
+
* Single Responsibility: Initialize Firebase App instance
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { initializeApp, getApps, FirebaseApp } from 'firebase/app';
|
|
8
|
+
import type { FirebaseConfig } from '../../../domain/value-objects/FirebaseConfig';
|
|
9
|
+
import { FirebaseInitializationError } from '../../../domain/errors/FirebaseError';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Initializes Firebase App
|
|
13
|
+
*/
|
|
14
|
+
export class FirebaseAppInitializer {
|
|
15
|
+
/**
|
|
16
|
+
* Initialize or get existing Firebase App
|
|
17
|
+
*/
|
|
18
|
+
static initialize(config: FirebaseConfig): FirebaseApp {
|
|
19
|
+
// Return existing app if already initialized
|
|
20
|
+
const existingApps = getApps();
|
|
21
|
+
if (existingApps.length > 0) {
|
|
22
|
+
return existingApps[0];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
const firebaseConfig = {
|
|
27
|
+
apiKey: config.apiKey,
|
|
28
|
+
authDomain: config.authDomain,
|
|
29
|
+
projectId: config.projectId,
|
|
30
|
+
storageBucket: config.storageBucket,
|
|
31
|
+
messagingSenderId: config.messagingSenderId,
|
|
32
|
+
appId: config.appId,
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
return initializeApp(firebaseConfig);
|
|
36
|
+
} catch (error) {
|
|
37
|
+
throw new FirebaseInitializationError(
|
|
38
|
+
`Failed to initialize Firebase App: ${
|
|
39
|
+
error instanceof Error ? error.message : 'Unknown error'
|
|
40
|
+
}`,
|
|
41
|
+
error
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Firebase Auth Initializer
|
|
3
|
+
*
|
|
4
|
+
* Single Responsibility: Initialize Firebase Auth instance
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { initializeAuth, getAuth } from 'firebase/auth';
|
|
8
|
+
import type { Auth } from 'firebase/auth';
|
|
9
|
+
import { Platform } from 'react-native';
|
|
10
|
+
import AsyncStorage from '@react-native-async-storage/async-storage';
|
|
11
|
+
import type { FirebaseApp } from 'firebase/app';
|
|
12
|
+
import type { FirebaseConfig } from '../../../domain/value-objects/FirebaseConfig';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Initializes Firebase Auth
|
|
16
|
+
*/
|
|
17
|
+
export class FirebaseAuthInitializer {
|
|
18
|
+
/**
|
|
19
|
+
* Initialize Firebase Auth with platform-specific persistence
|
|
20
|
+
*/
|
|
21
|
+
static initialize(app: FirebaseApp, config: FirebaseConfig): Auth {
|
|
22
|
+
try {
|
|
23
|
+
if (Platform.OS === 'web') {
|
|
24
|
+
return getAuth(app);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Try React Native persistence
|
|
28
|
+
return this.initializeWithPersistence(app, config);
|
|
29
|
+
} catch (error: any) {
|
|
30
|
+
// If already initialized, get existing instance
|
|
31
|
+
if (error.code === 'auth/already-initialized') {
|
|
32
|
+
return getAuth(app);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/* eslint-disable-next-line no-console */
|
|
36
|
+
if (__DEV__) console.warn('Firebase Auth initialization error:', error);
|
|
37
|
+
return getAuth(app);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
private static initializeWithPersistence(
|
|
42
|
+
app: FirebaseApp,
|
|
43
|
+
config: FirebaseConfig
|
|
44
|
+
): Auth {
|
|
45
|
+
try {
|
|
46
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
47
|
+
const authModule = require('firebase/auth');
|
|
48
|
+
const getReactNativePersistence = authModule.getReactNativePersistence;
|
|
49
|
+
|
|
50
|
+
if (!getReactNativePersistence) {
|
|
51
|
+
return getAuth(app);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const storage = config.authStorage || {
|
|
55
|
+
getItem: (key: string) => AsyncStorage.getItem(key),
|
|
56
|
+
setItem: (key: string, value: string) => AsyncStorage.setItem(key, value),
|
|
57
|
+
removeItem: (key: string) => AsyncStorage.removeItem(key),
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
return initializeAuth(app, {
|
|
61
|
+
persistence: getReactNativePersistence(storage),
|
|
62
|
+
});
|
|
63
|
+
} catch {
|
|
64
|
+
return getAuth(app);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Firebase Firestore Initializer
|
|
3
|
+
*
|
|
4
|
+
* Single Responsibility: Initialize Firestore instance
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
getFirestore,
|
|
9
|
+
initializeFirestore,
|
|
10
|
+
persistentLocalCache,
|
|
11
|
+
} from 'firebase/firestore';
|
|
12
|
+
import type { Firestore } from 'firebase/firestore';
|
|
13
|
+
import { Platform } from 'react-native';
|
|
14
|
+
import type { FirebaseApp } from 'firebase/app';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Initializes Firestore
|
|
18
|
+
*/
|
|
19
|
+
export class FirebaseFirestoreInitializer {
|
|
20
|
+
/**
|
|
21
|
+
* Initialize Firestore with platform-specific cache configuration
|
|
22
|
+
*/
|
|
23
|
+
static initialize(app: FirebaseApp): Firestore {
|
|
24
|
+
if (Platform.OS === 'web') {
|
|
25
|
+
return this.initializeForWeb(app);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// React Native: Use default persistence
|
|
29
|
+
return getFirestore(app);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
private static initializeForWeb(app: FirebaseApp): Firestore {
|
|
33
|
+
try {
|
|
34
|
+
return initializeFirestore(app, {
|
|
35
|
+
localCache: persistentLocalCache(),
|
|
36
|
+
});
|
|
37
|
+
} catch (error: any) {
|
|
38
|
+
// If already initialized, get existing instance
|
|
39
|
+
if (error.code === 'failed-precondition') {
|
|
40
|
+
/* eslint-disable-next-line no-console */
|
|
41
|
+
if (__DEV__)
|
|
42
|
+
console.warn(
|
|
43
|
+
'Firestore already initialized, using existing instance'
|
|
44
|
+
);
|
|
45
|
+
return getFirestore(app);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/* eslint-disable-next-line no-console */
|
|
49
|
+
if (__DEV__) console.warn('Firestore initialization error:', error);
|
|
50
|
+
return getFirestore(app);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Firebase Configuration Validator
|
|
3
|
+
*
|
|
4
|
+
* Single Responsibility: Validates Firebase configuration
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { FirebaseConfig } from '../../../domain/value-objects/FirebaseConfig';
|
|
8
|
+
import { FirebaseConfigurationError } from '../../../domain/errors/FirebaseError';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Validates Firebase configuration
|
|
12
|
+
*/
|
|
13
|
+
export class FirebaseConfigValidator {
|
|
14
|
+
/**
|
|
15
|
+
* Validate Firebase configuration
|
|
16
|
+
* @throws {FirebaseConfigurationError} If configuration is invalid
|
|
17
|
+
*/
|
|
18
|
+
static validate(config: FirebaseConfig): void {
|
|
19
|
+
if (!config.apiKey || typeof config.apiKey !== 'string') {
|
|
20
|
+
throw new FirebaseConfigurationError(
|
|
21
|
+
'Firebase API Key is required and must be a string'
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (!config.authDomain || typeof config.authDomain !== 'string') {
|
|
26
|
+
throw new FirebaseConfigurationError(
|
|
27
|
+
'Firebase Auth Domain is required and must be a string'
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (!config.projectId || typeof config.projectId !== 'string') {
|
|
32
|
+
throw new FirebaseConfigurationError(
|
|
33
|
+
'Firebase Project ID is required and must be a string'
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (config.apiKey.trim().length === 0) {
|
|
38
|
+
throw new FirebaseConfigurationError('Firebase API Key cannot be empty');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (
|
|
42
|
+
config.apiKey.includes('your_firebase_api_key') ||
|
|
43
|
+
config.projectId.includes('your-project-id')
|
|
44
|
+
) {
|
|
45
|
+
throw new FirebaseConfigurationError(
|
|
46
|
+
'Please replace placeholder values with actual Firebase credentials'
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|