/** * Kleap Next.js Error Detector * * Detects and reports Next.js build/runtime errors to parent window via postMessage * Works with Next.js 15 error overlay system * * Version: 1.0.0 */ (function () { console.debug('[Kleap] Next.js error detector loaded v1.0.0'); // Only run inside iframe const isInsideIframe = window.parent !== window; if (!isInsideIframe) { console.debug('[Kleap] Not inside iframe, skipping error detection'); return; } const PARENT_TARGET_ORIGIN = '*'; // Track reported errors to avoid duplicates const reportedErrors = new Set(); const ERROR_COOLDOWN_MS = 5000; /** * Send error report to parent window */ function reportError(errorData) { const errorKey = `${errorData.type}-${errorData.message}`; // Check if already reported recently if (reportedErrors.has(errorKey)) { console.debug('[Kleap] Error already reported, skipping:', errorKey); return; } // Mark as reported reportedErrors.add(errorKey); setTimeout(() => reportedErrors.delete(errorKey), ERROR_COOLDOWN_MS); console.log('[Kleap] Reporting error to parent:', errorData); window.parent.postMessage( { type: 'nextjs-build-error', payload: errorData, }, PARENT_TARGET_ORIGIN ); } /** * Extract error details from Next.js error overlay */ function extractNextJsError(container) { try { // Next.js 15 error overlay structure // Look for common error patterns // Method 1: Check for Next.js error container const errorTitle = container.querySelector('[data-nextjs-dialog-header]'); const errorBody = container.querySelector('[data-nextjs-dialog-body]'); if (errorTitle || errorBody) { const title = errorTitle?.textContent?.trim() || ''; const body = errorBody?.textContent?.trim() || ''; return { type: 'build-error', message: title || 'Build error detected', details: body, fullText: `${title}\n\n${body}`.trim(), }; } // Method 2: Look for error message in generic containers const h4 = container.querySelector('h4'); const pre = container.querySelector('pre'); if (h4 || pre) { return { type: 'build-error', message: h4?.textContent?.trim() || 'Build error', details: pre?.textContent?.trim() || '', fullText: container.textContent?.trim() || 'Unknown error', }; } // Method 3: Generic text extraction const text = container.textContent?.trim(); if (text && text.length > 10) { // Check for common Next.js error patterns if (text.includes('Module not found') || text.includes("Can't resolve") || text.includes('Build Error') || text.includes('Compile Error') || text.includes('Runtime Error')) { // Extract first meaningful line as message const lines = text.split('\n').map(l => l.trim()).filter(Boolean); const message = lines[0] || 'Build error detected'; return { type: 'build-error', message: message.substring(0, 200), // Limit length details: text.substring(0, 1000), // Limit details fullText: text.substring(0, 2000), // Full context }; } } return null; } catch (error) { console.error('[Kleap] Error extracting Next.js error:', error); return null; } } /** * Watch for Next.js error overlay in DOM */ function watchForNextJsErrors() { console.debug('[Kleap] Setting up Next.js error detection'); // Configuration for MutationObserver const config = { childList: true, subtree: true, // Watch all descendants (Next.js uses portals) attributes: false, }; // Callback when DOM changes const observerCallback = function (mutationsList) { for (const mutation of mutationsList) { if (mutation.type === 'childList' && mutation.addedNodes.length > 0) { for (const node of mutation.addedNodes) { if (node.nodeType !== Node.ELEMENT_NODE) continue; // Check for Next.js error overlay elements // Next.js 15 uses various IDs and attributes const isErrorOverlay = node.id?.includes('nextjs') || node.id?.includes('error') || node.getAttribute?.('data-nextjs-dialog') !== null || node.getAttribute?.('data-nextjs-toast') !== null || node.className?.includes?.('nextjs') || node.className?.includes?.('error-overlay'); if (isErrorOverlay) { console.log('[Kleap] Detected Next.js error overlay:', node); // Wait a bit for content to populate setTimeout(() => { const errorData = extractNextJsError(node); if (errorData) { reportError(errorData); } }, 100); } // Also check children recursively if (node.querySelector) { const errorChildren = node.querySelectorAll('[data-nextjs-dialog], [data-nextjs-toast], [id*="nextjs"], [id*="error"]'); errorChildren.forEach(child => { setTimeout(() => { const errorData = extractNextJsError(child); if (errorData) { reportError(errorData); } }, 100); }); } } } } }; // Start observing when DOM is ready if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => { console.debug('[Kleap] DOM loaded, starting error detection'); const observer = new MutationObserver(observerCallback); observer.observe(document.body || document.documentElement, config); // Check for existing errors checkExistingErrors(); }); } else { console.debug('[Kleap] DOM already loaded, starting error detection'); const observer = new MutationObserver(observerCallback); observer.observe(document.body || document.documentElement, config); // Check for existing errors checkExistingErrors(); } } /** * Check for errors that already exist in the DOM */ function checkExistingErrors() { console.debug('[Kleap] Checking for existing errors'); try { // Look for Next.js error elements const selectors = [ '[data-nextjs-dialog]', '[data-nextjs-toast]', '[id*="nextjs"][id*="error"]', 'div[class*="error-overlay"]', ]; selectors.forEach(selector => { const elements = document.querySelectorAll(selector); elements.forEach(element => { console.log('[Kleap] Found existing error element:', element); const errorData = extractNextJsError(element); if (errorData) { reportError(errorData); } }); }); } catch (error) { console.error('[Kleap] Error checking existing errors:', error); } } /** * Also intercept console.error for build errors * Next.js sometimes logs errors to console before showing overlay */ const originalConsoleError = console.error; console.error = function(...args) { // Call original originalConsoleError.apply(console, args); // Check if it looks like a Next.js error const message = args.map(arg => typeof arg === 'string' ? arg : String(arg) ).join(' '); if (message.includes('Module not found') || message.includes("Can't resolve 'tailwindcss'") || message.includes('Build Error') || message.includes('Compile Error')) { console.log('[Kleap] Detected build error in console.error'); reportError({ type: 'build-error', message: message.substring(0, 200), details: message.substring(0, 1000), fullText: message.substring(0, 2000), source: 'console.error', }); } }; // Start watching watchForNextJsErrors(); console.debug('[Kleap] Next.js error detector initialized'); })();