@umituz/react-native-exception 1.2.0 → 1.2.2
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 +5 -0
- package/package.json +5 -3
- package/src/domain/entities/ExceptionEntity.ts +4 -4
- package/src/domain/repositories/IExceptionRepository.ts +5 -0
- package/src/index.ts +5 -1
- package/src/infrastructure/services/ExceptionHandler.ts +93 -0
- package/src/infrastructure/services/ExceptionLogger.ts +157 -0
- package/src/infrastructure/services/ExceptionReporter.ts +124 -0
- package/src/infrastructure/services/ExceptionService.ts +87 -34
- package/src/infrastructure/storage/ExceptionStore.ts +5 -0
- package/src/presentation/components/EmptyState.tsx +1 -6
- package/src/presentation/components/ErrorBoundary.tsx +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-exception",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.2",
|
|
4
4
|
"description": "Exception handling and error tracking for React Native apps",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "./src/index.ts",
|
|
@@ -32,10 +32,11 @@
|
|
|
32
32
|
"react": ">=18.2.0",
|
|
33
33
|
"react-native": ">=0.74.0",
|
|
34
34
|
"zustand": "^5.0.2",
|
|
35
|
-
"uuid": "^
|
|
35
|
+
"uuid": "^10.0.0",
|
|
36
36
|
"react-native-get-random-values": "^1.11.0",
|
|
37
37
|
"@umituz/react-native-design-system-theme": "*",
|
|
38
|
-
"@umituz/react-native-design-system": "*"
|
|
38
|
+
"@umituz/react-native-design-system": "*",
|
|
39
|
+
"lucide-react-native": "^0.468.0"
|
|
39
40
|
},
|
|
40
41
|
"peerDependenciesMeta": {
|
|
41
42
|
"@umituz/react-native-design-system-theme": {
|
|
@@ -43,6 +44,7 @@
|
|
|
43
44
|
}
|
|
44
45
|
},
|
|
45
46
|
"devDependencies": {
|
|
47
|
+
"@types/uuid": "^10.0.0",
|
|
46
48
|
"typescript": "^5.3.3",
|
|
47
49
|
"@types/react": "^18.2.45",
|
|
48
50
|
"@types/react-native": "^0.73.0",
|
|
@@ -20,7 +20,7 @@ export interface ExceptionContext {
|
|
|
20
20
|
export interface ExceptionEntity {
|
|
21
21
|
id: string;
|
|
22
22
|
message: string;
|
|
23
|
-
|
|
23
|
+
stackTrace?: string;
|
|
24
24
|
severity: ExceptionSeverity;
|
|
25
25
|
category: ExceptionCategory;
|
|
26
26
|
context: ExceptionContext;
|
|
@@ -34,7 +34,7 @@ export interface ErrorLog {
|
|
|
34
34
|
exceptionId: string;
|
|
35
35
|
userId?: string;
|
|
36
36
|
message: string;
|
|
37
|
-
|
|
37
|
+
stackTrace?: string;
|
|
38
38
|
severity: ExceptionSeverity;
|
|
39
39
|
category: ExceptionCategory;
|
|
40
40
|
context: ExceptionContext;
|
|
@@ -53,7 +53,7 @@ export function createException(
|
|
|
53
53
|
return {
|
|
54
54
|
id: uuidv4(),
|
|
55
55
|
message: error.message,
|
|
56
|
-
|
|
56
|
+
stackTrace: error.stack,
|
|
57
57
|
severity,
|
|
58
58
|
category,
|
|
59
59
|
context,
|
|
@@ -74,7 +74,7 @@ export function createErrorLog(
|
|
|
74
74
|
exceptionId: exception.id,
|
|
75
75
|
userId: exception.context.userId,
|
|
76
76
|
message: exception.message,
|
|
77
|
-
|
|
77
|
+
stackTrace: exception.stackTrace,
|
|
78
78
|
severity: exception.severity,
|
|
79
79
|
category: exception.category,
|
|
80
80
|
context: exception.context,
|
package/src/index.ts
CHANGED
|
@@ -40,7 +40,11 @@ export type {
|
|
|
40
40
|
export { useExceptionStore, useExceptions } from './infrastructure/storage/ExceptionStore';
|
|
41
41
|
|
|
42
42
|
// Services
|
|
43
|
-
|
|
43
|
+
// Infrastructure Services
|
|
44
|
+
export { ExceptionService } from './infrastructure/services/ExceptionService';
|
|
45
|
+
export { ExceptionHandler } from './infrastructure/services/ExceptionHandler';
|
|
46
|
+
export { ExceptionReporter } from './infrastructure/services/ExceptionReporter';
|
|
47
|
+
export { ExceptionLogger } from './infrastructure/services/ExceptionLogger';
|
|
44
48
|
|
|
45
49
|
// =============================================================================
|
|
46
50
|
// PRESENTATION LAYER EXPORTS
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Exception Handler Service
|
|
3
|
+
*
|
|
4
|
+
* Handles exception creation, validation, and basic processing.
|
|
5
|
+
*
|
|
6
|
+
* SOLID: Single Responsibility - Only exception handling
|
|
7
|
+
* DRY: Centralized exception processing logic
|
|
8
|
+
* KISS: Simple exception handling interface
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type {
|
|
12
|
+
ExceptionEntity,
|
|
13
|
+
ExceptionContext,
|
|
14
|
+
ExceptionSeverity,
|
|
15
|
+
ExceptionCategory,
|
|
16
|
+
} from '../../domain/entities/ExceptionEntity';
|
|
17
|
+
import {
|
|
18
|
+
createException,
|
|
19
|
+
shouldReportException,
|
|
20
|
+
} from '../../domain/entities/ExceptionEntity';
|
|
21
|
+
|
|
22
|
+
export class ExceptionHandler {
|
|
23
|
+
/**
|
|
24
|
+
* Create and validate an exception
|
|
25
|
+
*/
|
|
26
|
+
static createException(
|
|
27
|
+
error: Error,
|
|
28
|
+
severity: ExceptionSeverity = 'error',
|
|
29
|
+
category: ExceptionCategory = 'unknown',
|
|
30
|
+
context: ExceptionContext = {},
|
|
31
|
+
): ExceptionEntity {
|
|
32
|
+
return createException(error, severity, category, context);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Check if exception should be reported
|
|
37
|
+
*/
|
|
38
|
+
static shouldReportException(exception: ExceptionEntity): boolean {
|
|
39
|
+
return shouldReportException(exception);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Validate exception data
|
|
44
|
+
*/
|
|
45
|
+
static validateException(exception: ExceptionEntity): boolean {
|
|
46
|
+
return !!(
|
|
47
|
+
exception.id &&
|
|
48
|
+
exception.message &&
|
|
49
|
+
exception.timestamp &&
|
|
50
|
+
exception.severity &&
|
|
51
|
+
exception.category
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Sanitize exception for logging
|
|
57
|
+
*/
|
|
58
|
+
static sanitizeException(exception: ExceptionEntity): ExceptionEntity {
|
|
59
|
+
return {
|
|
60
|
+
...exception,
|
|
61
|
+
message: this.sanitizeMessage(exception.message),
|
|
62
|
+
context: this.sanitizeContext(exception.context),
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Sanitize error message
|
|
68
|
+
*/
|
|
69
|
+
private static sanitizeMessage(message: string): string {
|
|
70
|
+
// Remove sensitive information from error messages
|
|
71
|
+
return message
|
|
72
|
+
.replace(/password=[^&\s]*/gi, 'password=***')
|
|
73
|
+
.replace(/token=[^&\s]*/gi, 'token=***')
|
|
74
|
+
.replace(/key=[^&\s]*/gi, 'key=***');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Sanitize context object
|
|
79
|
+
*/
|
|
80
|
+
private static sanitizeContext(context: ExceptionContext): ExceptionContext {
|
|
81
|
+
const sanitized: ExceptionContext = { ...context };
|
|
82
|
+
|
|
83
|
+
// Remove sensitive fields
|
|
84
|
+
const sensitiveFields = ['password', 'token', 'apiKey', 'secret'];
|
|
85
|
+
sensitiveFields.forEach(field => {
|
|
86
|
+
if (sanitized[field as keyof ExceptionContext]) {
|
|
87
|
+
(sanitized as any)[field] = '***';
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
return sanitized;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Exception Logger Service
|
|
3
|
+
*
|
|
4
|
+
* Handles local logging and persistence of exceptions.
|
|
5
|
+
*
|
|
6
|
+
* SOLID: Single Responsibility - Only exception logging/persistence
|
|
7
|
+
* DRY: Centralized logging logic
|
|
8
|
+
* KISS: Simple logging interface
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { ExceptionEntity } from '../../domain/entities/ExceptionEntity';
|
|
12
|
+
import { ExceptionHandler } from './ExceptionHandler';
|
|
13
|
+
|
|
14
|
+
export class ExceptionLogger {
|
|
15
|
+
private static readonly STORAGE_KEY = '@exceptions';
|
|
16
|
+
private maxStoredExceptions = 100;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Log exception locally
|
|
20
|
+
*/
|
|
21
|
+
async logException(exception: ExceptionEntity): Promise<void> {
|
|
22
|
+
try {
|
|
23
|
+
const sanitizedException = ExceptionHandler.sanitizeException(exception);
|
|
24
|
+
const existingExceptions = await this.getStoredExceptions();
|
|
25
|
+
|
|
26
|
+
// Add new exception
|
|
27
|
+
existingExceptions.unshift(sanitizedException);
|
|
28
|
+
|
|
29
|
+
// Limit storage size
|
|
30
|
+
if (existingExceptions.length > this.maxStoredExceptions) {
|
|
31
|
+
existingExceptions.splice(this.maxStoredExceptions);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
await this.setStorageItem(ExceptionLogger.STORAGE_KEY, JSON.stringify(existingExceptions));
|
|
35
|
+
} catch (error) {
|
|
36
|
+
// Fallback to console if storage fails
|
|
37
|
+
console.error('Failed to log exception:', error);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Get stored exceptions
|
|
43
|
+
*/
|
|
44
|
+
async getStoredExceptions(): Promise<ExceptionEntity[]> {
|
|
45
|
+
try {
|
|
46
|
+
const stored = await this.getStorageItem(ExceptionLogger.STORAGE_KEY);
|
|
47
|
+
return stored ? JSON.parse(stored) : [];
|
|
48
|
+
} catch (error) {
|
|
49
|
+
console.warn('Failed to get stored exceptions:', error);
|
|
50
|
+
return [];
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Clear all stored exceptions
|
|
56
|
+
*/
|
|
57
|
+
async clearStoredExceptions(): Promise<void> {
|
|
58
|
+
try {
|
|
59
|
+
await this.setStorageItem(ExceptionLogger.STORAGE_KEY, JSON.stringify([]));
|
|
60
|
+
} catch (error) {
|
|
61
|
+
console.warn('Failed to clear stored exceptions:', error);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Get exceptions by category
|
|
67
|
+
*/
|
|
68
|
+
async getExceptionsByCategory(category: ExceptionEntity['category']): Promise<ExceptionEntity[]> {
|
|
69
|
+
const exceptions = await this.getStoredExceptions();
|
|
70
|
+
return exceptions.filter(ex => ex.category === category);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Get exceptions by severity
|
|
75
|
+
*/
|
|
76
|
+
async getExceptionsBySeverity(severity: ExceptionEntity['severity']): Promise<ExceptionEntity[]> {
|
|
77
|
+
const exceptions = await this.getStoredExceptions();
|
|
78
|
+
return exceptions.filter(ex => ex.severity === severity);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Get recent exceptions (last N days)
|
|
83
|
+
*/
|
|
84
|
+
async getRecentExceptions(days: number = 7): Promise<ExceptionEntity[]> {
|
|
85
|
+
const exceptions = await this.getStoredExceptions();
|
|
86
|
+
const cutoffDate = new Date();
|
|
87
|
+
cutoffDate.setDate(cutoffDate.getDate() - days);
|
|
88
|
+
|
|
89
|
+
return exceptions.filter(ex => new Date(ex.timestamp) >= cutoffDate);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Get exception statistics
|
|
94
|
+
*/
|
|
95
|
+
async getExceptionStats(): Promise<{
|
|
96
|
+
total: number;
|
|
97
|
+
bySeverity: Record<ExceptionEntity['severity'], number>;
|
|
98
|
+
byCategory: Record<ExceptionEntity['category'], number>;
|
|
99
|
+
}> {
|
|
100
|
+
const exceptions = await this.getStoredExceptions();
|
|
101
|
+
|
|
102
|
+
const bySeverity: Record<ExceptionEntity['severity'], number> = {
|
|
103
|
+
fatal: 0,
|
|
104
|
+
error: 0,
|
|
105
|
+
warning: 0,
|
|
106
|
+
info: 0,
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const byCategory: Record<ExceptionEntity['category'], number> = {
|
|
110
|
+
network: 0,
|
|
111
|
+
validation: 0,
|
|
112
|
+
authentication: 0,
|
|
113
|
+
authorization: 0,
|
|
114
|
+
'business-logic': 0,
|
|
115
|
+
system: 0,
|
|
116
|
+
storage: 0,
|
|
117
|
+
unknown: 0,
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
exceptions.forEach(ex => {
|
|
121
|
+
bySeverity[ex.severity]++;
|
|
122
|
+
byCategory[ex.category]++;
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
total: exceptions.length,
|
|
127
|
+
bySeverity,
|
|
128
|
+
byCategory,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Update max stored exceptions limit
|
|
134
|
+
*/
|
|
135
|
+
setMaxStoredExceptions(limit: number): void {
|
|
136
|
+
this.maxStoredExceptions = Math.max(1, limit);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Storage abstraction (can be overridden for different storage backends)
|
|
141
|
+
*/
|
|
142
|
+
protected async getStorageItem(key: string): Promise<string | null> {
|
|
143
|
+
// Default implementation using localStorage/globalThis for web, AsyncStorage for RN
|
|
144
|
+
if (typeof globalThis !== 'undefined' && globalThis.localStorage) {
|
|
145
|
+
return globalThis.localStorage.getItem(key);
|
|
146
|
+
}
|
|
147
|
+
// For React Native, this would be overridden
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
protected async setStorageItem(key: string, value: string): Promise<void> {
|
|
152
|
+
if (typeof globalThis !== 'undefined' && globalThis.localStorage) {
|
|
153
|
+
globalThis.localStorage.setItem(key, value);
|
|
154
|
+
}
|
|
155
|
+
// For React Native, this would be overridden
|
|
156
|
+
}
|
|
157
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Exception Reporter Service
|
|
3
|
+
*
|
|
4
|
+
* Handles reporting exceptions to external services.
|
|
5
|
+
*
|
|
6
|
+
* SOLID: Single Responsibility - Only exception reporting
|
|
7
|
+
* DRY: Centralized reporting logic
|
|
8
|
+
* KISS: Simple reporting interface
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { ExceptionEntity } from '../../domain/entities/ExceptionEntity';
|
|
12
|
+
|
|
13
|
+
export interface ExceptionReporterConfig {
|
|
14
|
+
enabled: boolean;
|
|
15
|
+
endpoint?: string;
|
|
16
|
+
apiKey?: string;
|
|
17
|
+
environment: 'development' | 'staging' | 'production';
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export class ExceptionReporter {
|
|
21
|
+
private config: ExceptionReporterConfig;
|
|
22
|
+
|
|
23
|
+
constructor(config: ExceptionReporterConfig) {
|
|
24
|
+
this.config = config;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Report exception to external service
|
|
29
|
+
*/
|
|
30
|
+
async reportException(exception: ExceptionEntity): Promise<boolean> {
|
|
31
|
+
if (!this.config.enabled) {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
// Default console reporting for development
|
|
37
|
+
if (this.config.environment === 'development') {
|
|
38
|
+
return this.reportToConsole(exception);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// External service reporting
|
|
42
|
+
if (this.config.endpoint) {
|
|
43
|
+
return await this.reportToExternalService(exception);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return false;
|
|
47
|
+
} catch (error) {
|
|
48
|
+
// Don't throw in reporter to avoid infinite loops
|
|
49
|
+
console.warn('Exception reporting failed:', error);
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Report to console (development)
|
|
56
|
+
*/
|
|
57
|
+
private reportToConsole(exception: ExceptionEntity): boolean {
|
|
58
|
+
const level = this.getConsoleLevel(exception.severity);
|
|
59
|
+
|
|
60
|
+
console[level](
|
|
61
|
+
`[${exception.severity.toUpperCase()}] ${exception.category}:`,
|
|
62
|
+
exception.message,
|
|
63
|
+
{
|
|
64
|
+
id: exception.id,
|
|
65
|
+
timestamp: exception.timestamp,
|
|
66
|
+
context: exception.context,
|
|
67
|
+
stack: exception.stackTrace,
|
|
68
|
+
}
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Report to external service
|
|
76
|
+
*/
|
|
77
|
+
private async reportToExternalService(exception: ExceptionEntity): Promise<boolean> {
|
|
78
|
+
if (!this.config.endpoint) {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const response = await fetch(this.config.endpoint, {
|
|
83
|
+
method: 'POST',
|
|
84
|
+
headers: {
|
|
85
|
+
'Content-Type': 'application/json',
|
|
86
|
+
...(this.config.apiKey && { 'Authorization': `Bearer ${this.config.apiKey}` }),
|
|
87
|
+
},
|
|
88
|
+
body: JSON.stringify({
|
|
89
|
+
id: exception.id,
|
|
90
|
+
message: exception.message,
|
|
91
|
+
severity: exception.severity,
|
|
92
|
+
category: exception.category,
|
|
93
|
+
timestamp: exception.timestamp,
|
|
94
|
+
context: exception.context,
|
|
95
|
+
stackTrace: exception.stackTrace,
|
|
96
|
+
environment: this.config.environment,
|
|
97
|
+
}),
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
return response.ok;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Get console level for severity
|
|
105
|
+
*/
|
|
106
|
+
private getConsoleLevel(severity: ExceptionEntity['severity']): 'log' | 'warn' | 'error' {
|
|
107
|
+
switch (severity) {
|
|
108
|
+
case 'fatal':
|
|
109
|
+
case 'error':
|
|
110
|
+
return 'error';
|
|
111
|
+
case 'warning':
|
|
112
|
+
return 'warn';
|
|
113
|
+
default:
|
|
114
|
+
return 'log';
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Update reporter configuration
|
|
120
|
+
*/
|
|
121
|
+
updateConfig(config: Partial<ExceptionReporterConfig>): void {
|
|
122
|
+
this.config = { ...this.config, ...config };
|
|
123
|
+
}
|
|
124
|
+
}
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Exception Service - Infrastructure Layer
|
|
3
|
-
*
|
|
3
|
+
*
|
|
4
|
+
* Facade for exception handling using composition.
|
|
5
|
+
* Delegates to specialized services for specific operations.
|
|
6
|
+
*
|
|
7
|
+
* SOLID: Facade pattern - Single entry point, delegates to specialists
|
|
8
|
+
* DRY: Avoids code duplication by composing smaller services
|
|
9
|
+
* KISS: Simple interface, complex operations delegated
|
|
4
10
|
*/
|
|
5
11
|
|
|
6
12
|
import type {
|
|
@@ -9,41 +15,45 @@ import type {
|
|
|
9
15
|
ExceptionSeverity,
|
|
10
16
|
ExceptionCategory,
|
|
11
17
|
} from '../../domain/entities/ExceptionEntity';
|
|
12
|
-
import {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
} from '../../domain/entities/ExceptionEntity';
|
|
18
|
+
import { ExceptionHandler } from './ExceptionHandler';
|
|
19
|
+
import { ExceptionReporter } from './ExceptionReporter';
|
|
20
|
+
import { ExceptionLogger } from './ExceptionLogger';
|
|
16
21
|
import { useExceptionStore } from '../storage/ExceptionStore';
|
|
17
22
|
|
|
18
23
|
export class ExceptionService {
|
|
19
|
-
private
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
24
|
+
private logger: ExceptionLogger;
|
|
25
|
+
private reporter: ExceptionReporter;
|
|
26
|
+
|
|
27
|
+
constructor(reporterConfig?: ExceptionReporter['config']) {
|
|
28
|
+
this.logger = new ExceptionLogger();
|
|
29
|
+
this.reporter = new ExceptionReporter(
|
|
30
|
+
reporterConfig || {
|
|
31
|
+
enabled: false,
|
|
32
|
+
environment: 'development'
|
|
33
|
+
}
|
|
34
|
+
);
|
|
28
35
|
}
|
|
29
36
|
|
|
30
37
|
/**
|
|
31
38
|
* Handle an exception
|
|
32
39
|
*/
|
|
33
|
-
handleException(
|
|
40
|
+
async handleException(
|
|
34
41
|
error: Error,
|
|
35
42
|
severity: ExceptionSeverity = 'error',
|
|
36
43
|
category: ExceptionCategory = 'unknown',
|
|
37
44
|
context: ExceptionContext = {},
|
|
38
|
-
): void {
|
|
39
|
-
const exception = createException(error, severity, category, context);
|
|
45
|
+
): Promise<void> {
|
|
46
|
+
const exception = ExceptionHandler.createException(error, severity, category, context);
|
|
40
47
|
|
|
41
48
|
// Add to store
|
|
42
49
|
useExceptionStore.getState().addException(exception);
|
|
43
50
|
|
|
51
|
+
// Log locally
|
|
52
|
+
await this.logger.logException(exception);
|
|
53
|
+
|
|
44
54
|
// Report to external service if needed
|
|
45
|
-
if (shouldReportException(exception)) {
|
|
46
|
-
this.reportException(exception);
|
|
55
|
+
if (ExceptionHandler.shouldReportException(exception)) {
|
|
56
|
+
await this.reporter.reportException(exception);
|
|
47
57
|
}
|
|
48
58
|
|
|
49
59
|
// Mark as handled
|
|
@@ -51,29 +61,66 @@ export class ExceptionService {
|
|
|
51
61
|
}
|
|
52
62
|
|
|
53
63
|
/**
|
|
54
|
-
*
|
|
64
|
+
* Handle network errors
|
|
55
65
|
*/
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
// Mark as reported
|
|
59
|
-
useExceptionStore.getState().markAsReported(exception.id);
|
|
60
|
-
} catch (error) {
|
|
61
|
-
// Silent failure
|
|
62
|
-
}
|
|
66
|
+
async handleNetworkError(error: Error, context: ExceptionContext = {}): Promise<void> {
|
|
67
|
+
await this.handleException(error, 'error', 'network', context);
|
|
63
68
|
}
|
|
64
69
|
|
|
65
70
|
/**
|
|
66
|
-
* Handle
|
|
71
|
+
* Handle validation errors
|
|
67
72
|
*/
|
|
68
|
-
|
|
69
|
-
this.handleException(error, '
|
|
73
|
+
async handleValidationError(error: Error, context: ExceptionContext = {}): Promise<void> {
|
|
74
|
+
await this.handleException(error, 'warning', 'validation', context);
|
|
70
75
|
}
|
|
71
76
|
|
|
72
77
|
/**
|
|
73
|
-
* Handle
|
|
78
|
+
* Handle authentication errors
|
|
74
79
|
*/
|
|
75
|
-
|
|
76
|
-
this.handleException(error, '
|
|
80
|
+
async handleAuthError(error: Error, context: ExceptionContext = {}): Promise<void> {
|
|
81
|
+
await this.handleException(error, 'error', 'authentication', context);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Handle system errors
|
|
86
|
+
*/
|
|
87
|
+
async handleSystemError(error: Error, context: ExceptionContext = {}): Promise<void> {
|
|
88
|
+
await this.handleException(error, 'fatal', 'system', context);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Get stored exceptions
|
|
93
|
+
*/
|
|
94
|
+
async getStoredExceptions(): Promise<ExceptionEntity[]> {
|
|
95
|
+
return this.logger.getStoredExceptions();
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Get exception statistics
|
|
100
|
+
*/
|
|
101
|
+
async getExceptionStats() {
|
|
102
|
+
return this.logger.getExceptionStats();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Clear stored exceptions
|
|
107
|
+
*/
|
|
108
|
+
async clearStoredExceptions(): Promise<void> {
|
|
109
|
+
await this.logger.clearStoredExceptions();
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Update reporter configuration
|
|
114
|
+
*/
|
|
115
|
+
updateReporterConfig(config: Partial<ExceptionReporter['config']>): void {
|
|
116
|
+
this.reporter.updateConfig(config);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Set max stored exceptions
|
|
121
|
+
*/
|
|
122
|
+
setMaxStoredExceptions(limit: number): void {
|
|
123
|
+
this.logger.setMaxStoredExceptions(limit);
|
|
77
124
|
}
|
|
78
125
|
|
|
79
126
|
/**
|
|
@@ -98,7 +145,13 @@ export class ExceptionService {
|
|
|
98
145
|
}
|
|
99
146
|
}
|
|
100
147
|
|
|
101
|
-
|
|
148
|
+
// Export for backward compatibility - create default instance
|
|
149
|
+
export const exceptionService = new ExceptionService();
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
|
|
102
155
|
|
|
103
156
|
|
|
104
157
|
|
|
@@ -7,12 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import React from "react";
|
|
9
9
|
import { View, StyleSheet, TouchableOpacity } from "react-native";
|
|
10
|
-
import {
|
|
11
|
-
useAppDesignTokens,
|
|
12
|
-
AtomicIcon,
|
|
13
|
-
AtomicText,
|
|
14
|
-
STATIC_TOKENS,
|
|
15
|
-
} from "@umituz/react-native-design-system";
|
|
10
|
+
import { AtomicIcon, AtomicText, useAppDesignTokens, STATIC_TOKENS } from "@umituz/react-native-design-system";
|
|
16
11
|
|
|
17
12
|
export interface EmptyStateProps {
|
|
18
13
|
icon?: string;
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
import React, { Component, ReactNode } from 'react';
|
|
7
7
|
import { View, Text, StyleSheet, TouchableOpacity } from 'react-native';
|
|
8
8
|
import { exceptionService } from '../../infrastructure/services/ExceptionService';
|
|
9
|
-
import { useAppDesignTokens } from '@umituz/react-native-design-system
|
|
9
|
+
import { useAppDesignTokens } from '@umituz/react-native-design-system';
|
|
10
10
|
|
|
11
11
|
interface Props {
|
|
12
12
|
children: ReactNode;
|