UNPKG

ai-debug-local-mcp

Version:

🎯 ENHANCED AI GUIDANCE v4.1.2: Dramatically improved tool descriptions help AI users choose the right tools instead of 'close enough' options. Ultra-fast keyboard automation (10x speed), universal recording, multi-ecosystem debugging support, and compreh

314 lines • 13.9 kB
export class LiveViewDebugEngine { page; constructor(page) { this.page = page; } async debugLiveViewConnection() { const result = { websocket: { connected: false, connectionAttempts: 0, errors: [] }, liveSocket: { present: false, enabled: false, debug: false, connected: false }, cors: { headers: {}, issues: [] }, security: { issues: [] }, phoenix: { issues: [] }, javascriptErrors: { blocking: false, errors: [] }, recommendations: [] }; // Inject debugging code await this.page.addInitScript(() => { // Track WebSocket connections window.__wsDebug = { connections: [], attempts: 0 }; const OriginalWebSocket = window.WebSocket; window.WebSocket = function (url, protocols) { window.__wsDebug.attempts++; const ws = new OriginalWebSocket(url, protocols); const connection = { url, readyState: ws.readyState, opened: false, closed: false, error: null, closeCode: null, closeReason: null, messages: [] }; window.__wsDebug.connections.push(connection); ws.addEventListener('open', () => { connection.opened = true; connection.readyState = ws.readyState; }); ws.addEventListener('error', (event) => { connection.error = event; connection.readyState = ws.readyState; }); ws.addEventListener('close', (event) => { connection.closed = true; connection.closeCode = event.code; connection.closeReason = event.reason; connection.readyState = ws.readyState; }); ws.addEventListener('message', (event) => { connection.messages.push({ data: event.data, timestamp: Date.now() }); }); return ws; }; // Track JavaScript errors window.__jsErrors = []; window.addEventListener('error', (event) => { window.__jsErrors.push({ message: event.message, filename: event.filename, lineno: event.lineno, colno: event.colno, stack: event.error?.stack, timestamp: Date.now() }); }); window.addEventListener('unhandledrejection', (event) => { window.__jsErrors.push({ message: `Unhandled Promise rejection: ${event.reason}`, stack: event.reason?.stack, timestamp: Date.now() }); }); }); // Navigate to the page if needed if (!this.page.url() || this.page.url() === 'about:blank') { throw new Error('Page not loaded. Use start_debug_session first.'); } // Wait a bit for connections to establish await this.page.waitForTimeout(2000); // Gather debug information const debugInfo = await this.page.evaluate(() => { const wsDebug = window.__wsDebug || { connections: [], attempts: 0 }; const jsErrors = window.__jsErrors || []; // Check LiveSocket const liveSocket = window.liveSocket; let liveSocketInfo = { present: false, enabled: false, debug: false, connected: false, connectionState: undefined, transport: undefined, latency: undefined }; if (liveSocket) { liveSocketInfo = { present: true, enabled: liveSocket.enableDebug !== undefined, debug: liveSocket.enableDebug === true, connected: liveSocket.isConnected?.() || false, connectionState: liveSocket.connectionState?.() || undefined, transport: liveSocket.transport?.name || undefined, latency: liveSocket.getLatencySim?.() || undefined }; } // Check Phoenix info const phoenixInfo = { version: window.Phoenix?.VERSION || undefined, endpoint: document.querySelector('meta[name="phoenix-socket"]')?.getAttribute('content') || undefined, csrfToken: document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') || undefined }; // Get page headers (if available through performance API) const headers = {}; const navEntry = performance.getEntriesByType('navigation')[0]; if (navEntry && navEntry.serverTiming) { navEntry.serverTiming.forEach((timing) => { headers[timing.name] = timing.description || timing.duration?.toString() || ''; }); } return { wsDebug, jsErrors, liveSocketInfo, phoenixInfo, headers, documentReadyState: document.readyState }; }); // Process WebSocket information if (debugInfo.wsDebug.connections.length > 0) { const lastConnection = debugInfo.wsDebug.connections[debugInfo.wsDebug.connections.length - 1]; result.websocket = { connected: lastConnection.opened && !lastConnection.closed, connectionAttempts: debugInfo.wsDebug.attempts, errors: lastConnection.error ? [`WebSocket error occurred`] : [], endpoint: lastConnection.url, readyState: this.getReadyStateString(lastConnection.readyState), closeCode: lastConnection.closeCode, closeReason: lastConnection.closeReason }; // Analyze close codes if (lastConnection.closeCode) { result.websocket.errors.push(this.analyzeCloseCode(lastConnection.closeCode)); } } // Process LiveSocket information result.liveSocket = debugInfo.liveSocketInfo; // Process Phoenix information result.phoenix = { version: debugInfo.phoenixInfo.version, endpoint: debugInfo.phoenixInfo.endpoint, csrfToken: debugInfo.phoenixInfo.csrfToken, issues: [] }; // Check for common Phoenix issues if (!result.phoenix.csrfToken) { result.phoenix.issues.push('Missing CSRF token meta tag'); result.recommendations.push('Add <meta name="csrf-token" content="<%= csrf_token_value() %>"> to your layout'); } // Process JavaScript errors result.javascriptErrors.errors = debugInfo.jsErrors; result.javascriptErrors.blocking = debugInfo.jsErrors.some((error) => error.message.toLowerCase().includes('livesocket') || error.message.toLowerCase().includes('phoenix') || error.message.toLowerCase().includes('websocket')); if (result.javascriptErrors.blocking) { result.recommendations.push('Fix JavaScript errors that may be preventing LiveSocket initialization'); } // Check response headers for CORS and security issues try { const response = await this.page.evaluate(async () => { const response = await fetch(window.location.href); const headers = {}; response.headers.forEach((value, key) => { headers[key] = value; }); return headers; }); result.cors.headers = response; // Check CORS headers if (!response['access-control-allow-origin']) { result.cors.issues.push('No Access-Control-Allow-Origin header found'); } // Check security headers result.security.csp = response['content-security-policy']; result.security.xFrameOptions = response['x-frame-options']; if (result.security.csp && result.security.csp.includes('upgrade-insecure-requests')) { result.security.issues.push('CSP may block WebSocket connections in development'); result.recommendations.push('Check Content Security Policy settings for WebSocket restrictions'); } } catch (error) { console.error('Failed to fetch headers:', error); } // Generate recommendations based on findings if (!result.websocket.connected) { if (result.websocket.connectionAttempts === 0) { result.recommendations.push('No WebSocket connection attempts detected. Check if LiveSocket is properly initialized.'); result.recommendations.push('Ensure app.js is loaded and liveSocket.connect() is called.'); } else { result.recommendations.push('WebSocket failed to connect after ' + result.websocket.connectionAttempts + ' attempts.'); if (result.websocket.closeCode === 1006) { result.recommendations.push('Abnormal closure (1006) - Check if Phoenix endpoint is running and accessible.'); result.recommendations.push('Verify WebSocket endpoint URL matches Phoenix configuration.'); } } } if (!result.liveSocket.present) { result.recommendations.push('LiveSocket not found. Check if Phoenix LiveView JavaScript is properly included.'); } else if (!result.liveSocket.connected) { result.recommendations.push('LiveSocket present but not connected. Enable debug mode with liveSocket.enableDebug() for more info.'); } // Check for common misconfigurations if (result.phoenix.endpoint && !result.phoenix.endpoint.startsWith('/')) { result.phoenix.issues.push('Phoenix socket endpoint should start with /'); } return result; } getReadyStateString(state) { switch (state) { case 0: return 'CONNECTING'; case 1: return 'OPEN'; case 2: return 'CLOSING'; case 3: return 'CLOSED'; default: return 'UNKNOWN'; } } analyzeCloseCode(code) { const closeCodes = { 1000: 'Normal closure', 1001: 'Going away (server shutdown or browser navigation)', 1002: 'Protocol error', 1003: 'Unsupported data type', 1006: 'Abnormal closure (no close frame received)', 1007: 'Invalid frame payload data', 1008: 'Policy violation', 1009: 'Message too big', 1010: 'Mandatory extension missing', 1011: 'Internal server error', 1015: 'TLS handshake failure' }; return closeCodes[code] || `Unknown close code: ${code}`; } async checkWebSocketEndpoint(expectedUrl) { try { const info = await this.page.evaluate((expectedUrl) => { // Try to find the WebSocket URL from various sources const metaTag = document.querySelector('meta[name="phoenix-socket"]')?.getAttribute('content'); const liveSocket = window.liveSocket; const actualUrl = liveSocket?.endPointURL?.() || metaTag || expectedUrl; if (!actualUrl) { return { accessible: false, error: 'No WebSocket endpoint found' }; } // Convert relative URL to absolute const wsUrl = new URL(actualUrl, window.location.origin); wsUrl.protocol = wsUrl.protocol.replace('http', 'ws'); return { actualUrl: wsUrl.toString() }; }, expectedUrl); if (info.error) { return { accessible: false, error: info.error }; } // Try to establish a test connection const testResult = await this.page.evaluate((wsUrl) => { return new Promise((resolve) => { const ws = new WebSocket(wsUrl); const timeout = setTimeout(() => { ws.close(); resolve({ accessible: false, error: 'Connection timeout' }); }, 5000); ws.onopen = () => { clearTimeout(timeout); ws.close(); resolve({ accessible: true }); }; ws.onerror = () => { clearTimeout(timeout); resolve({ accessible: false, error: 'Connection failed' }); }; }); }, info.actualUrl); return { ...testResult, actualUrl: info.actualUrl }; } catch (error) { return { accessible: false, error: error.message }; } } } //# sourceMappingURL=liveview-debug-engine.js.map