@umituz/react-native-firebase 1.13.139 → 1.13.141
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 +1 -1
- package/src/auth/infrastructure/config/FirebaseAuthClient.ts +11 -2
- package/src/auth/infrastructure/services/account-deletion.service.ts +40 -20
- package/src/auth/infrastructure/services/anonymous-auth.service.ts +7 -11
- package/src/auth/infrastructure/services/apple-auth.service.ts +44 -18
- package/src/auth/infrastructure/services/base/base-auth.service.ts +9 -40
- package/src/auth/infrastructure/services/firestore-utils.service.ts +2 -2
- package/src/auth/infrastructure/services/google-auth.service.ts +27 -23
- package/src/auth/infrastructure/services/password.service.ts +6 -21
- package/src/auth/infrastructure/services/reauthentication.service.ts +19 -29
- package/src/auth/presentation/hooks/shared/hook-utils.util.ts +222 -0
- package/src/auth/presentation/hooks/useSocialAuth.ts +10 -1
- package/src/domain/utils/async-executor.util.ts +176 -0
- package/src/domain/utils/credential.util.ts +102 -0
- package/src/domain/utils/index.ts +101 -0
- package/src/domain/utils/result.util.ts +129 -0
- package/src/domain/utils/service-config.util.ts +99 -0
- package/src/domain/utils/validation.util.ts +78 -0
- package/src/firestore/infrastructure/middleware/QueryDeduplicationMiddleware.ts +3 -5
- package/src/firestore/infrastructure/repositories/BaseQueryRepository.ts +3 -3
- package/src/firestore/utils/dateUtils.ts +21 -5
- package/src/firestore/utils/deduplication/pending-query-manager.util.ts +3 -7
- package/src/firestore/utils/deduplication/query-key-generator.util.ts +12 -3
- package/src/firestore/utils/deduplication/timer-manager.util.ts +8 -2
- package/src/infrastructure/config/FirebaseClient.ts +36 -4
- package/src/init/createFirebaseInitModule.ts +18 -3
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Result Utility
|
|
3
|
+
* Unified result type for all operations across auth and firestore modules
|
|
4
|
+
* Provides type-safe success/failure handling
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Standard error information structure
|
|
9
|
+
*/
|
|
10
|
+
export interface ErrorInfo {
|
|
11
|
+
code: string;
|
|
12
|
+
message: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Standard result type for operations
|
|
17
|
+
* Success contains data, failure contains error info
|
|
18
|
+
*/
|
|
19
|
+
export interface Result<T = void> {
|
|
20
|
+
readonly success: boolean;
|
|
21
|
+
readonly data?: T;
|
|
22
|
+
readonly error?: ErrorInfo;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Success result type guard
|
|
27
|
+
*/
|
|
28
|
+
export type SuccessResult<T = void> = Result<T> & { readonly success: true; readonly data: T; readonly error?: never };
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Failure result type guard
|
|
32
|
+
*/
|
|
33
|
+
export type FailureResult = Result & { readonly success: false; readonly data?: never; readonly error: ErrorInfo };
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Create a success result with optional data
|
|
37
|
+
*/
|
|
38
|
+
export function successResult<T = void>(data?: T): SuccessResult<T> {
|
|
39
|
+
return { success: true, data: data as T };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Create a failure result with error information
|
|
44
|
+
*/
|
|
45
|
+
export function failureResult(error: ErrorInfo): FailureResult {
|
|
46
|
+
return { success: false, error };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Create a failure result from error code and message
|
|
51
|
+
*/
|
|
52
|
+
export function failureResultFrom(code: string, message: string): FailureResult {
|
|
53
|
+
return { success: false, error: { code, message } };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Create a failure result from an unknown error
|
|
58
|
+
*/
|
|
59
|
+
export function failureResultFromError(error: unknown, defaultCode = 'operation/failed'): FailureResult {
|
|
60
|
+
if (error instanceof Error) {
|
|
61
|
+
return {
|
|
62
|
+
success: false,
|
|
63
|
+
error: {
|
|
64
|
+
code: (error as { code?: string }).code ?? defaultCode,
|
|
65
|
+
message: error.message,
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
return {
|
|
70
|
+
success: false,
|
|
71
|
+
error: {
|
|
72
|
+
code: defaultCode,
|
|
73
|
+
message: typeof error === 'string' ? error : 'Unknown error occurred',
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Check if result is successful
|
|
80
|
+
*/
|
|
81
|
+
export function isSuccess<T>(result: Result<T>): result is SuccessResult<T> {
|
|
82
|
+
return result.success === true && result.error === undefined;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Check if result is a failure
|
|
87
|
+
*/
|
|
88
|
+
export function isFailure<T>(result: Result<T>): result is FailureResult {
|
|
89
|
+
return result.success === false;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Get data from result or return default
|
|
94
|
+
*/
|
|
95
|
+
export function getDataOrDefault<T>(result: Result<T>, defaultValue: T): T {
|
|
96
|
+
return isSuccess(result) ? (result.data ?? defaultValue) : defaultValue;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Map success result data to another type
|
|
101
|
+
*/
|
|
102
|
+
export function mapResult<T, U>(
|
|
103
|
+
result: Result<T>,
|
|
104
|
+
mapper: (data: T) => U
|
|
105
|
+
): Result<U> {
|
|
106
|
+
if (isSuccess(result) && result.data !== undefined) {
|
|
107
|
+
return successResult(mapper(result.data));
|
|
108
|
+
}
|
|
109
|
+
// Return a new failure result to avoid type conflicts
|
|
110
|
+
if (isFailure(result)) {
|
|
111
|
+
return { success: false, error: result.error };
|
|
112
|
+
}
|
|
113
|
+
return successResult();
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Chain multiple results, stopping at first failure
|
|
118
|
+
*/
|
|
119
|
+
export async function chainResults<T>(
|
|
120
|
+
...operations: (() => Promise<Result<T>>)[]
|
|
121
|
+
): Promise<Result<T>> {
|
|
122
|
+
for (const operation of operations) {
|
|
123
|
+
const result = await operation();
|
|
124
|
+
if (isFailure(result)) {
|
|
125
|
+
return result;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return successResult();
|
|
129
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configurable Service Base Class
|
|
3
|
+
* Provides common service configuration pattern
|
|
4
|
+
* Eliminates code duplication across services
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Configuration state management
|
|
9
|
+
*/
|
|
10
|
+
export interface ConfigState<TConfig> {
|
|
11
|
+
config: TConfig | null;
|
|
12
|
+
initialized: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Configurable service interface
|
|
17
|
+
*/
|
|
18
|
+
export interface IConfigurableService<TConfig> {
|
|
19
|
+
configure(config: TConfig): void;
|
|
20
|
+
isConfigured(): boolean;
|
|
21
|
+
getConfig(): TConfig | null;
|
|
22
|
+
reset(): void;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Base class for configurable services
|
|
27
|
+
* Provides configuration management pattern
|
|
28
|
+
*/
|
|
29
|
+
export class ConfigurableService<TConfig = unknown> implements IConfigurableService<TConfig> {
|
|
30
|
+
protected configState: ConfigState<TConfig>;
|
|
31
|
+
private customValidator?: (config: TConfig) => boolean;
|
|
32
|
+
|
|
33
|
+
constructor(validator?: (config: TConfig) => boolean) {
|
|
34
|
+
this.configState = {
|
|
35
|
+
config: null,
|
|
36
|
+
initialized: false,
|
|
37
|
+
};
|
|
38
|
+
this.customValidator = validator;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Configure the service
|
|
43
|
+
*/
|
|
44
|
+
configure(config: TConfig): void {
|
|
45
|
+
this.configState.config = config;
|
|
46
|
+
this.configState.initialized = this.isValidConfig(config);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Check if service is configured
|
|
51
|
+
*/
|
|
52
|
+
isConfigured(): boolean {
|
|
53
|
+
return this.configState.initialized && this.configState.config !== null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Get current configuration
|
|
58
|
+
*/
|
|
59
|
+
getConfig(): TConfig | null {
|
|
60
|
+
return this.configState.config;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Reset configuration
|
|
65
|
+
*/
|
|
66
|
+
reset(): void {
|
|
67
|
+
this.configState.config = null;
|
|
68
|
+
this.configState.initialized = false;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Validate configuration - can be overridden by custom validator
|
|
73
|
+
*/
|
|
74
|
+
protected isValidConfig(config: TConfig): boolean {
|
|
75
|
+
if (this.customValidator) {
|
|
76
|
+
return this.customValidator(config);
|
|
77
|
+
}
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Get required configuration or throw error
|
|
83
|
+
*/
|
|
84
|
+
protected requireConfig(): TConfig {
|
|
85
|
+
if (!this.configState.config) {
|
|
86
|
+
throw new Error('Service is not configured');
|
|
87
|
+
}
|
|
88
|
+
return this.configState.config;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Create a configurable service instance
|
|
94
|
+
*/
|
|
95
|
+
export function createConfigurableService<TConfig>(
|
|
96
|
+
validator?: (config: TConfig) => boolean
|
|
97
|
+
): ConfigurableService<TConfig> {
|
|
98
|
+
return new ConfigurableService<TConfig>(validator);
|
|
99
|
+
}
|
|
@@ -131,3 +131,81 @@ export function isPositive(value: number): boolean {
|
|
|
131
131
|
export function isNonNegative(value: number): boolean {
|
|
132
132
|
return typeof value === 'number' && value >= 0;
|
|
133
133
|
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Validate password strength
|
|
137
|
+
* At least 8 characters, containing uppercase, lowercase, and number
|
|
138
|
+
*/
|
|
139
|
+
export function isStrongPassword(password: string): boolean {
|
|
140
|
+
if (!isValidString(password) || password.length < 8) {
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
const hasUpperCase = /[A-Z]/.test(password);
|
|
144
|
+
const hasLowerCase = /[a-z]/.test(password);
|
|
145
|
+
const hasNumber = /[0-9]/.test(password);
|
|
146
|
+
return hasUpperCase && hasLowerCase && hasNumber;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Validate username format
|
|
151
|
+
* Alphanumeric, underscores, and hyphens, 3-20 characters
|
|
152
|
+
*/
|
|
153
|
+
export function isValidUsername(username: string): boolean {
|
|
154
|
+
if (!isValidString(username)) {
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
157
|
+
const pattern = /^[a-zA-Z0-9_-]{3,20}$/;
|
|
158
|
+
return pattern.test(username);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Validate phone number format (basic check)
|
|
163
|
+
*/
|
|
164
|
+
export function isValidPhoneNumber(phone: string): boolean {
|
|
165
|
+
if (!isValidString(phone)) {
|
|
166
|
+
return false;
|
|
167
|
+
}
|
|
168
|
+
const cleaned = phone.replace(/\s+/g, '').replace(/[-+()]/g, '');
|
|
169
|
+
return /^[0-9]{10,15}$/.test(cleaned);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Validate object has required properties
|
|
174
|
+
*/
|
|
175
|
+
export function hasRequiredProperties<T extends Record<string, unknown>>(
|
|
176
|
+
obj: unknown,
|
|
177
|
+
requiredProps: (keyof T)[]
|
|
178
|
+
): obj is T {
|
|
179
|
+
if (typeof obj !== 'object' || obj === null) {
|
|
180
|
+
return false;
|
|
181
|
+
}
|
|
182
|
+
return requiredProps.every((prop) => prop in obj);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Validate all items in array match predicate
|
|
187
|
+
*/
|
|
188
|
+
export function allMatch<T>(items: unknown[], predicate: (item: unknown) => item is T): boolean {
|
|
189
|
+
return Array.isArray(items) && items.every(predicate);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Validate at least one item in array matches predicate
|
|
194
|
+
*/
|
|
195
|
+
export function anyMatch<T>(items: unknown[], predicate: (item: unknown) => item is T): boolean {
|
|
196
|
+
return Array.isArray(items) && items.some(predicate);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Create a validator that combines multiple validators
|
|
201
|
+
*/
|
|
202
|
+
export function combineValidators(...validators: ((value: string) => boolean)[]): (value: string) => boolean {
|
|
203
|
+
return (value: string) => validators.every((validator) => validator(value));
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Create a validator that checks if value matches one of validators
|
|
208
|
+
*/
|
|
209
|
+
export function anyValidator(...validators: ((value: string) => boolean)[]): (value: string) => boolean {
|
|
210
|
+
return (value: string) => validators.some((validator) => validator(value));
|
|
211
|
+
}
|
|
@@ -30,14 +30,12 @@ export class QueryDeduplicationMiddleware {
|
|
|
30
30
|
): Promise<T> {
|
|
31
31
|
const key = generateQueryKey(queryKey);
|
|
32
32
|
|
|
33
|
+
// Check if query is already pending
|
|
33
34
|
if (this.queryManager.isPending(key)) {
|
|
34
35
|
const pendingPromise = this.queryManager.get(key);
|
|
35
36
|
if (pendingPromise) {
|
|
36
|
-
//
|
|
37
|
-
return
|
|
38
|
-
// Retry the original query after pending completes
|
|
39
|
-
return queryFn();
|
|
40
|
-
});
|
|
37
|
+
// Return the existing pending promise instead of executing again
|
|
38
|
+
return pendingPromise as Promise<T>;
|
|
41
39
|
}
|
|
42
40
|
}
|
|
43
41
|
|
|
@@ -29,9 +29,9 @@ export abstract class BaseQueryRepository extends BaseRepository {
|
|
|
29
29
|
cached: boolean = false,
|
|
30
30
|
uniqueKey?: string
|
|
31
31
|
): Promise<T> {
|
|
32
|
-
//
|
|
33
|
-
//
|
|
34
|
-
const safeKey = uniqueKey || `${collection}
|
|
32
|
+
// Use provided uniqueKey, or generate a stable key based on collection
|
|
33
|
+
// IMPORTANT: Don't use Date.now() as it defeats deduplication!
|
|
34
|
+
const safeKey = uniqueKey || `${collection}_query`;
|
|
35
35
|
|
|
36
36
|
const queryKey = {
|
|
37
37
|
collection,
|
|
@@ -1,14 +1,30 @@
|
|
|
1
1
|
import { Timestamp } from 'firebase/firestore';
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Validate ISO 8601 date string format
|
|
5
|
+
*/
|
|
6
|
+
function isValidISODate(isoString: string): boolean {
|
|
7
|
+
// Check if it matches ISO 8601 format (basic check)
|
|
8
|
+
const isoPattern = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{3})?Z?([+-]\d{2}:\d{2})?$/;
|
|
9
|
+
if (!isoPattern.test(isoString)) {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// Verify it's a valid date
|
|
14
|
+
const date = new Date(isoString);
|
|
15
|
+
return !isNaN(date.getTime());
|
|
16
|
+
}
|
|
17
|
+
|
|
3
18
|
/**
|
|
4
19
|
* Convert ISO string to Firestore Timestamp
|
|
20
|
+
* @throws Error if isoString is not a valid ISO 8601 date
|
|
5
21
|
*/
|
|
6
22
|
export function isoToTimestamp(isoString: string): Timestamp {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
23
|
+
if (!isValidISODate(isoString)) {
|
|
24
|
+
throw new Error(`Invalid ISO date string: ${isoString}`);
|
|
25
|
+
}
|
|
26
|
+
const date = new Date(isoString);
|
|
27
|
+
return Timestamp.fromDate(date);
|
|
12
28
|
}
|
|
13
29
|
|
|
14
30
|
/**
|
|
@@ -44,13 +44,9 @@ export class PendingQueryManager {
|
|
|
44
44
|
* Add query to pending list with guaranteed cleanup
|
|
45
45
|
*/
|
|
46
46
|
add(key: string, promise: Promise<unknown>): void {
|
|
47
|
-
const wrappedPromise = promise
|
|
48
|
-
.
|
|
49
|
-
|
|
50
|
-
})
|
|
51
|
-
.finally(() => {
|
|
52
|
-
this.pendingQueries.delete(key);
|
|
53
|
-
});
|
|
47
|
+
const wrappedPromise = promise.finally(() => {
|
|
48
|
+
this.pendingQueries.delete(key);
|
|
49
|
+
});
|
|
54
50
|
|
|
55
51
|
this.pendingQueries.set(key, {
|
|
56
52
|
promise: wrappedPromise,
|
|
@@ -10,15 +10,24 @@ export interface QueryKey {
|
|
|
10
10
|
orderBy?: string;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
+
/**
|
|
14
|
+
* Escape special characters in query key components
|
|
15
|
+
* Prevents key collisions when filter strings contain separator characters
|
|
16
|
+
*/
|
|
17
|
+
function escapeKeyComponent(component: string): string {
|
|
18
|
+
return component.replace(/%/g, '%25').replace(/\|/g, '%7C');
|
|
19
|
+
}
|
|
20
|
+
|
|
13
21
|
/**
|
|
14
22
|
* Generate a unique key from query parameters
|
|
23
|
+
* Uses URL encoding to prevent collisions from separator characters
|
|
15
24
|
*/
|
|
16
25
|
export function generateQueryKey(key: QueryKey): string {
|
|
17
26
|
const parts = [
|
|
18
|
-
key.collection,
|
|
19
|
-
key.filters,
|
|
27
|
+
escapeKeyComponent(key.collection),
|
|
28
|
+
escapeKeyComponent(key.filters),
|
|
20
29
|
key.limit?.toString() || '',
|
|
21
|
-
key.orderBy || '',
|
|
30
|
+
escapeKeyComponent(key.orderBy || ''),
|
|
22
31
|
];
|
|
23
32
|
return parts.join('|');
|
|
24
33
|
}
|
|
@@ -18,17 +18,23 @@ export class TimerManager {
|
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
20
|
* Start the cleanup timer
|
|
21
|
+
* Idempotent: safe to call multiple times
|
|
21
22
|
*/
|
|
22
23
|
start(): void {
|
|
24
|
+
// Clear existing timer if running (prevents duplicate timers)
|
|
23
25
|
if (this.timer) {
|
|
24
|
-
|
|
26
|
+
this.stop();
|
|
25
27
|
}
|
|
26
28
|
|
|
27
29
|
this.timer = setInterval(() => {
|
|
28
30
|
try {
|
|
29
31
|
this.options.onCleanup();
|
|
30
|
-
} catch {
|
|
32
|
+
} catch (error) {
|
|
31
33
|
// Silently handle cleanup errors to prevent timer from causing issues
|
|
34
|
+
// Log error in development for debugging
|
|
35
|
+
if (process.env.NODE_ENV === 'development') {
|
|
36
|
+
console.error('TimerManager cleanup error:', error);
|
|
37
|
+
}
|
|
32
38
|
}
|
|
33
39
|
}, this.options.cleanupIntervalMs);
|
|
34
40
|
}
|
|
@@ -45,6 +45,7 @@ export interface ServiceInitializationResult {
|
|
|
45
45
|
class FirebaseClientSingleton implements IFirebaseClient {
|
|
46
46
|
private static instance: FirebaseClientSingleton | null = null;
|
|
47
47
|
private state: FirebaseClientState;
|
|
48
|
+
private lastError: string | null = null;
|
|
48
49
|
|
|
49
50
|
private constructor() {
|
|
50
51
|
this.state = new FirebaseClientState();
|
|
@@ -58,23 +59,54 @@ class FirebaseClientSingleton implements IFirebaseClient {
|
|
|
58
59
|
}
|
|
59
60
|
|
|
60
61
|
initialize(config: FirebaseConfig): FirebaseApp | null {
|
|
61
|
-
|
|
62
|
+
try {
|
|
63
|
+
const result = FirebaseInitializationOrchestrator.initialize(config);
|
|
64
|
+
// Sync state with orchestrator result
|
|
65
|
+
this.state.setInstance(result);
|
|
66
|
+
this.lastError = null;
|
|
67
|
+
return result;
|
|
68
|
+
} catch (error) {
|
|
69
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
70
|
+
this.lastError = errorMessage;
|
|
71
|
+
this.state.setInitializationError(errorMessage);
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
62
74
|
}
|
|
63
75
|
|
|
64
76
|
getApp(): FirebaseApp | null {
|
|
65
|
-
|
|
77
|
+
// Check local state first
|
|
78
|
+
const localApp = this.state.getApp();
|
|
79
|
+
if (localApp) return localApp;
|
|
80
|
+
|
|
81
|
+
// Try to get from orchestrator
|
|
82
|
+
const result = FirebaseInitializationOrchestrator.autoInitialize();
|
|
83
|
+
if (result) {
|
|
84
|
+
this.state.setInstance(result);
|
|
85
|
+
}
|
|
86
|
+
return result;
|
|
66
87
|
}
|
|
67
88
|
|
|
68
89
|
isInitialized(): boolean {
|
|
69
|
-
|
|
90
|
+
// Check both local state and orchestrator for consistency
|
|
91
|
+
if (this.state.isInitialized()) return true;
|
|
92
|
+
|
|
93
|
+
// Check if Firebase has any apps initialized
|
|
94
|
+
return FirebaseInitializationOrchestrator.autoInitialize() !== null;
|
|
70
95
|
}
|
|
71
96
|
|
|
72
97
|
getInitializationError(): string | null {
|
|
73
|
-
|
|
98
|
+
// Check local state first
|
|
99
|
+
const localError = this.state.getInitializationError();
|
|
100
|
+
if (localError) return localError;
|
|
101
|
+
// Return last error
|
|
102
|
+
return this.lastError;
|
|
74
103
|
}
|
|
75
104
|
|
|
76
105
|
reset(): void {
|
|
106
|
+
// Reset local state
|
|
77
107
|
this.state.reset();
|
|
108
|
+
this.lastError = null;
|
|
109
|
+
// Note: We don't reset Firebase apps as they might be in use
|
|
78
110
|
}
|
|
79
111
|
}
|
|
80
112
|
|
|
@@ -46,12 +46,27 @@ export function createFirebaseInitModule(
|
|
|
46
46
|
critical,
|
|
47
47
|
init: async () => {
|
|
48
48
|
try {
|
|
49
|
-
await initializeAllFirebaseServices(undefined, {
|
|
49
|
+
const result = await initializeAllFirebaseServices(undefined, {
|
|
50
50
|
authInitializer: authInitializer ?? (() => Promise.resolve()),
|
|
51
51
|
});
|
|
52
|
+
|
|
53
|
+
// Check if initialization was successful
|
|
54
|
+
if (!result.app) {
|
|
55
|
+
console.error('[Firebase] Initialization failed: Firebase app not initialized');
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Check if auth initialization failed
|
|
60
|
+
if (result.auth === false && result.authError) {
|
|
61
|
+
console.error(`[Firebase] Auth initialization failed: ${result.authError}`);
|
|
62
|
+
// Auth failure is not critical for the app to function
|
|
63
|
+
// Log the error but don't fail the entire initialization
|
|
64
|
+
}
|
|
65
|
+
|
|
52
66
|
return true;
|
|
53
|
-
} catch {
|
|
54
|
-
|
|
67
|
+
} catch (error) {
|
|
68
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
69
|
+
console.error(`[Firebase] Initialization failed: ${errorMessage}`);
|
|
55
70
|
return false;
|
|
56
71
|
}
|
|
57
72
|
},
|