/** * Error Boundary System für Module * Bietet Schutz vor Module-Crashes und Recovery-Mechanismen */ import { Logger } from './logger.js'; export class ModuleErrorBoundary { constructor() { this.crashedModules = new Set(); this.recoveryAttempts = new Map(); this.maxRecoveryAttempts = 3; this.recoveryDelay = 1000; // 1 second } /** * Wraps a module with error handling * @param {Object} module - The module to wrap * @param {string} moduleName - Name of the module for logging * @returns {Proxy} - Protected module instance */ wrapModule(module, moduleName) { if (!module || typeof module !== 'object') { Logger.warn(`[ErrorBoundary] Cannot wrap non-object module: ${moduleName}`); return module; } return new Proxy(module, { get: (target, prop, receiver) => { const originalValue = target[prop]; // If property is not a function, return as-is if (typeof originalValue !== 'function') { return originalValue; } // Check if property is non-configurable - return original if so const descriptor = Object.getOwnPropertyDescriptor(target, prop); if (descriptor && !descriptor.configurable) { return originalValue; } // Return wrapped function with error handling return (...args) => { try { const result = originalValue.apply(target, args); // Handle async functions if (result && typeof result.catch === 'function') { return result.catch(error => { this.handleModuleError(error, moduleName, prop, args); return this.getRecoveryValue(moduleName, prop); }); } return result; } catch (error) { this.handleModuleError(error, moduleName, prop, args); return this.getRecoveryValue(moduleName, prop); } }; }, getOwnPropertyDescriptor: (target, prop) => { const descriptor = Object.getOwnPropertyDescriptor(target, prop); // For non-configurable properties, return original descriptor if (descriptor && !descriptor.configurable) { return descriptor; } return descriptor; }, has: (target, prop) => { return prop in target; }, ownKeys: (target) => { return Object.getOwnPropertyNames(target); } }); } /** * Handles module errors with recovery logic * @param {Error} error - The error that occurred * @param {string} moduleName - Name of the failing module * @param {string} method - Method that failed * @param {Array} args - Arguments passed to the method */ handleModuleError(error, moduleName, method, args) { const errorKey = `${moduleName}.${method}`; Logger.error(`[ErrorBoundary] Module ${moduleName} crashed in ${method}():`, error); // Track crashed modules this.crashedModules.add(moduleName); // Track recovery attempts const attempts = this.recoveryAttempts.get(errorKey) || 0; this.recoveryAttempts.set(errorKey, attempts + 1); // Emit custom event for external handling window.dispatchEvent(new CustomEvent('module-error', { detail: { moduleName, method, error: error.message, args, attempts: attempts + 1 } })); // Attempt recovery if under limit if (attempts < this.maxRecoveryAttempts) { this.scheduleRecovery(moduleName, method, args); } else { Logger.error(`[ErrorBoundary] Module ${moduleName} exceeded recovery attempts. Marking as permanently failed.`); this.markModuleAsPermanentlyFailed(moduleName); } } /** * Schedules a recovery attempt for a failed module method * @param {string} moduleName - Name of the module * @param {string} method - Method to retry * @param {Array} args - Original arguments */ scheduleRecovery(moduleName, method, args) { setTimeout(() => { try { Logger.info(`[ErrorBoundary] Attempting recovery for ${moduleName}.${method}()`); // This would need module registry integration // For now, just log the attempt } catch (recoveryError) { Logger.error(`[ErrorBoundary] Recovery failed for ${moduleName}.${method}():`, recoveryError); } }, this.recoveryDelay); } /** * Returns a safe fallback value for failed module methods * @param {string} moduleName - Name of the module * @param {string} method - Method that failed * @returns {*} - Safe fallback value */ getRecoveryValue(moduleName, method) { // Return safe defaults based on common method names switch (method) { case 'init': case 'destroy': case 'update': case 'render': return Promise.resolve(); case 'getData': case 'getConfig': return {}; case 'isEnabled': case 'isActive': return false; default: return undefined; } } /** * Marks a module as permanently failed * @param {string} moduleName - Name of the module */ markModuleAsPermanentlyFailed(moduleName) { window.dispatchEvent(new CustomEvent('module-permanent-failure', { detail: { moduleName } })); } /** * Gets health status of all modules * @returns {Object} - Health status report */ getHealthStatus() { return { totalCrashedModules: this.crashedModules.size, crashedModules: Array.from(this.crashedModules), recoveryAttempts: Object.fromEntries(this.recoveryAttempts), timestamp: new Date().toISOString() }; } /** * Resets error tracking for a module (useful for hot reload) * @param {string} moduleName - Name of the module to reset */ resetModule(moduleName) { this.crashedModules.delete(moduleName); // Remove all recovery attempts for this module for (const [key] of this.recoveryAttempts) { if (key.startsWith(`${moduleName}.`)) { this.recoveryAttempts.delete(key); } } Logger.info(`[ErrorBoundary] Reset error tracking for module: ${moduleName}`); } /** * Clears all error tracking */ reset() { this.crashedModules.clear(); this.recoveryAttempts.clear(); Logger.info('[ErrorBoundary] Reset all error tracking'); } } // Global instance export const moduleErrorBoundary = new ModuleErrorBoundary(); // Global error handlers window.addEventListener('error', (event) => { Logger.error('[Global] Unhandled error:', event.error || event.message); }); window.addEventListener('unhandledrejection', (event) => { Logger.error('[Global] Unhandled promise rejection:', event.reason); });