vanillaforge 1.9.0

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.
@@ -0,0 +1,442 @@
1
+ /**
2
+ * Error Handler Utility
3
+ *
4
+ * Centralized error handling system for the VanillaForge.
5
+ * Provides consistent error processing, user notifications, and error reporting.
6
+ *
7
+ * @author VanillaForge Team
8
+ * @version 3.0.0
9
+ * @since 2025-06-14
10
+ */
11
+
12
+ import { Logger } from './logger.js';
13
+ import { Notification } from './notification.js';
14
+
15
+ /**
16
+ * Error types enum
17
+ * @readonly
18
+ * @enum {string}
19
+ */
20
+ export const ErrorType = {
21
+ VALIDATION: 'validation',
22
+ AUTHENTICATION: 'authentication',
23
+ AUTHORIZATION: 'authorization',
24
+ NETWORK: 'network',
25
+ DATABASE: 'database',
26
+ SYSTEM: 'system',
27
+ USER_INPUT: 'user_input',
28
+ CONFIGURATION: 'configuration'
29
+ };
30
+
31
+ /**
32
+ * Error severity levels
33
+ * @readonly
34
+ * @enum {string}
35
+ */
36
+ export const ErrorSeverity = {
37
+ LOW: 'low',
38
+ MEDIUM: 'medium',
39
+ HIGH: 'high',
40
+ CRITICAL: 'critical'
41
+ };
42
+
43
+ /**
44
+ * Error Handler class for centralized error management
45
+ *
46
+ * Handles all application errors, provides user-friendly messages,
47
+ * and manages error reporting and recovery strategies.
48
+ */
49
+ export class ErrorHandler { /**
50
+ * Initialize the error handler
51
+ */
52
+ constructor(notification) {
53
+ this.logger = new Logger('ErrorHandler');
54
+
55
+ // Safely initialize notification system
56
+ if (notification) {
57
+ this.notification = notification;
58
+ } else if (typeof window !== 'undefined' && typeof document !== 'undefined') {
59
+ // Only create Notification in browser environment
60
+ this.notification = new Notification();
61
+ } else {
62
+ // Fallback for non-browser environments
63
+ this.notification = {
64
+ showToast: (message, type) => console.log(`[${type.toUpperCase()}] ${message}`),
65
+ showModal: (title, message, _options) => console.log(`[MODAL] ${title}: ${message}`)
66
+ };
67
+ }
68
+
69
+ this.errorCounts = new Map();
70
+ this.lastErrors = [];
71
+ this.maxLastErrors = 10;
72
+ this.logger.info('Error handler initialized');
73
+ }
74
+ /**
75
+ * Handle application errors
76
+ *
77
+ * @param {Error} error - The error object
78
+ * @param {Object} [context={}] - Additional context about the error
79
+ * @param {boolean} [showToUser=true] - Whether to show error to user
80
+ * @returns {Object} Processed error information
81
+ */
82
+ handleError(error, context = {}, showToUser = true) {
83
+ try {
84
+ const errorInfo = this.createErrorInfo(error, context);
85
+
86
+ this.logError(errorInfo);
87
+ this.trackError(errorInfo);
88
+
89
+ if (showToUser) {
90
+ this.showUserNotification(errorInfo);
91
+ }
92
+
93
+ this.reportError(errorInfo);
94
+
95
+ return errorInfo;
96
+ } catch (handlingError) {
97
+ this.logger.error('Error in error handler:', { error: handlingError, originalError: error });
98
+ this.notification.showToast('An unexpected error occurred.', 'error');
99
+ return this.createFallbackErrorInfo(error);
100
+ }
101
+ }
102
+
103
+ /**
104
+ * Create fallback error info when error handler fails
105
+ *
106
+ * @private
107
+ * @param {Error} error - Original error
108
+ * @returns {Object} Fallback error information
109
+ */
110
+ createFallbackErrorInfo(error) {
111
+ return {
112
+ id: this.generateErrorId(),
113
+ timestamp: new Date().toISOString(),
114
+ type: ErrorType.SYSTEM,
115
+ severity: ErrorSeverity.HIGH,
116
+ message: error?.message || 'Unknown error occurred',
117
+ handled: false,
118
+ fallback: true
119
+ };
120
+ }
121
+
122
+ /**
123
+ * Handle global errors (uncaught exceptions)
124
+ *
125
+ * @param {Error} error - The error object
126
+ * @param {Object} [context={}] - Additional context
127
+ */
128
+ handleGlobalError(error, context = {}) {
129
+ const errorInfo = this.handleError(error, {
130
+ ...context,
131
+ global: true,
132
+ severity: ErrorSeverity.HIGH
133
+ }, true);
134
+
135
+ this.logger.error('Global error caught', errorInfo);
136
+ }
137
+
138
+ /**
139
+ * Create structured error information
140
+ *
141
+ * @private
142
+ * @param {Error} error - The error object
143
+ * @param {Object} context - Additional context
144
+ * @returns {Object} Structured error information
145
+ */
146
+ createErrorInfo(error, context) {
147
+ const errorId = this.generateErrorId();
148
+ const timestamp = new Date().toISOString();
149
+
150
+ // Determine error type
151
+ const type = this.determineErrorType(error, context);
152
+
153
+ // Determine severity
154
+ const severity = this.determineSeverity(error, context, type);
155
+
156
+ return {
157
+ id: errorId,
158
+ timestamp,
159
+ type,
160
+ severity,
161
+ message: error?.message || 'Unknown error',
162
+ stack: error?.stack,
163
+ name: error?.name,
164
+ code: error?.code,
165
+ context: {
166
+ ...context,
167
+ url: window.location.href,
168
+ userAgent: navigator.userAgent,
169
+ timestamp: timestamp
170
+ },
171
+ user: this.getCurrentUserContext(),
172
+ handled: true
173
+ };
174
+ }
175
+
176
+ /**
177
+ * Determine error type based on error object and context
178
+ *
179
+ * @private
180
+ * @param {Error} error - The error object
181
+ * @param {Object} context - Error context
182
+ * @returns {string} Error type
183
+ */
184
+ determineErrorType(error, context) {
185
+ // Check context type first
186
+ if (context.type) {
187
+ return context.type;
188
+ }
189
+
190
+ // Check error properties
191
+ if (error?.code) {
192
+ if (error.code.includes('auth/')) {
193
+ return ErrorType.AUTHENTICATION;
194
+ }
195
+ if (error.code.includes('permission')) {
196
+ return ErrorType.AUTHORIZATION;
197
+ }
198
+ if (error.code.includes('network') || error.code.includes('offline')) {
199
+ return ErrorType.NETWORK;
200
+ }
201
+ }
202
+
203
+ // Check error message
204
+ const message = error?.message?.toLowerCase() || '';
205
+ if (message.includes('validation') || message.includes('invalid')) {
206
+ return ErrorType.VALIDATION;
207
+ }
208
+ if (message.includes('network') || message.includes('fetch')) {
209
+ return ErrorType.NETWORK;
210
+ }
211
+ if (message.includes('permission') || message.includes('unauthorized')) {
212
+ return ErrorType.AUTHORIZATION;
213
+ }
214
+
215
+ return ErrorType.SYSTEM;
216
+ }
217
+
218
+ /**
219
+ * Determine error severity
220
+ *
221
+ * @private
222
+ * @param {Error} error - The error object
223
+ * @param {Object} context - Error context
224
+ * @param {string} type - Error type
225
+ * @returns {string} Error severity
226
+ */
227
+ determineSeverity(error, context, type) {
228
+ // Check context severity first
229
+ if (context.severity) {
230
+ return context.severity;
231
+ }
232
+
233
+ // Global errors are high severity
234
+ if (context.global) {
235
+ return ErrorSeverity.HIGH;
236
+ }
237
+
238
+ // Type-based severity
239
+ switch (type) {
240
+ case ErrorType.SYSTEM:
241
+ case ErrorType.DATABASE:
242
+ return ErrorSeverity.HIGH;
243
+ case ErrorType.NETWORK:
244
+ case ErrorType.AUTHENTICATION:
245
+ return ErrorSeverity.MEDIUM;
246
+ case ErrorType.VALIDATION:
247
+ case ErrorType.USER_INPUT:
248
+ return ErrorSeverity.LOW;
249
+ default:
250
+ return ErrorSeverity.MEDIUM;
251
+ }
252
+ }
253
+
254
+ /**
255
+ * Log error information
256
+ *
257
+ * @private
258
+ * @param {Object} errorInfo - Structured error information
259
+ */
260
+ logError(errorInfo) {
261
+ const logData = {
262
+ errorId: errorInfo.id,
263
+ type: errorInfo.type,
264
+ severity: errorInfo.severity,
265
+ message: errorInfo.message,
266
+ context: errorInfo.context
267
+ };
268
+
269
+ // Log based on severity
270
+ switch (errorInfo.severity) {
271
+ case ErrorSeverity.CRITICAL:
272
+ case ErrorSeverity.HIGH:
273
+ this.logger.error(`[${errorInfo.type.toUpperCase()}] ${errorInfo.message}`, logData);
274
+ break;
275
+ case ErrorSeverity.MEDIUM:
276
+ this.logger.warn(`[${errorInfo.type.toUpperCase()}] ${errorInfo.message}`, logData);
277
+ break;
278
+ case ErrorSeverity.LOW:
279
+ this.logger.info(`[${errorInfo.type.toUpperCase()}] ${errorInfo.message}`, logData);
280
+ break;
281
+ }
282
+ }
283
+
284
+ /**
285
+ * Track error occurrence for analytics
286
+ *
287
+ * @private
288
+ * @param {Object} errorInfo - Structured error information
289
+ */
290
+ trackError(errorInfo) {
291
+ // Count error occurrences
292
+ const errorKey = `${errorInfo.type}:${errorInfo.message}`;
293
+ const count = this.errorCounts.get(errorKey) || 0;
294
+ this.errorCounts.set(errorKey, count + 1);
295
+
296
+ // Store recent errors
297
+ this.lastErrors.unshift(errorInfo);
298
+ if (this.lastErrors.length > this.maxLastErrors) {
299
+ this.lastErrors = this.lastErrors.slice(0, this.maxLastErrors);
300
+ }
301
+ }
302
+
303
+ showUserNotification(errorInfo) {
304
+ const message = this.getUserFriendlyMessage(errorInfo);
305
+
306
+ switch (errorInfo.severity) {
307
+ case ErrorSeverity.CRITICAL:
308
+ case ErrorSeverity.HIGH:
309
+ this.notification.showModal('Error', message, {
310
+ details: JSON.stringify(errorInfo, null, 2),
311
+ buttons: [
312
+ { label: 'Report Issue', action: 'report', onClick: () => this.reportIssue(errorInfo) },
313
+ { label: 'Close', action: 'close' }
314
+ ]
315
+ });
316
+ break;
317
+ case ErrorSeverity.MEDIUM:
318
+ this.notification.showToast(message, 'error');
319
+ break;
320
+ case ErrorSeverity.LOW:
321
+ this.notification.showToast(message, 'warning');
322
+ break;
323
+ }
324
+ }
325
+
326
+ /**
327
+ * Get user-friendly error message
328
+ *
329
+ * @private
330
+ * @param {Object} errorInfo - Structured error information
331
+ * @returns {string} User-friendly message
332
+ */
333
+ getUserFriendlyMessage(errorInfo) {
334
+ const messages = {
335
+ [ErrorType.VALIDATION]: `Please check the ${errorInfo.context.field || 'input'} field and try again.`,
336
+ [ErrorType.AUTHENTICATION]: 'Please sign in to continue.',
337
+ [ErrorType.AUTHORIZATION]: "You don't have permission to perform this action.",
338
+ [ErrorType.NETWORK]: 'Please check your internet connection and try again.',
339
+ [ErrorType.DATABASE]: "We're experiencing technical difficulties. Please try again later.",
340
+ [ErrorType.SYSTEM]: 'Something went wrong. Please try again later.',
341
+ [ErrorType.USER_INPUT]: 'Invalid input provided. Please correct and try again.',
342
+ [ErrorType.CONFIGURATION]: 'Configuration error. Please contact support.'
343
+ };
344
+
345
+ return messages[errorInfo.type] || messages[ErrorType.SYSTEM];
346
+ }
347
+
348
+
349
+ /**
350
+ * Report error to external services
351
+ *
352
+ * @private
353
+ * @param {Object} errorInfo - Error information
354
+ */
355
+ async reportError(errorInfo) {
356
+ if (typeof localStorage === 'undefined') return;
357
+ try {
358
+ const reports = JSON.parse(localStorage.getItem('ucm_error_reports') || '[]');
359
+ reports.push(errorInfo);
360
+ if (reports.length > 50) {
361
+ reports.shift();
362
+ }
363
+ localStorage.setItem('ucm_error_reports', JSON.stringify(reports));
364
+ } catch (_e) {
365
+ // Non-critical — storage quota exceeded or private mode.
366
+ }
367
+ }
368
+
369
+ /**
370
+ * Report issue via user action
371
+ *
372
+ * @private
373
+ * @param {Object} errorInfo - Error information
374
+ */
375
+ reportIssue(errorInfo) {
376
+ // This would open a support ticket or email
377
+ const subject = `Error Report: ${errorInfo.type}`;
378
+ const body = `Error ID: ${errorInfo.id}\nTimestamp: ${errorInfo.timestamp}\nMessage: ${errorInfo.message}\n\nTechnical Details:\n${JSON.stringify(errorInfo, null, 2)}`;
379
+
380
+ const mailtoLink = `mailto:support@example.com?subject=${encodeURIComponent(subject)}&body=${encodeURIComponent(body)}`;
381
+ window.open(mailtoLink);
382
+ }
383
+
384
+ /**
385
+ * Get current user context for error reporting
386
+ *
387
+ * @private
388
+ * @returns {Object} User context
389
+ */
390
+ getCurrentUserContext() {
391
+ try {
392
+ // This would get actual user information
393
+ return {
394
+ authenticated: false, // Would check actual auth state
395
+ timestamp: new Date().toISOString()
396
+ };
397
+ } catch (_e) {
398
+ return { error: 'Failed to get user context' };
399
+ }
400
+ }
401
+
402
+ /**
403
+ * Generate unique error ID
404
+ *
405
+ * @private
406
+ * @returns {string} Unique error ID
407
+ */
408
+ generateErrorId() {
409
+ return `err_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
410
+ }
411
+
412
+ /**
413
+ * Get error statistics
414
+ *
415
+ * @returns {Object} Error statistics
416
+ */
417
+ getErrorStats() {
418
+ return {
419
+ totalErrors: this.lastErrors.length,
420
+ errorCounts: Object.fromEntries(this.errorCounts),
421
+ recentErrors: this.lastErrors.slice(0, 5).map(err => ({
422
+ id: err.id,
423
+ type: err.type,
424
+ severity: err.severity,
425
+ timestamp: err.timestamp,
426
+ message: err.message
427
+ }))
428
+ };
429
+ }
430
+
431
+ /**
432
+ * Clear error history
433
+ */
434
+ clearErrorHistory() {
435
+ this.errorCounts.clear();
436
+ this.lastErrors = [];
437
+ if (typeof localStorage !== 'undefined') {
438
+ localStorage.removeItem('ucm_error_reports');
439
+ }
440
+ this.logger.info('Error history cleared');
441
+ }
442
+ }