@umituz/react-native-exception 1.2.19 → 1.2.20
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 +3 -3
- package/src/domain/entities/ExceptionEntity.ts +1 -1
- package/src/infrastructure/services/ExceptionHandler.ts +2 -2
- package/src/infrastructure/services/ExceptionLogger.ts +8 -25
- package/src/infrastructure/services/ExceptionService.ts +5 -20
- package/src/infrastructure/storage/ExceptionStore.ts +4 -4
- package/src/presentation/components/EmptyState.tsx +17 -17
- package/src/presentation/components/ErrorBoundary.tsx +20 -26
- package/src/presentation/components/ErrorState.tsx +19 -19
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.20",
|
|
4
4
|
"description": "Exception handling and error tracking for React Native apps",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "./src/index.ts",
|
|
@@ -25,16 +25,16 @@
|
|
|
25
25
|
"url": "https://github.com/umituz/react-native-exception"
|
|
26
26
|
},
|
|
27
27
|
"peerDependencies": {
|
|
28
|
+
"@umituz/react-native-design-system": "latest",
|
|
28
29
|
"@umituz/react-native-firebase": "latest",
|
|
29
|
-
"@umituz/react-native-firebase-auth": "latest",
|
|
30
30
|
"@umituz/react-native-storage": "latest",
|
|
31
31
|
"@umituz/react-native-uuid": "latest",
|
|
32
32
|
"react": ">=18.2.0",
|
|
33
33
|
"react-native": ">=0.74.0"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
|
+
"@umituz/react-native-design-system": "latest",
|
|
36
37
|
"@umituz/react-native-firebase": "latest",
|
|
37
|
-
"@umituz/react-native-firebase-auth": "latest",
|
|
38
38
|
"@umituz/react-native-storage": "latest",
|
|
39
39
|
"@umituz/react-native-uuid": "latest",
|
|
40
40
|
"@types/react": "~19.1.10",
|
|
@@ -83,8 +83,8 @@ export class ExceptionHandler {
|
|
|
83
83
|
// Remove sensitive fields
|
|
84
84
|
const sensitiveFields = ['password', 'token', 'apiKey', 'secret'];
|
|
85
85
|
sensitiveFields.forEach(field => {
|
|
86
|
-
if (sanitized[field
|
|
87
|
-
|
|
86
|
+
if (sanitized.metadata && sanitized.metadata[field]) {
|
|
87
|
+
sanitized.metadata = { ...sanitized.metadata, [field]: '***' };
|
|
88
88
|
}
|
|
89
89
|
});
|
|
90
90
|
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
import type { ExceptionEntity } from '../../domain/entities/ExceptionEntity';
|
|
12
12
|
import { ExceptionHandler } from './ExceptionHandler';
|
|
13
|
+
import { storageRepository } from '@umituz/react-native-storage';
|
|
13
14
|
|
|
14
15
|
export class ExceptionLogger {
|
|
15
16
|
private static readonly STORAGE_KEY = '@exceptions';
|
|
@@ -31,7 +32,7 @@ export class ExceptionLogger {
|
|
|
31
32
|
existingExceptions.splice(this.maxStoredExceptions);
|
|
32
33
|
}
|
|
33
34
|
|
|
34
|
-
await
|
|
35
|
+
await storageRepository.setString(ExceptionLogger.STORAGE_KEY, JSON.stringify(existingExceptions));
|
|
35
36
|
} catch (error) {
|
|
36
37
|
// Fallback to console if storage fails
|
|
37
38
|
console.error('Failed to log exception:', error);
|
|
@@ -43,8 +44,11 @@ export class ExceptionLogger {
|
|
|
43
44
|
*/
|
|
44
45
|
async getStoredExceptions(): Promise<ExceptionEntity[]> {
|
|
45
46
|
try {
|
|
46
|
-
const
|
|
47
|
-
|
|
47
|
+
const result = await storageRepository.getString(ExceptionLogger.STORAGE_KEY, '[]');
|
|
48
|
+
if (result.success) {
|
|
49
|
+
return JSON.parse(result.data);
|
|
50
|
+
}
|
|
51
|
+
return [];
|
|
48
52
|
} catch (error) {
|
|
49
53
|
console.warn('Failed to get stored exceptions:', error);
|
|
50
54
|
return [];
|
|
@@ -56,7 +60,7 @@ export class ExceptionLogger {
|
|
|
56
60
|
*/
|
|
57
61
|
async clearStoredExceptions(): Promise<void> {
|
|
58
62
|
try {
|
|
59
|
-
await
|
|
63
|
+
await storageRepository.setString(ExceptionLogger.STORAGE_KEY, '[]');
|
|
60
64
|
} catch (error) {
|
|
61
65
|
console.warn('Failed to clear stored exceptions:', error);
|
|
62
66
|
}
|
|
@@ -135,25 +139,4 @@ export class ExceptionLogger {
|
|
|
135
139
|
setMaxStoredExceptions(limit: number): void {
|
|
136
140
|
this.maxStoredExceptions = Math.max(1, limit);
|
|
137
141
|
}
|
|
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
|
-
const g = globalThis as any;
|
|
145
|
-
if (typeof g !== 'undefined' && g.localStorage) {
|
|
146
|
-
return g.localStorage.getItem(key);
|
|
147
|
-
}
|
|
148
|
-
// For React Native, this would be overridden
|
|
149
|
-
return null;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
protected async setStorageItem(key: string, value: string): Promise<void> {
|
|
153
|
-
const g = globalThis as any;
|
|
154
|
-
if (typeof g !== 'undefined' && g.localStorage) {
|
|
155
|
-
g.localStorage.setItem(key, value);
|
|
156
|
-
}
|
|
157
|
-
// For React Native, this would be overridden
|
|
158
|
-
}
|
|
159
142
|
}
|
|
@@ -126,15 +126,15 @@ export class ExceptionService {
|
|
|
126
126
|
/**
|
|
127
127
|
* Handle storage/permission errors
|
|
128
128
|
*/
|
|
129
|
-
handleStorageError(error: Error, context: ExceptionContext = {}): void {
|
|
130
|
-
this.handleException(error, 'error', 'storage', context);
|
|
129
|
+
async handleStorageError(error: Error, context: ExceptionContext = {}): Promise<void> {
|
|
130
|
+
await this.handleException(error, 'error', 'storage', context);
|
|
131
131
|
}
|
|
132
132
|
|
|
133
133
|
/**
|
|
134
134
|
* Handle fatal errors
|
|
135
135
|
*/
|
|
136
|
-
handleFatalError(error: Error, context: ExceptionContext = {}): void {
|
|
137
|
-
this.handleException(error, 'fatal', 'system', context);
|
|
136
|
+
async handleFatalError(error: Error, context: ExceptionContext = {}): Promise<void> {
|
|
137
|
+
await this.handleException(error, 'fatal', 'system', context);
|
|
138
138
|
}
|
|
139
139
|
|
|
140
140
|
/**
|
|
@@ -145,25 +145,10 @@ export class ExceptionService {
|
|
|
145
145
|
}
|
|
146
146
|
}
|
|
147
147
|
|
|
148
|
-
// Export
|
|
148
|
+
// Export default instance
|
|
149
149
|
export const exceptionService = new ExceptionService();
|
|
150
150
|
|
|
151
151
|
|
|
152
152
|
|
|
153
153
|
|
|
154
154
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
@@ -22,14 +22,14 @@ export const useExceptionStore = createStore<ExceptionState, ExceptionActions>({
|
|
|
22
22
|
exceptions: [],
|
|
23
23
|
},
|
|
24
24
|
persist: false,
|
|
25
|
-
actions: (set, get) => ({
|
|
25
|
+
actions: (set: (state: Partial<ExceptionState>) => void, get: () => ExceptionState) => ({
|
|
26
26
|
addException: (exception: ExceptionEntity) =>
|
|
27
27
|
set({ exceptions: [...get().exceptions, exception] }),
|
|
28
28
|
clearExceptions: () => set({ exceptions: [] }),
|
|
29
29
|
markAsHandled: (id: string) =>
|
|
30
30
|
set({
|
|
31
|
-
exceptions: get().exceptions.map((e) =>
|
|
32
|
-
e.id === id ? { ...e,
|
|
31
|
+
exceptions: get().exceptions.map((e: ExceptionEntity) =>
|
|
32
|
+
e.id === id ? { ...e, handled: true } : e
|
|
33
33
|
),
|
|
34
34
|
}),
|
|
35
35
|
}),
|
|
@@ -39,6 +39,6 @@ export const useExceptionStore = createStore<ExceptionState, ExceptionActions>({
|
|
|
39
39
|
* Hook to get exceptions from store
|
|
40
40
|
*/
|
|
41
41
|
export const useExceptions = () => {
|
|
42
|
-
const exceptions = useExceptionStore((state) => state.exceptions);
|
|
42
|
+
const exceptions = useExceptionStore((state: ExceptionState) => state.exceptions);
|
|
43
43
|
return exceptions;
|
|
44
44
|
};
|
|
@@ -84,35 +84,35 @@ export const EmptyState: React.FC<EmptyStateProps> = ({
|
|
|
84
84
|
};
|
|
85
85
|
|
|
86
86
|
const styles = StyleSheet.create({
|
|
87
|
+
actionButton: {
|
|
88
|
+
borderRadius: STATIC_TOKENS.borders.radius.md,
|
|
89
|
+
marginTop: STATIC_TOKENS.spacing.sm,
|
|
90
|
+
paddingHorizontal: STATIC_TOKENS.spacing.lg,
|
|
91
|
+
paddingVertical: STATIC_TOKENS.spacing.md,
|
|
92
|
+
},
|
|
93
|
+
actionButtonText: {
|
|
94
|
+
// AtomicText handles typography
|
|
95
|
+
},
|
|
87
96
|
container: {
|
|
88
|
-
flex: 1,
|
|
89
97
|
alignItems: "center",
|
|
98
|
+
flex: 1,
|
|
90
99
|
justifyContent: "center",
|
|
91
100
|
padding: STATIC_TOKENS.spacing.xl,
|
|
92
101
|
},
|
|
102
|
+
description: {
|
|
103
|
+
marginBottom: STATIC_TOKENS.spacing.lg,
|
|
104
|
+
maxWidth: 280,
|
|
105
|
+
},
|
|
93
106
|
iconContainer: {
|
|
94
|
-
width: 120,
|
|
95
|
-
height: 120,
|
|
96
|
-
borderRadius: 60,
|
|
97
107
|
alignItems: "center",
|
|
108
|
+
borderRadius: 60,
|
|
109
|
+
height: 120,
|
|
98
110
|
justifyContent: "center",
|
|
99
111
|
marginBottom: STATIC_TOKENS.spacing.lg,
|
|
112
|
+
width: 120,
|
|
100
113
|
},
|
|
101
114
|
title: {
|
|
102
115
|
marginBottom: STATIC_TOKENS.spacing.sm,
|
|
103
116
|
},
|
|
104
|
-
description: {
|
|
105
|
-
marginBottom: STATIC_TOKENS.spacing.lg,
|
|
106
|
-
maxWidth: 280,
|
|
107
|
-
},
|
|
108
|
-
actionButton: {
|
|
109
|
-
paddingHorizontal: STATIC_TOKENS.spacing.lg,
|
|
110
|
-
paddingVertical: STATIC_TOKENS.spacing.md,
|
|
111
|
-
borderRadius: STATIC_TOKENS.borders.radius.md,
|
|
112
|
-
marginTop: STATIC_TOKENS.spacing.sm,
|
|
113
|
-
},
|
|
114
|
-
actionButtonText: {
|
|
115
|
-
// AtomicText handles typography
|
|
116
|
-
},
|
|
117
117
|
});
|
|
118
118
|
|
|
@@ -94,42 +94,36 @@ const ErrorDisplay: React.FC<ErrorDisplayProps> = ({ error, onReset }) => {
|
|
|
94
94
|
|
|
95
95
|
const getStyles = (tokens: ReturnType<typeof useAppDesignTokens>) =>
|
|
96
96
|
StyleSheet.create({
|
|
97
|
-
container: {
|
|
98
|
-
flex: 1,
|
|
99
|
-
justifyContent: 'center',
|
|
100
|
-
alignItems: 'center',
|
|
101
|
-
padding: tokens.spacing.lg,
|
|
102
|
-
backgroundColor: tokens.colors.backgroundPrimary,
|
|
103
|
-
},
|
|
104
|
-
title: {
|
|
105
|
-
fontSize: tokens.typography.headlineSmall.fontSize,
|
|
106
|
-
fontWeight: tokens.typography.headlineSmall.fontWeight,
|
|
107
|
-
marginBottom: tokens.spacing.sm,
|
|
108
|
-
color: tokens.colors.textPrimary,
|
|
109
|
-
},
|
|
110
|
-
message: {
|
|
111
|
-
fontSize: tokens.typography.bodyLarge.fontSize,
|
|
112
|
-
color: tokens.colors.textSecondary,
|
|
113
|
-
textAlign: 'center',
|
|
114
|
-
marginBottom: tokens.spacing.lg,
|
|
115
|
-
},
|
|
116
97
|
button: {
|
|
117
98
|
backgroundColor: tokens.colors.primary,
|
|
99
|
+
borderRadius: tokens.borders.radius.sm,
|
|
118
100
|
paddingHorizontal: tokens.spacing.lg,
|
|
119
101
|
paddingVertical: tokens.spacing.sm,
|
|
120
|
-
borderRadius: tokens.borders.radius.sm,
|
|
121
102
|
},
|
|
122
103
|
buttonText: {
|
|
123
104
|
color: tokens.colors.textInverse,
|
|
124
105
|
fontSize: tokens.typography.bodyLarge.fontSize,
|
|
125
106
|
fontWeight: tokens.typography.labelLarge.fontWeight,
|
|
126
107
|
},
|
|
108
|
+
container: {
|
|
109
|
+
alignItems: 'center',
|
|
110
|
+
backgroundColor: tokens.colors.backgroundPrimary,
|
|
111
|
+
flex: 1,
|
|
112
|
+
justifyContent: 'center',
|
|
113
|
+
padding: tokens.spacing.lg,
|
|
114
|
+
},
|
|
115
|
+
message: {
|
|
116
|
+
color: tokens.colors.textSecondary,
|
|
117
|
+
fontSize: tokens.typography.bodyLarge.fontSize,
|
|
118
|
+
marginBottom: tokens.spacing.lg,
|
|
119
|
+
textAlign: 'center',
|
|
120
|
+
},
|
|
121
|
+
title: {
|
|
122
|
+
color: tokens.colors.textPrimary,
|
|
123
|
+
fontSize: tokens.typography.headlineSmall.fontSize,
|
|
124
|
+
fontWeight: tokens.typography.headlineSmall.fontWeight,
|
|
125
|
+
marginBottom: tokens.spacing.sm,
|
|
126
|
+
},
|
|
127
127
|
});
|
|
128
128
|
|
|
129
129
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
@@ -47,7 +47,7 @@ export const ErrorState: React.FC<ErrorStateProps> = ({
|
|
|
47
47
|
<View
|
|
48
48
|
style={[styles.iconContainer, { backgroundColor: tokens.colors.surface }]}
|
|
49
49
|
>
|
|
50
|
-
<AtomicIcon name={icon as
|
|
50
|
+
<AtomicIcon name={icon as never} size="xxl" color="secondary" />
|
|
51
51
|
</View>
|
|
52
52
|
)}
|
|
53
53
|
|
|
@@ -78,36 +78,36 @@ export const ErrorState: React.FC<ErrorStateProps> = ({
|
|
|
78
78
|
};
|
|
79
79
|
|
|
80
80
|
const styles = StyleSheet.create({
|
|
81
|
+
actionButton: {
|
|
82
|
+
alignItems: "center",
|
|
83
|
+
borderRadius: STATIC_TOKENS.borders.radius.full,
|
|
84
|
+
flexDirection: "row",
|
|
85
|
+
gap: STATIC_TOKENS.spacing.sm,
|
|
86
|
+
marginTop: STATIC_TOKENS.spacing.sm,
|
|
87
|
+
paddingHorizontal: STATIC_TOKENS.spacing.lg,
|
|
88
|
+
paddingVertical: STATIC_TOKENS.spacing.md,
|
|
89
|
+
},
|
|
81
90
|
container: {
|
|
82
|
-
flex: 1,
|
|
83
91
|
alignItems: "center",
|
|
92
|
+
flex: 1,
|
|
84
93
|
justifyContent: "center",
|
|
85
94
|
padding: STATIC_TOKENS.spacing.xl,
|
|
86
95
|
},
|
|
96
|
+
description: {
|
|
97
|
+
marginBottom: STATIC_TOKENS.spacing.lg,
|
|
98
|
+
maxWidth: 280,
|
|
99
|
+
textAlign: "center",
|
|
100
|
+
},
|
|
87
101
|
iconContainer: {
|
|
88
|
-
width: 120,
|
|
89
|
-
height: 120,
|
|
90
|
-
borderRadius: 60,
|
|
91
102
|
alignItems: "center",
|
|
103
|
+
borderRadius: 60,
|
|
104
|
+
height: 120,
|
|
92
105
|
justifyContent: "center",
|
|
93
106
|
marginBottom: STATIC_TOKENS.spacing.lg,
|
|
107
|
+
width: 120,
|
|
94
108
|
},
|
|
95
109
|
title: {
|
|
96
110
|
marginBottom: STATIC_TOKENS.spacing.sm,
|
|
97
111
|
textAlign: "center",
|
|
98
112
|
},
|
|
99
|
-
description: {
|
|
100
|
-
marginBottom: STATIC_TOKENS.spacing.lg,
|
|
101
|
-
maxWidth: 280,
|
|
102
|
-
textAlign: "center",
|
|
103
|
-
},
|
|
104
|
-
actionButton: {
|
|
105
|
-
flexDirection: "row",
|
|
106
|
-
alignItems: "center",
|
|
107
|
-
paddingHorizontal: STATIC_TOKENS.spacing.lg,
|
|
108
|
-
paddingVertical: STATIC_TOKENS.spacing.md,
|
|
109
|
-
borderRadius: STATIC_TOKENS.borders.radius.full,
|
|
110
|
-
marginTop: STATIC_TOKENS.spacing.sm,
|
|
111
|
-
gap: STATIC_TOKENS.spacing.sm,
|
|
112
|
-
},
|
|
113
113
|
});
|