@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 CHANGED
@@ -34,3 +34,8 @@ SOFTWARE.
34
34
 
35
35
 
36
36
 
37
+
38
+
39
+
40
+
41
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-exception",
3
- "version": "1.2.0",
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": "^9.0.0",
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
- stack?: string;
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
- stack?: string;
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
- stack: error.stack,
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
- stack: exception.stack,
77
+ stackTrace: exception.stackTrace,
78
78
  severity: exception.severity,
79
79
  category: exception.category,
80
80
  context: exception.context,
@@ -58,3 +58,8 @@ export interface IExceptionRepository {
58
58
 
59
59
 
60
60
 
61
+
62
+
63
+
64
+
65
+
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
- export { ExceptionService, exceptionService } from './infrastructure/services/ExceptionService';
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
- * Centralized exception handling and reporting
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
- createException,
14
- shouldReportException,
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 static instance: ExceptionService;
20
-
21
- private constructor() {}
22
-
23
- static getInstance(): ExceptionService {
24
- if (!ExceptionService.instance) {
25
- ExceptionService.instance = new ExceptionService();
26
- }
27
- return ExceptionService.instance;
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
- * Report exception to external service (e.g., Sentry)
64
+ * Handle network errors
55
65
  */
56
- private async reportException(exception: ExceptionEntity): Promise<void> {
57
- try {
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 network errors
71
+ * Handle validation errors
67
72
  */
68
- handleNetworkError(error: Error, context: ExceptionContext = {}): void {
69
- this.handleException(error, 'error', 'network', context);
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 validation errors
78
+ * Handle authentication errors
74
79
  */
75
- handleValidationError(error: Error, context: ExceptionContext = {}): void {
76
- this.handleException(error, 'warning', 'validation', context);
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
- export const exceptionService = ExceptionService.getInstance();
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
 
@@ -107,3 +107,8 @@ export const useExceptions = () => {
107
107
 
108
108
 
109
109
 
110
+
111
+
112
+
113
+
114
+
@@ -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-theme';
9
+ import { useAppDesignTokens } from '@umituz/react-native-design-system';
10
10
 
11
11
  interface Props {
12
12
  children: ReactNode;