UNPKG

gebeya-dala-error-fixer

Version:

Automatic runtime error detection and fix suggestions for Next.js applications with config-based setup

457 lines (451 loc) 15.7 kB
import { loadConfig, validateConfig } from './config'; import { ErrorDetector } from './ErrorDetector'; export class AutoInitializer { constructor() { this.errorDetector = null; this.modalContainer = null; this.floatingButton = null; this.errorCount = 0; this.currentError = null; this.currentSuggestion = null; this.isModalOpen = false; this.config = validateConfig(loadConfig()); this.init(); } static getInstance() { if (!AutoInitializer.instance) { AutoInitializer.instance = new AutoInitializer(); } return AutoInitializer.instance; } init() { // Only initialize in browser environment if (typeof window === 'undefined') return; // Check if should be enabled based on environment if (!this.shouldEnable()) return; // Wait for DOM to be ready if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => this.setupErrorDetection()); } else { this.setupErrorDetection(); } } shouldEnable() { if (!this.config.enabled) return false; const nodeEnv = process.env.NODE_ENV; switch (this.config.environment) { case 'development': return nodeEnv === 'development'; case 'production': return nodeEnv === 'production'; case 'all': return true; default: return nodeEnv === 'development'; } } setupErrorDetection() { // Initialize error detector this.errorDetector = new ErrorDetector((error, suggestion) => { this.handleError(error, suggestion); }); // Create UI elements this.createFloatingButton(); this.createModalContainer(); // Setup error boundary for React errors this.setupGlobalErrorBoundary(); } handleError(error, suggestion) { console.log('Error detected by auto-initializer:', error); this.currentError = error; this.currentSuggestion = suggestion; this.errorCount++; // Update floating button this.updateFloatingButton(); // Auto-show modal if enabled if (this.config.autoShow) { this.showModal(); } } createFloatingButton() { this.floatingButton = document.createElement('button'); this.floatingButton.innerHTML = '✓'; this.floatingButton.title = 'No errors detected'; // Apply styles const styles = { position: 'fixed', zIndex: '9998', width: '50px', height: '50px', borderRadius: '50%', backgroundColor: '#333', border: '2px solid #555', color: '#fff', fontSize: '18px', cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center', boxShadow: '0 4px 12px rgba(0, 0, 0, 0.3)', transition: 'all 0.2s ease', fontFamily: 'system-ui, -apple-system, sans-serif' }; // Set position based on config const position = this.getButtonPosition(); Object.assign(this.floatingButton.style, styles, position); // Add click handler this.floatingButton.addEventListener('click', () => { this.showModal(); }); // Add to document document.body.appendChild(this.floatingButton); } getButtonPosition() { switch (this.config.position) { case 'top-right': return { top: '20px', right: '20px' }; case 'top-left': return { top: '20px', left: '20px' }; case 'bottom-right': return { bottom: '20px', right: '20px' }; case 'bottom-left': return { bottom: '20px', left: '20px' }; default: return { top: '20px', right: '20px' }; } } updateFloatingButton() { if (!this.floatingButton) return; if (this.errorCount > 0) { this.floatingButton.innerHTML = '!'; this.floatingButton.style.backgroundColor = '#ef4444'; this.floatingButton.title = `${this.errorCount} error${this.errorCount > 1 ? 's' : ''} detected`; // Add error count badge const existingBadge = this.floatingButton.querySelector('.error-count'); if (existingBadge) { existingBadge.remove(); } const badge = document.createElement('div'); badge.className = 'error-count'; badge.textContent = this.errorCount > 99 ? '99+' : this.errorCount.toString(); badge.style.cssText = ` position: absolute; top: -8px; right: -8px; background-color: #dc2626; color: #fff; border-radius: 50%; width: 20px; height: 20px; font-size: 12px; display: flex; align-items: center; justify-content: center; font-weight: bold; `; this.floatingButton.appendChild(badge); } } createModalContainer() { this.modalContainer = document.createElement('div'); this.modalContainer.id = 'error-detector-modal'; this.modalContainer.style.display = 'none'; document.body.appendChild(this.modalContainer); } showModal() { if (!this.modalContainer || !this.currentError || !this.currentSuggestion) return; this.isModalOpen = true; this.modalContainer.style.display = 'block'; // Render modal content this.renderModal(); } hideModal() { if (!this.modalContainer) return; this.isModalOpen = false; this.modalContainer.style.display = 'none'; this.modalContainer.innerHTML = ''; } renderModal() { if (!this.modalContainer || !this.currentError || !this.currentSuggestion) return; const isDark = this.config.theme === 'dark'; const bgColor = isDark ? '#1a1a1a' : '#ffffff'; const textColor = isDark ? '#ffffff' : '#000000'; const borderColor = isDark ? '#333' : '#e5e5e5'; this.modalContainer.innerHTML = ` <div style=" position: fixed; top: 0; left: 0; right: 0; bottom: 0; background-color: rgba(0, 0, 0, 0.8); display: flex; align-items: center; justify-content: center; z-index: 9999; font-family: system-ui, -apple-system, sans-serif; "> <div style=" background-color: ${bgColor}; color: ${textColor}; border-radius: 12px; padding: 24px; max-width: 600px; width: 90%; max-height: 80vh; overflow: auto; box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3); border: 1px solid ${borderColor}; "> ${this.renderModalContent()} </div> </div> `; // Add event listeners this.addModalEventListeners(); } renderModalContent() { var _a; if (!this.currentError || !this.currentSuggestion) return ''; const isDark = this.config.theme === 'dark'; const secondaryBg = isDark ? '#2a2a2a' : '#f5f5f5'; const secondaryColor = isDark ? '#ccc' : '#666'; return ` <!-- Header --> <div style=" display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; padding-bottom: 16px; border-bottom: 1px solid ${isDark ? '#333' : '#e5e5e5'}; "> <div style="display: flex; align-items: center; gap: 12px;"> <div style=" width: 24px; height: 24px; background-color: #ef4444; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 14px; font-weight: bold; color: white; ">!</div> <h2 style="margin: 0; font-size: 18px; font-weight: 600;"> ${this.currentSuggestion.title} </h2> </div> <button id="close-modal" style=" background: none; border: none; color: #999; font-size: 24px; cursor: pointer; padding: 0; width: 24px; height: 24px; display: flex; align-items: center; justify-content: center; ">×</button> </div> <!-- Error Details --> <div style="margin-bottom: 20px;"> <div style="font-size: 12px; color: #999; margin-bottom: 8px;"> Error occurred at ${new Date(this.currentError.timestamp).toLocaleTimeString()} </div> <div style=" background-color: ${secondaryBg}; padding: 12px; border-radius: 6px; border: 1px solid ${isDark ? '#333' : '#e5e5e5'}; font-size: 14px; font-family: monospace; word-break: break-word; max-height: 100px; overflow: auto; "> ${this.currentError.message} </div> </div> <!-- Description --> <div style="margin-bottom: 20px;"> <h3 style="margin: 0 0 8px 0; font-size: 14px; font-weight: 600;"> What's happening? </h3> <p style="margin: 0; font-size: 14px; color: ${secondaryColor}; line-height: 1.4;"> ${this.currentSuggestion.description} </p> </div> ${this.currentSuggestion.code ? ` <!-- Code Example --> <div style="margin-bottom: 20px;"> <div style=" display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px; "> <h3 style="margin: 0; font-size: 14px; font-weight: 600;"> How to fix it: </h3> <button id="copy-code" style=" background: ${isDark ? '#333' : '#f5f5f5'}; border: 1px solid ${isDark ? '#555' : '#ccc'}; color: inherit; padding: 4px 8px; border-radius: 4px; font-size: 12px; cursor: pointer; ">Copy</button> </div> <pre style=" background-color: ${secondaryBg}; padding: 12px; border-radius: 6px; border: 1px solid ${isDark ? '#333' : '#e5e5e5'}; font-size: 13px; font-family: monospace; margin: 0; overflow: auto; white-space: pre-wrap; ">${this.currentSuggestion.code}</pre> </div> ` : ''} <!-- Actions --> <div style="margin-bottom: 20px;"> <h3 style="margin: 0 0 12px 0; font-size: 14px; font-weight: 600;"> Suggested actions: </h3> <ul style="margin: 0; padding-left: 20px; font-size: 14px; color: ${secondaryColor};"> ${this.currentSuggestion.actions.map(action => `<li style="margin-bottom: 4px;">${action}</li>`).join('')} </ul> </div> <!-- Fix It Button --> ${((_a = this.config.fixItButton) === null || _a === void 0 ? void 0 : _a.enabled) ? ` <div style=" display: flex; gap: 12px; justify-content: flex-end; margin-top: 24px; padding-top: 16px; border-top: 1px solid ${isDark ? '#333' : '#e5e5e5'}; "> <button id="fix-it-button" style=" background: linear-gradient(135deg, #10b981, #059669); color: white; border: none; padding: 12px 24px; border-radius: 8px; font-size: 14px; font-weight: 600; cursor: pointer; transition: all 0.2s ease; box-shadow: 0 2px 8px rgba(16, 185, 129, 0.3); "> 🔧 Fix it </button> <button id="dismiss-button" style=" background: ${isDark ? '#333' : '#f5f5f5'}; color: inherit; border: 1px solid ${isDark ? '#555' : '#ccc'}; padding: 12px 24px; border-radius: 8px; font-size: 14px; cursor: pointer; transition: all 0.2s ease; "> Dismiss </button> </div> ` : ''} `; } addModalEventListeners() { var _a, _b; // Close button const closeBtn = document.getElementById('close-modal'); if (closeBtn) { closeBtn.addEventListener('click', () => this.hideModal()); } // Copy code button const copyBtn = document.getElementById('copy-code'); if (copyBtn && ((_a = this.currentSuggestion) === null || _a === void 0 ? void 0 : _a.code)) { copyBtn.addEventListener('click', () => { navigator.clipboard.writeText(this.currentSuggestion.code); copyBtn.textContent = 'Copied!'; setTimeout(() => { copyBtn.textContent = 'Copy'; }, 2000); }); } // Fix it button const fixBtn = document.getElementById('fix-it-button'); if (fixBtn) { fixBtn.addEventListener('click', () => this.handleFixIt()); } // Dismiss button const dismissBtn = document.getElementById('dismiss-button'); if (dismissBtn) { dismissBtn.addEventListener('click', () => this.hideModal()); } // Click outside to close (_b = this.modalContainer) === null || _b === void 0 ? void 0 : _b.addEventListener('click', (e) => { if (e.target === this.modalContainer) { this.hideModal(); } }); } handleFixIt() { var _a, _b; if (((_a = this.config.fixItButton) === null || _a === void 0 ? void 0 : _a.customAction) && this.currentError && this.currentSuggestion) { this.config.fixItButton.customAction(this.currentError, this.currentSuggestion); } else { // Default fix it action - copy code to clipboard and show instructions if ((_b = this.currentSuggestion) === null || _b === void 0 ? void 0 : _b.code) { navigator.clipboard.writeText(this.currentSuggestion.code); alert('Code copied to clipboard! Apply the fix to your code.'); } else { alert('Please follow the suggested actions to fix this error.'); } } this.hideModal(); } setupGlobalErrorBoundary() { // This will be handled by the ErrorDetector class // which already sets up global error handlers } destroy() { if (this.errorDetector) { this.errorDetector.destroy(); } if (this.floatingButton) { this.floatingButton.remove(); } if (this.modalContainer) { this.modalContainer.remove(); } AutoInitializer.instance = null; } } AutoInitializer.instance = null; // Auto-initialize when script is loaded if (typeof window !== 'undefined') { // Use setTimeout to ensure DOM is ready setTimeout(() => { AutoInitializer.getInstance(); }, 0); }