@umituz/react-native-firebase 2.6.4 → 2.6.5
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/domains/auth/infrastructure/config/FirebaseAuthClient.ts +48 -60
- package/src/domains/auth/infrastructure/config/index.ts +2 -0
- package/src/domains/auth/infrastructure/config/initializers/index.ts +1 -0
- package/src/domains/auth/infrastructure/services/index.ts +16 -0
- package/src/domains/auth/infrastructure/services/utils/index.ts +1 -0
- package/src/domains/auth/infrastructure/stores/index.ts +1 -0
- package/src/domains/auth/infrastructure/utils/index.ts +1 -0
- package/src/domains/auth/presentation/hooks/useGoogleOAuth.ts +18 -59
- package/src/domains/firestore/index.ts +0 -1
- package/src/domains/firestore/infrastructure/config/FirestoreClient.ts +42 -60
- package/src/shared/infrastructure/base/ErrorHandler.ts +81 -0
- package/src/shared/infrastructure/base/ServiceBase.ts +62 -0
- package/src/shared/infrastructure/config/base/ServiceClientSingleton.ts +12 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-firebase",
|
|
3
|
-
"version": "2.6.
|
|
3
|
+
"version": "2.6.5",
|
|
4
4
|
"description": "Unified Firebase package for React Native apps - Auth and Firestore services using Firebase JS SDK (no native modules).",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "./src/index.ts",
|
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import type { Auth } from 'firebase/auth';
|
|
8
|
+
import { getAuth as getFirebaseAuthFromFirebase } from 'firebase/auth';
|
|
8
9
|
import { getFirebaseApp } from '../../../../shared/infrastructure/config/services/FirebaseInitializationService';
|
|
9
|
-
import { FirebaseAuthInitializer } from './initializers/FirebaseAuthInitializer';
|
|
10
10
|
import type { FirebaseAuthConfig } from '../../domain/value-objects/FirebaseAuthConfig';
|
|
11
11
|
import { ServiceClientSingleton } from '../../../../shared/infrastructure/config/base/ServiceClientSingleton';
|
|
12
12
|
|
|
@@ -15,76 +15,64 @@ import { ServiceClientSingleton } from '../../../../shared/infrastructure/config
|
|
|
15
15
|
*/
|
|
16
16
|
class FirebaseAuthClientSingleton extends ServiceClientSingleton<Auth, FirebaseAuthConfig> {
|
|
17
17
|
private constructor() {
|
|
18
|
-
super(
|
|
19
|
-
serviceName: 'FirebaseAuth',
|
|
20
|
-
initializer: (config?: FirebaseAuthConfig) => {
|
|
21
|
-
const app = getFirebaseApp();
|
|
22
|
-
if (!app) {
|
|
23
|
-
this.setError('Firebase App is not initialized');
|
|
24
|
-
return null;
|
|
25
|
-
}
|
|
26
|
-
const auth = FirebaseAuthInitializer.initialize(app, config);
|
|
27
|
-
if (!auth) {
|
|
28
|
-
this.setError('Auth initialization returned null');
|
|
29
|
-
}
|
|
30
|
-
return auth;
|
|
31
|
-
},
|
|
32
|
-
});
|
|
18
|
+
super();
|
|
33
19
|
}
|
|
34
20
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
21
|
+
initialize(): Auth {
|
|
22
|
+
try {
|
|
23
|
+
const app = getFirebaseApp();
|
|
24
|
+
if (!app) {
|
|
25
|
+
this.setError('Firebase App is not initialized');
|
|
26
|
+
throw new Error('Firebase App is not initialized');
|
|
27
|
+
}
|
|
41
28
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
29
|
+
const auth = getFirebaseAuthFromFirebase(app);
|
|
30
|
+
this.instance = auth;
|
|
31
|
+
return auth;
|
|
32
|
+
} catch (error) {
|
|
33
|
+
const errorMessage = error instanceof Error ? error.message : 'Auth initialization failed';
|
|
34
|
+
this.setError(errorMessage);
|
|
35
|
+
throw error;
|
|
36
|
+
}
|
|
47
37
|
}
|
|
48
38
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
getAuth(): Auth | null {
|
|
53
|
-
// Attempt initialization if not already initialized
|
|
54
|
-
if (!this.isInitialized() && !this.getInitializationError()) {
|
|
55
|
-
try {
|
|
56
|
-
const app = getFirebaseApp();
|
|
57
|
-
if (app) {
|
|
58
|
-
this.initialize();
|
|
59
|
-
}
|
|
60
|
-
} catch (error) {
|
|
61
|
-
// Silently handle auto-initialization errors
|
|
62
|
-
// The error will be stored in state for later retrieval
|
|
63
|
-
const errorMessage = error instanceof Error ? error.message : 'Auto-initialization failed';
|
|
64
|
-
this.setError(errorMessage);
|
|
65
|
-
}
|
|
39
|
+
getAuth(): Auth {
|
|
40
|
+
if (!this.isInitialized()) {
|
|
41
|
+
return this.initialize();
|
|
66
42
|
}
|
|
67
|
-
|
|
68
|
-
return this.getInstance(true);
|
|
43
|
+
return this.getInstance();
|
|
69
44
|
}
|
|
70
|
-
}
|
|
71
45
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
console.warn('[Firebase] Could not create FirebaseAuth client singleton.');
|
|
46
|
+
private static instance: FirebaseAuthClientSingleton | null = null;
|
|
47
|
+
|
|
48
|
+
static getInstance(): FirebaseAuthClientSingleton {
|
|
49
|
+
if (!this.instance) {
|
|
50
|
+
this.instance = new FirebaseAuthClientSingleton();
|
|
78
51
|
}
|
|
79
|
-
return
|
|
52
|
+
return this.instance;
|
|
80
53
|
}
|
|
81
54
|
}
|
|
82
55
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
export const
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
56
|
+
const firebaseAuthClientSingleton = FirebaseAuthClientSingleton.getInstance();
|
|
57
|
+
|
|
58
|
+
export const initializeFirebaseAuth = (): Auth => {
|
|
59
|
+
return firebaseAuthClientSingleton.initialize();
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export const getFirebaseAuth = (): Auth => {
|
|
63
|
+
return firebaseAuthClientSingleton.getAuth();
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export const isFirebaseAuthInitialized = (): boolean => {
|
|
67
|
+
return firebaseAuthClientSingleton.isInitialized();
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
export const getFirebaseAuthInitializationError = (): Error | null => {
|
|
71
|
+
return firebaseAuthClientSingleton.getInitializationError();
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export const resetFirebaseAuthClient = (): void => {
|
|
75
|
+
firebaseAuthClientSingleton.reset();
|
|
76
|
+
};
|
|
89
77
|
|
|
90
78
|
export type { Auth } from 'firebase/auth';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './FirebaseAuthInitializer';
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export * from './anonymous-auth.service';
|
|
2
|
+
export * from './apple-auth.service';
|
|
3
|
+
export * from './apple-auth.types';
|
|
4
|
+
export * from './auth-listener.service';
|
|
5
|
+
export * from './auth-utils.service';
|
|
6
|
+
export * from './email-auth.service';
|
|
7
|
+
export * from './google-auth.service';
|
|
8
|
+
export * from './google-auth.types';
|
|
9
|
+
export * from './google-oauth.service';
|
|
10
|
+
export * from './password.service';
|
|
11
|
+
export * from './user-document-builder.util';
|
|
12
|
+
export * from './user-document.service';
|
|
13
|
+
export * from './user-document.types';
|
|
14
|
+
export * from './crypto.util';
|
|
15
|
+
export * from './firestore-utils.service';
|
|
16
|
+
export * from './utils';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './auth-result-converter.util';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './auth.store';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './auth-guard.util';
|
|
@@ -2,20 +2,13 @@
|
|
|
2
2
|
* useGoogleOAuth Hook
|
|
3
3
|
* Handles Google OAuth flow using expo-auth-session and Firebase auth
|
|
4
4
|
*
|
|
5
|
-
* This hook delegates business logic to GoogleOAuthHookService.
|
|
6
|
-
* Focuses only on React state management and side effects.
|
|
7
|
-
*
|
|
8
5
|
* Max lines: 150 (enforced for maintainability)
|
|
9
6
|
*/
|
|
10
7
|
|
|
11
|
-
import { useState, useCallback
|
|
8
|
+
import { useState, useCallback } from 'react';
|
|
12
9
|
import { getFirebaseAuth } from '../../infrastructure/config/FirebaseAuthClient';
|
|
13
10
|
import type { GoogleOAuthConfig } from '../../infrastructure/services/google-oauth.service';
|
|
14
|
-
import {
|
|
15
|
-
GoogleOAuthHookService,
|
|
16
|
-
createGoogleOAuthHookService,
|
|
17
|
-
isExpoAuthSessionAvailable,
|
|
18
|
-
} from './GoogleOAuthHookService';
|
|
11
|
+
import { GoogleOAuthService } from '../../infrastructure/services/google-oauth.service';
|
|
19
12
|
|
|
20
13
|
export interface UseGoogleOAuthResult {
|
|
21
14
|
signInWithGoogle: () => Promise<SocialAuthResult>;
|
|
@@ -33,68 +26,41 @@ interface SocialAuthResult {
|
|
|
33
26
|
|
|
34
27
|
/**
|
|
35
28
|
* Hook for Google OAuth authentication
|
|
36
|
-
* Requires expo-auth-session and expo-web-browser to be installed
|
|
37
29
|
*/
|
|
38
30
|
export function useGoogleOAuth(config?: GoogleOAuthConfig): UseGoogleOAuthResult {
|
|
39
31
|
const [isLoading, setIsLoading] = useState(false);
|
|
40
32
|
const [googleError, setGoogleError] = useState<string | null>(null);
|
|
41
33
|
|
|
42
|
-
|
|
43
|
-
const service = useMemo(() => createGoogleOAuthHookService(config), [config]);
|
|
44
|
-
|
|
45
|
-
// Update service when config changes
|
|
46
|
-
useEffect(() => {
|
|
47
|
-
service.updateConfig(config);
|
|
48
|
-
}, [service, config]);
|
|
49
|
-
|
|
50
|
-
// Memoize service checks
|
|
51
|
-
const googleAvailable = useMemo(() => service.isAvailable(), [service]);
|
|
52
|
-
const googleConfigured = useMemo(() => service.isConfigured(), [service]);
|
|
53
|
-
|
|
54
|
-
// Get auth request tuple from service
|
|
55
|
-
const [, response] = service.getAuthRequest();
|
|
56
|
-
|
|
57
|
-
// Handle OAuth response
|
|
58
|
-
useEffect(() => {
|
|
59
|
-
if (!googleAvailable || !response) return;
|
|
60
|
-
|
|
61
|
-
const handleResponse = async () => {
|
|
62
|
-
setIsLoading(true);
|
|
63
|
-
setGoogleError(null);
|
|
64
|
-
|
|
65
|
-
try {
|
|
66
|
-
const auth = getFirebaseAuth();
|
|
67
|
-
await service.handleResponse(response, auth);
|
|
68
|
-
} catch (error) {
|
|
69
|
-
setGoogleError(service.getErrorMessage(error));
|
|
70
|
-
} finally {
|
|
71
|
-
setIsLoading(false);
|
|
72
|
-
}
|
|
73
|
-
};
|
|
34
|
+
const service = new GoogleOAuthService();
|
|
74
35
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
console.error('[useGoogleOAuth] Unexpected error in handleResponse:', err);
|
|
78
|
-
}
|
|
79
|
-
});
|
|
80
|
-
}, [response, googleAvailable, service]);
|
|
36
|
+
const googleAvailable = service.isAvailable();
|
|
37
|
+
const googleConfigured = service.isConfigured(config);
|
|
81
38
|
|
|
82
|
-
// Sign in with Google
|
|
83
39
|
const signInWithGoogle = useCallback(async (): Promise<SocialAuthResult> => {
|
|
84
40
|
setIsLoading(true);
|
|
85
41
|
setGoogleError(null);
|
|
86
42
|
|
|
87
43
|
try {
|
|
88
44
|
const auth = getFirebaseAuth();
|
|
89
|
-
|
|
45
|
+
const result = await service.signInWithOAuth(auth, config);
|
|
46
|
+
|
|
47
|
+
if (!result.success) {
|
|
48
|
+
setGoogleError(result.error || 'Google sign-in failed');
|
|
49
|
+
return { success: false, error: result.error };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
success: true,
|
|
54
|
+
isNewUser: result.isNewUser,
|
|
55
|
+
};
|
|
90
56
|
} catch (error) {
|
|
91
|
-
const errorMessage =
|
|
57
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
92
58
|
setGoogleError(errorMessage);
|
|
93
59
|
return { success: false, error: errorMessage };
|
|
94
60
|
} finally {
|
|
95
61
|
setIsLoading(false);
|
|
96
62
|
}
|
|
97
|
-
}, [service]);
|
|
63
|
+
}, [service, config]);
|
|
98
64
|
|
|
99
65
|
return {
|
|
100
66
|
signInWithGoogle,
|
|
@@ -104,10 +70,3 @@ export function useGoogleOAuth(config?: GoogleOAuthConfig): UseGoogleOAuthResult
|
|
|
104
70
|
googleError,
|
|
105
71
|
};
|
|
106
72
|
}
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* Check if expo-auth-session is available
|
|
110
|
-
* Useful for conditional rendering
|
|
111
|
-
*/
|
|
112
|
-
export { isExpoAuthSessionAvailable };
|
|
113
|
-
|
|
@@ -3,14 +3,11 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Domain-Driven Design: Infrastructure implementation of Firestore client
|
|
5
5
|
* Singleton pattern for managing Firestore instance
|
|
6
|
-
*
|
|
7
|
-
* IMPORTANT: This package requires Firebase App to be initialized first.
|
|
8
|
-
* Use @umituz/react-native-firebase to initialize Firebase App.
|
|
9
6
|
*/
|
|
10
7
|
|
|
11
8
|
import type { Firestore } from 'firebase/firestore';
|
|
9
|
+
import { getFirestore as getFirestoreFromFirebase } from 'firebase/firestore';
|
|
12
10
|
import { getFirebaseApp } from '../../../../shared/infrastructure/config/services/FirebaseInitializationService';
|
|
13
|
-
import { FirebaseFirestoreInitializer } from './initializers/FirebaseFirestoreInitializer';
|
|
14
11
|
import { ServiceClientSingleton } from '../../../../shared/infrastructure/config/base/ServiceClientSingleton';
|
|
15
12
|
|
|
16
13
|
/**
|
|
@@ -19,79 +16,64 @@ import { ServiceClientSingleton } from '../../../../shared/infrastructure/config
|
|
|
19
16
|
*/
|
|
20
17
|
class FirestoreClientSingleton extends ServiceClientSingleton<Firestore> {
|
|
21
18
|
private constructor() {
|
|
22
|
-
super(
|
|
23
|
-
serviceName: 'Firestore',
|
|
24
|
-
initializer: () => {
|
|
25
|
-
const app = getFirebaseApp();
|
|
26
|
-
if (!app) {
|
|
27
|
-
this.setError('Firebase App is not initialized');
|
|
28
|
-
return null;
|
|
29
|
-
}
|
|
30
|
-
return FirebaseFirestoreInitializer.initialize(app);
|
|
31
|
-
},
|
|
32
|
-
autoInitializer: () => {
|
|
33
|
-
const app = getFirebaseApp();
|
|
34
|
-
if (!app) return null;
|
|
35
|
-
return FirebaseFirestoreInitializer.initialize(app);
|
|
36
|
-
},
|
|
37
|
-
});
|
|
19
|
+
super();
|
|
38
20
|
}
|
|
39
21
|
|
|
40
|
-
|
|
22
|
+
initialize(): Firestore {
|
|
23
|
+
try {
|
|
24
|
+
const app = getFirebaseApp();
|
|
25
|
+
if (!app) {
|
|
26
|
+
this.setError('Firebase App is not initialized');
|
|
27
|
+
throw new Error('Firebase App is not initialized');
|
|
28
|
+
}
|
|
41
29
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
30
|
+
const firestore = getFirestoreFromFirebase(app);
|
|
31
|
+
this.instance = firestore;
|
|
32
|
+
return firestore;
|
|
33
|
+
} catch (error) {
|
|
34
|
+
const errorMessage = error instanceof Error ? error.message : 'Firestore initialization failed';
|
|
35
|
+
this.setError(errorMessage);
|
|
36
|
+
throw error;
|
|
45
37
|
}
|
|
46
|
-
return this.instance;
|
|
47
38
|
}
|
|
48
39
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
return
|
|
40
|
+
getFirestore(): Firestore {
|
|
41
|
+
if (!this.isInitialized()) {
|
|
42
|
+
return this.initialize();
|
|
43
|
+
}
|
|
44
|
+
return this.getInstance();
|
|
54
45
|
}
|
|
55
46
|
|
|
56
|
-
|
|
57
|
-
* Get Firestore instance with auto-initialization
|
|
58
|
-
*/
|
|
59
|
-
getFirestore(): Firestore | null {
|
|
60
|
-
return this.getInstance(true);
|
|
61
|
-
}
|
|
62
|
-
}
|
|
47
|
+
private static instance: FirestoreClientSingleton | null = null;
|
|
63
48
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
} catch (error) {
|
|
68
|
-
if (__DEV__) {
|
|
69
|
-
console.error('[Firestore] Could not create Firestore client singleton:', error);
|
|
49
|
+
static getInstance(): FirestoreClientSingleton {
|
|
50
|
+
if (!this.instance) {
|
|
51
|
+
this.instance = new FirestoreClientSingleton();
|
|
70
52
|
}
|
|
71
|
-
return
|
|
53
|
+
return this.instance;
|
|
72
54
|
}
|
|
73
55
|
}
|
|
74
56
|
|
|
75
|
-
|
|
57
|
+
const firestoreClientSingleton = FirestoreClientSingleton.getInstance();
|
|
76
58
|
|
|
77
|
-
export
|
|
78
|
-
return
|
|
79
|
-
}
|
|
59
|
+
export const initializeFirestore = (): Firestore => {
|
|
60
|
+
return firestoreClientSingleton.initialize();
|
|
61
|
+
};
|
|
80
62
|
|
|
81
|
-
export
|
|
82
|
-
return
|
|
83
|
-
}
|
|
63
|
+
export const getFirestore = (): Firestore => {
|
|
64
|
+
return firestoreClientSingleton.getFirestore();
|
|
65
|
+
};
|
|
84
66
|
|
|
85
|
-
export
|
|
86
|
-
return
|
|
87
|
-
}
|
|
67
|
+
export const isFirestoreInitialized = (): boolean => {
|
|
68
|
+
return firestoreClientSingleton.isInitialized();
|
|
69
|
+
};
|
|
88
70
|
|
|
89
|
-
export
|
|
90
|
-
return
|
|
91
|
-
}
|
|
71
|
+
export const getFirestoreInitializationError = (): Error | null => {
|
|
72
|
+
return firestoreClientSingleton.getInitializationError();
|
|
73
|
+
};
|
|
92
74
|
|
|
93
|
-
export
|
|
94
|
-
|
|
95
|
-
}
|
|
75
|
+
export const resetFirestoreClient = (): void => {
|
|
76
|
+
firestoreClientSingleton.reset();
|
|
77
|
+
};
|
|
96
78
|
|
|
97
79
|
export type { Firestore } from 'firebase/firestore';
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error Handler
|
|
3
|
+
* Centralized error handling for services
|
|
4
|
+
*
|
|
5
|
+
* Max lines: 150 (enforced for maintainability)
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { Result } from '../../domain/utils';
|
|
9
|
+
|
|
10
|
+
export interface ErrorHandlerOptions {
|
|
11
|
+
readonly serviceName?: string;
|
|
12
|
+
readonly defaultErrorCode?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface ErrorInfo {
|
|
16
|
+
readonly code: string;
|
|
17
|
+
readonly message: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export class ErrorHandler {
|
|
21
|
+
readonly serviceName: string;
|
|
22
|
+
readonly defaultErrorCode: string;
|
|
23
|
+
|
|
24
|
+
constructor(options: ErrorHandlerOptions = {}) {
|
|
25
|
+
this.serviceName = options.serviceName || 'Service';
|
|
26
|
+
this.defaultErrorCode = options.defaultErrorCode || 'ERROR';
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async handleAsync<T>(
|
|
30
|
+
operation: () => Promise<T>,
|
|
31
|
+
errorCode?: string
|
|
32
|
+
): Promise<Result<T>> {
|
|
33
|
+
try {
|
|
34
|
+
const result = await operation();
|
|
35
|
+
return {
|
|
36
|
+
success: true,
|
|
37
|
+
data: result,
|
|
38
|
+
};
|
|
39
|
+
} catch (error) {
|
|
40
|
+
return {
|
|
41
|
+
success: false,
|
|
42
|
+
error: this.toErrorInfo(error, errorCode),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
handle<T>(operation: () => T, errorCode?: string): Result<T> {
|
|
48
|
+
try {
|
|
49
|
+
const result = operation();
|
|
50
|
+
return {
|
|
51
|
+
success: true,
|
|
52
|
+
data: result,
|
|
53
|
+
};
|
|
54
|
+
} catch (error) {
|
|
55
|
+
return {
|
|
56
|
+
success: false,
|
|
57
|
+
error: this.toErrorInfo(error, errorCode),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
toErrorInfo(error: unknown, code?: string): ErrorInfo {
|
|
63
|
+
if (error instanceof Error) {
|
|
64
|
+
return {
|
|
65
|
+
code: code || this.defaultErrorCode,
|
|
66
|
+
message: error.message,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
code: code || this.defaultErrorCode,
|
|
72
|
+
message: String(error),
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
getUserMessage(error: ErrorInfo): string {
|
|
77
|
+
return error.message;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export const defaultErrorHandler = new ErrorHandler();
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Service Base
|
|
3
|
+
* Base class for all services with common functionality
|
|
4
|
+
*
|
|
5
|
+
* Max lines: 150 (enforced for maintainability)
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { Result } from '../../domain/utils';
|
|
9
|
+
|
|
10
|
+
export interface ServiceBaseOptions {
|
|
11
|
+
readonly serviceName?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export class ServiceBase {
|
|
15
|
+
readonly serviceName: string;
|
|
16
|
+
|
|
17
|
+
constructor(options: ServiceBaseOptions = {}) {
|
|
18
|
+
this.serviceName = options.serviceName || 'Service';
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
protected async execute<T>(
|
|
22
|
+
operation: () => Promise<T>,
|
|
23
|
+
errorCode?: string
|
|
24
|
+
): Promise<Result<T>> {
|
|
25
|
+
try {
|
|
26
|
+
const result = await operation();
|
|
27
|
+
return {
|
|
28
|
+
success: true,
|
|
29
|
+
data: result,
|
|
30
|
+
};
|
|
31
|
+
} catch (error) {
|
|
32
|
+
return {
|
|
33
|
+
success: false,
|
|
34
|
+
error: {
|
|
35
|
+
code: errorCode || `${this.serviceName}_ERROR`,
|
|
36
|
+
message: error instanceof Error ? error.message : 'Unknown error',
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
protected executeSync<T>(
|
|
43
|
+
operation: () => T,
|
|
44
|
+
errorCode?: string
|
|
45
|
+
): Result<T> {
|
|
46
|
+
try {
|
|
47
|
+
const result = operation();
|
|
48
|
+
return {
|
|
49
|
+
success: true,
|
|
50
|
+
data: result,
|
|
51
|
+
};
|
|
52
|
+
} catch (error) {
|
|
53
|
+
return {
|
|
54
|
+
success: false,
|
|
55
|
+
error: {
|
|
56
|
+
code: errorCode || `${this.serviceName}_ERROR`,
|
|
57
|
+
message: error instanceof Error ? error.message : 'Unknown error',
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -5,12 +5,13 @@
|
|
|
5
5
|
* Max lines: 150 (enforced for maintainability)
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
export abstract class ServiceClientSingleton<TInstance> {
|
|
8
|
+
export abstract class ServiceClientSingleton<TInstance, TConfig = unknown> {
|
|
9
9
|
protected instance: TInstance | null = null;
|
|
10
|
+
protected initializationError: Error | null = null;
|
|
10
11
|
|
|
11
12
|
protected constructor() {}
|
|
12
13
|
|
|
13
|
-
abstract initialize(): Promise<TInstance
|
|
14
|
+
abstract initialize(config?: TConfig): Promise<TInstance> | TInstance;
|
|
14
15
|
|
|
15
16
|
getInstance(): TInstance {
|
|
16
17
|
if (!this.instance) {
|
|
@@ -23,7 +24,16 @@ export abstract class ServiceClientSingleton<TInstance> {
|
|
|
23
24
|
return this.instance !== null;
|
|
24
25
|
}
|
|
25
26
|
|
|
27
|
+
getInitializationError(): Error | null {
|
|
28
|
+
return this.initializationError;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
setError(message: string): void {
|
|
32
|
+
this.initializationError = new Error(message);
|
|
33
|
+
}
|
|
34
|
+
|
|
26
35
|
reset(): void {
|
|
27
36
|
this.instance = null;
|
|
37
|
+
this.initializationError = null;
|
|
28
38
|
}
|
|
29
39
|
}
|