@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 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
+