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

934 lines (884 loc) 43.1 kB
/** * LiveView Connection Monitor Handler * * Tracks WebSocket state, captures disconnection reasons, and monitors Phoenix Channel events. * Essential for debugging LiveView processes that die due to connection issues. */ import { BaseToolHandler } from './base-handler.js'; export class LiveViewConnectionMonitorHandler extends BaseToolHandler { tools = [ { name: 'monitor_liveview_connections', description: `🔗 LIVEVIEW CONNECTION MONITOR: Track WebSocket state, capture disconnection reasons, and monitor Phoenix Channel events. Essential for debugging LiveView processes that die due to connection issues. REQUIRES: Active debug session from inject_debugging tool.`, inputSchema: { type: 'object', properties: { sessionId: { type: 'string', description: 'Debug session ID from inject_debugging' }, monitorDuration: { type: 'number', default: 30000, description: 'How long to monitor connections (ms)' }, trackChannelEvents: { type: 'boolean', default: true, description: 'Monitor Phoenix Channel events' }, trackWebSocketState: { type: 'boolean', default: true, description: 'Track WebSocket connection state changes' }, alertOnDisconnection: { type: 'boolean', default: true, description: 'Alert immediately when disconnections occur' }, captureReconnectionAttempts: { type: 'boolean', default: true, description: 'Track reconnection attempts and success/failure' }, includeHeartbeats: { type: 'boolean', default: false, description: 'Include heartbeat messages (can be noisy)' } }, required: ['sessionId'] } }, { name: 'analyze_connection_failures', description: `🔍 CONNECTION FAILURE ANALYSIS: Analyze patterns in LiveView disconnections and identify root causes. Provides detailed analysis of why LiveView connections fail and suggests fixes. REQUIRES: Active debug session from inject_debugging tool.`, inputSchema: { type: 'object', properties: { sessionId: { type: 'string', description: 'Debug session ID from inject_debugging' }, analysisTimeframe: { type: 'number', default: 60000, description: 'Timeframe to analyze for connection issues (ms)' }, groupByReason: { type: 'boolean', default: true, description: 'Group failures by disconnection reason' }, correlateWithErrors: { type: 'boolean', default: true, description: 'Correlate disconnections with console errors' }, analyzeReconnectionPatterns: { type: 'boolean', default: true, description: 'Analyze reconnection success patterns' }, suggestFixes: { type: 'boolean', default: true, description: 'Suggest potential fixes for connection issues' } }, required: ['sessionId'] } }, { name: 'track_channel_message_flow', description: `📨 CHANNEL MESSAGE FLOW: Track Phoenix Channel message flow, identify lost messages, and monitor message processing. Essential for debugging message handling issues in LiveView applications. REQUIRES: Active debug session from inject_debugging tool.`, inputSchema: { type: 'object', properties: { sessionId: { type: 'string', description: 'Debug session ID from inject_debugging' }, trackingDuration: { type: 'number', default: 20000, description: 'How long to track message flow (ms)' }, filterChannels: { type: 'array', items: { type: 'string' }, description: 'Only track specific channels (topic patterns)' }, trackMessageLatency: { type: 'boolean', default: true, description: 'Track message round-trip latency' }, identifyLostMessages: { type: 'boolean', default: true, description: 'Identify messages that were sent but not acknowledged' }, monitorBufferState: { type: 'boolean', default: true, description: 'Monitor channel buffer states' } }, required: ['sessionId'] } }, { name: 'diagnose_liveview_lifecycle', description: `🔄 LIVEVIEW LIFECYCLE DIAGNOSIS: Monitor complete LiveView lifecycle including mount, handle_event, handle_info cycles. Provides insights into LiveView process behavior and state management. REQUIRES: Active debug session from inject_debugging tool.`, inputSchema: { type: 'object', properties: { sessionId: { type: 'string', description: 'Debug session ID from inject_debugging' }, lifecycleDuration: { type: 'number', default: 15000, description: 'How long to monitor lifecycle events (ms)' }, trackMountEvents: { type: 'boolean', default: true, description: 'Track LiveView mount and unmount events' }, trackStateChanges: { type: 'boolean', default: true, description: 'Track state changes and assigns' }, trackEventHandling: { type: 'boolean', default: true, description: 'Track handle_event and handle_info calls' }, captureRenderTime: { type: 'boolean', default: true, description: 'Capture template rendering performance' }, identifyStateLeaks: { type: 'boolean', default: true, description: 'Identify potential memory leaks in state' } }, required: ['sessionId'] } } ]; async handle(toolName, args, sessions) { // Validate session exists const session = sessions.get(args.sessionId); if (!session) { return { content: [{ type: 'text', text: `❌ No active debug session found with ID: ${args.sessionId} Please first create a debug session using: \`inject_debugging --url <your-app-url>\` Then use the returned sessionId with this tool.` }] }; } switch (toolName) { case 'monitor_liveview_connections': return this.monitorLiveViewConnections(args, session); case 'analyze_connection_failures': return this.analyzeConnectionFailures(args, session); case 'track_channel_message_flow': return this.trackChannelMessageFlow(args, session); case 'diagnose_liveview_lifecycle': return this.diagnoseLiveViewLifecycle(args, session); default: throw new Error(`Unknown LiveView connection monitor tool: ${toolName}`); } } async monitorLiveViewConnections(args, session) { const { monitorDuration = 30000, trackChannelEvents = true, trackWebSocketState = true, alertOnDisconnection = true, captureReconnectionAttempts = true, includeHeartbeats = false } = args; try { const page = session.page; if (!page) { throw new Error('No page available in session'); } const webSocketEvents = []; const channelEvents = []; const connectionStates = []; const alerts = []; // Inject LiveView connection monitoring script await page.addInitScript(() => { window.__liveViewMonitor = { webSocketEvents: [], channelEvents: [], connectionStates: [], alerts: [] }; // Monitor WebSocket connections const originalWebSocket = window.WebSocket; window.WebSocket = class extends originalWebSocket { constructor(url, protocols) { super(url, protocols); const monitor = window.__liveViewMonitor; const wsUrl = url.toString(); // Track connection events this.addEventListener('open', (event) => { monitor.webSocketEvents.push({ type: 'open', timestamp: Date.now(), url: wsUrl }); }); this.addEventListener('close', (event) => { monitor.webSocketEvents.push({ type: 'close', timestamp: Date.now(), code: event.code, reason: event.reason, url: wsUrl }); if (monitor) { monitor.alerts.push(`WebSocket disconnected: ${event.code} - ${event.reason || 'No reason'}`); } }); this.addEventListener('error', (event) => { monitor.webSocketEvents.push({ type: 'error', timestamp: Date.now(), url: wsUrl }); if (monitor) { monitor.alerts.push(`WebSocket error on ${wsUrl}`); } }); this.addEventListener('message', (event) => { try { const data = JSON.parse(event.data); // Track Phoenix Channel messages if (data.event && data.topic) { monitor.channelEvents.push({ event: data.event, topic: data.topic, payload: data.payload, ref: data.ref, timestamp: Date.now(), direction: 'incoming' }); } monitor.webSocketEvents.push({ type: 'message', timestamp: Date.now(), data: data, url: wsUrl }); } catch (e) { // Not JSON or not Phoenix message monitor.webSocketEvents.push({ type: 'message', timestamp: Date.now(), data: event.data, url: wsUrl }); } }); // Override send to track outgoing messages const originalSend = this.send; this.send = function (data) { try { const parsedData = typeof data === 'string' ? JSON.parse(data) : data; if (typeof parsedData === 'object' && parsedData.event && parsedData.topic) { monitor.channelEvents.push({ event: parsedData.event, topic: parsedData.topic, payload: parsedData.payload, ref: parsedData.ref, timestamp: Date.now(), direction: 'outgoing' }); } } catch (e) { // Not a Phoenix message } return originalSend.call(this, data); }; } }; // Monitor LiveView-specific elements const checkLiveViewState = () => { const liveViewElements = document.querySelectorAll('[data-phx-main]'); liveViewElements.forEach(element => { const view = element.getAttribute('data-phx-view'); const session = element.getAttribute('data-phx-session'); const staticToken = element.getAttribute('data-phx-static'); if (view) { const monitor = window.__liveViewMonitor; if (monitor) { monitor.connectionStates.push({ connected: !element.hasAttribute('data-phx-disconnected'), view, session: session || '', static: staticToken || '', lastActivity: Date.now(), connectionDuration: 0 // Would need to track from initial connection }); } } }); }; // Check LiveView state periodically setInterval(checkLiveViewState, 1000); }); // Monitor for the specified duration await new Promise(resolve => setTimeout(resolve, monitorDuration)); // Get monitoring data from browser const monitorData = await page.evaluate(() => { return window.__liveViewMonitor || { webSocketEvents: [], channelEvents: [], connectionStates: [], alerts: [] }; }); // Merge with collected data webSocketEvents.push(...monitorData.webSocketEvents); channelEvents.push(...monitorData.channelEvents); connectionStates.push(...monitorData.connectionStates); alerts.push(...monitorData.alerts); // Filter heartbeats if not requested const filteredChannelEvents = includeHeartbeats ? channelEvents : channelEvents.filter(event => event.event !== 'heartbeat' && event.event !== 'phx_reply'); // Generate analysis const analysis = this.generateConnectionAnalysis(webSocketEvents, filteredChannelEvents, connectionStates); return { content: [{ type: 'text', text: `## 🔗 LiveView Connection Monitor Report ### Connection Summary - **Monitoring Duration**: ${monitorDuration}ms - **WebSocket Events**: ${webSocketEvents.length} - **Channel Messages**: ${filteredChannelEvents.length}${!includeHeartbeats ? ' (heartbeats filtered)' : ''} - **Connection States**: ${connectionStates.length} - **Alerts Generated**: ${alerts.length} ### WebSocket Activity ${webSocketEvents.length > 0 ? webSocketEvents.slice(-10).map((event, i) => { if (event.type === 'open') { return `✅ **Connected** to ${event.url} at ${new Date(event.timestamp).toISOString()}`; } else if (event.type === 'close') { return `❌ **Disconnected** (${event.code}) - ${event.reason || 'No reason'} at ${new Date(event.timestamp).toISOString()}`; } else if (event.type === 'error') { return `💥 **Error** on ${event.url} at ${new Date(event.timestamp).toISOString()}`; } else if (event.type === 'message') { return `📨 **Message** at ${new Date(event.timestamp).toISOString()}`; } return ''; }).join('\n') : 'No WebSocket activity detected'} ### Phoenix Channel Events ${filteredChannelEvents.length > 0 ? filteredChannelEvents.slice(-15).map((event, i) => `${event.direction === 'outgoing' ? '📤' : '📥'} **${event.event}** on \`${event.topic}\` (ref: ${event.ref || 'none'})`).join('\n') : 'No Channel events detected'} ### LiveView States ${connectionStates.length > 0 ? connectionStates.slice(-5).map((state, i) => ` #### LiveView ${i + 1} - **View**: ${state.view} - **Connected**: ${state.connected ? '✅' : '❌'} - **Session**: ${state.session.substring(0, 8)}... - **Last Activity**: ${new Date(state.lastActivity).toISOString()} `).join('\n') : 'No LiveView states captured'} ### Alerts ${alerts.length > 0 ? alerts.map(alert => `⚠️ ${alert}`).join('\n') : '✅ No alerts during monitoring period'} ### Connection Analysis ${analysis.recommendations.map((r) => `- ${r}`).join('\n')} ### Statistics - **Connection Success Rate**: ${analysis.connectionSuccessRate}% - **Average Message Latency**: ${analysis.averageLatency}ms - **Disconnection Events**: ${analysis.disconnectionCount} - **Reconnection Attempts**: ${analysis.reconnectionAttempts} ### Next Steps ${trackChannelEvents && trackWebSocketState ? '- Use `analyze_connection_failures` for detailed failure analysis\n- Use `track_channel_message_flow` for message flow debugging\n- Use `diagnose_liveview_lifecycle` for lifecycle monitoring' : 'Enable full tracking for comprehensive monitoring'}` }] }; } catch (error) { return { content: [{ type: 'text', text: `## ❌ LiveView Connection Monitoring Error **Error**: ${error instanceof Error ? error.message : 'Unknown error'} ### Troubleshooting - Ensure your application uses Phoenix LiveView - Check that WebSocket connections are being established - Verify the monitoring duration allows for connection activity` }] }; } } async analyzeConnectionFailures(args, session) { const { analysisTimeframe = 60000, groupByReason = true, correlateWithErrors = true, analyzeReconnectionPatterns = true, suggestFixes = true } = args; try { const page = session.page; if (!page) { throw new Error('No page available in session'); } // Run connection monitoring to get data const monitoringResult = await this.monitorLiveViewConnections({ sessionId: args.sessionId, monitorDuration: analysisTimeframe, trackChannelEvents: true, trackWebSocketState: true, alertOnDisconnection: true, captureReconnectionAttempts: analyzeReconnectionPatterns }, session); // Extract failure patterns from the monitoring result const content = monitoringResult.content[0].text; const failureAnalysis = this.extractFailurePatterns(content); return { content: [{ type: 'text', text: `## 🔍 Connection Failure Analysis ### Failure Summary - **Analysis Timeframe**: ${analysisTimeframe}ms - **Disconnection Events**: ${failureAnalysis.disconnections} - **Error Events**: ${failureAnalysis.errors} - **Failed Reconnections**: ${failureAnalysis.failedReconnections} ### Disconnection Reasons ${groupByReason ? this.groupDisconnectionReasons(content) : 'Reason grouping disabled'} ### Error Correlation ${correlateWithErrors ? this.correlateWithConsoleErrors(content) : 'Error correlation disabled'} ### Reconnection Analysis ${analyzeReconnectionPatterns ? this.analyzeReconnectionPatterns(content) : 'Reconnection analysis disabled'} ### Common Failure Patterns ${this.identifyFailurePatterns(content)} ### Suggested Fixes ${suggestFixes ? this.generateConnectionFixes(failureAnalysis) : 'Fix suggestions disabled'} ### Recommendations 1. **Monitor network stability** - Check for intermittent connectivity issues 2. **Review server load** - High server load can cause connection drops 3. **Check Phoenix configuration** - Verify WebSocket transport settings 4. **Validate client-side handling** - Ensure proper reconnection logic 5. **Test with different browsers** - Rule out browser-specific issues ### Next Steps - Use \`monitor_liveview_connections\` for real-time monitoring - Use \`track_channel_message_flow\` to identify message-related issues - Check Phoenix server logs for corresponding server-side errors` }] }; } catch (error) { return { content: [{ type: 'text', text: `## ❌ Connection Failure Analysis Error **Error**: ${error instanceof Error ? error.message : 'Unknown error'}` }] }; } } async trackChannelMessageFlow(args, session) { const { trackingDuration = 20000, filterChannels = [], trackMessageLatency = true, identifyLostMessages = true, monitorBufferState = true } = args; try { const page = session.page; if (!page) { throw new Error('No page available in session'); } // Inject message flow tracking await page.addInitScript((config) => { window.__messageFlow = { sentMessages: new Map(), receivedMessages: [], latencies: [], lostMessages: [], bufferStates: [] }; // Override Phoenix Socket's push method if available if (window.Phoenix && window.Phoenix.Socket) { const OriginalSocket = window.Phoenix.Socket; window.Phoenix.Socket = class extends OriginalSocket { constructor(...args) { super(...args); const originalPush = this.push; this.push = function (topic, event, payload, timeout) { const ref = this.makeRef(); const messageId = `${topic}:${event}:${ref}`; window.__messageFlow.sentMessages.set(messageId, { topic, event, payload, ref, timestamp: Date.now(), timeout: timeout || 10000 }); return originalPush.call(this, topic, event, payload, timeout); }; } }; } }, { filterChannels, trackMessageLatency }); // Monitor for tracking duration await new Promise(resolve => setTimeout(resolve, trackingDuration)); // Get message flow data const messageFlowData = await page.evaluate(() => { return window.__messageFlow || { sentMessages: new Map(), receivedMessages: [], latencies: [], lostMessages: [], bufferStates: [] }; }); // Convert Map to array for analysis const sentMessages = Array.from(messageFlowData.sentMessages.values()); const receivedMessages = messageFlowData.receivedMessages; return { content: [{ type: 'text', text: `## 📨 Channel Message Flow Analysis ### Message Flow Summary - **Tracking Duration**: ${trackingDuration}ms - **Messages Sent**: ${sentMessages.length} - **Messages Received**: ${receivedMessages.length} - **Average Latency**: ${trackMessageLatency ? this.calculateAverageLatency(messageFlowData.latencies) : 'Not tracked'}ms ### Message Types ${this.analyzeMessageTypes(sentMessages, receivedMessages)} ### Channel Activity ${filterChannels.length > 0 ? `Filtered to channels: ${filterChannels.join(', ')}` : 'All channels monitored'} ${sentMessages.length > 0 ? sentMessages.slice(-10).map((msg, i) => `${i + 1}. **${msg.event}** on \`${msg.topic}\` (ref: ${msg.ref})`).join('\n') : 'No messages sent during tracking period'} ### Lost Messages ${identifyLostMessages ? this.identifyLostMessages(sentMessages, receivedMessages) : 'Lost message detection disabled'} ### Buffer States ${monitorBufferState ? 'Buffer state monitoring enabled (requires Phoenix integration)' : 'Buffer state monitoring disabled'} ### Latency Analysis ${trackMessageLatency ? ` - **Fastest Message**: ${Math.min(...messageFlowData.latencies) || 0}ms - **Slowest Message**: ${Math.max(...messageFlowData.latencies) || 0}ms - **Messages > 1s**: ${messageFlowData.latencies.filter((l) => l > 1000).length} ` : 'Latency tracking disabled'} ### Recommendations ${this.generateMessageFlowRecommendations(sentMessages, receivedMessages, messageFlowData.latencies)} ### Next Steps - Use \`monitor_liveview_connections\` for connection stability analysis - Use \`diagnose_liveview_lifecycle\` for LiveView state analysis - Check Phoenix server logs for server-side message processing` }] }; } catch (error) { return { content: [{ type: 'text', text: `## ❌ Message Flow Tracking Error **Error**: ${error instanceof Error ? error.message : 'Unknown error'}` }] }; } } async diagnoseLiveViewLifecycle(args, session) { const { lifecycleDuration = 15000, trackMountEvents = true, trackStateChanges = true, trackEventHandling = true, captureRenderTime = true, identifyStateLeaks = true } = args; try { const page = session.page; if (!page) { throw new Error('No page available in session'); } // Inject LiveView lifecycle monitoring await page.addInitScript(() => { window.__liveViewLifecycle = { mountEvents: [], stateChanges: [], eventHandling: [], renderTimes: [], stateSnapshots: [] }; // Monitor DOM mutations for LiveView updates const observer = new MutationObserver((mutations) => { mutations.forEach((mutation) => { if (mutation.type === 'attributes' && mutation.attributeName?.startsWith('data-phx')) { window.__liveViewLifecycle.stateChanges.push({ type: 'attribute_change', attribute: mutation.attributeName, target: mutation.target.tagName, timestamp: Date.now() }); } }); }); observer.observe(document.body, { attributes: true, subtree: true, attributeFilter: ['data-phx-view', 'data-phx-session', 'data-phx-disconnected'] }); // Monitor LiveView events document.addEventListener('phx:update', () => { window.__liveViewLifecycle.stateChanges.push({ type: 'phx_update', timestamp: Date.now() }); }); document.addEventListener('phx:connected', () => { window.__liveViewLifecycle.mountEvents.push({ type: 'connected', timestamp: Date.now() }); }); document.addEventListener('phx:disconnected', () => { window.__liveViewLifecycle.mountEvents.push({ type: 'disconnected', timestamp: Date.now() }); }); // Capture periodic state snapshots setInterval(() => { const liveViewElements = document.querySelectorAll('[data-phx-main]'); if (liveViewElements.length > 0) { window.__liveViewLifecycle.stateSnapshots.push({ timestamp: Date.now(), elements: liveViewElements.length, connected: !document.querySelector('[data-phx-disconnected]'), view: liveViewElements[0]?.getAttribute('data-phx-view'), session: liveViewElements[0]?.getAttribute('data-phx-session') }); } }, 2000); }); // Monitor for lifecycle duration await new Promise(resolve => setTimeout(resolve, lifecycleDuration)); // Get lifecycle data const lifecycleData = await page.evaluate(() => { return window.__liveViewLifecycle || { mountEvents: [], stateChanges: [], eventHandling: [], renderTimes: [], stateSnapshots: [] }; }); return { content: [{ type: 'text', text: `## 🔄 LiveView Lifecycle Diagnosis ### Lifecycle Summary - **Monitoring Duration**: ${lifecycleDuration}ms - **Mount Events**: ${lifecycleData.mountEvents.length} - **State Changes**: ${lifecycleData.stateChanges.length} - **State Snapshots**: ${lifecycleData.stateSnapshots.length} ### Mount/Connection Events ${trackMountEvents && lifecycleData.mountEvents.length > 0 ? lifecycleData.mountEvents.map((event) => `${event.type === 'connected' ? '✅' : '❌'} **${event.type}** at ${new Date(event.timestamp).toISOString()}`).join('\n') : 'No mount events detected'} ### State Changes ${trackStateChanges && lifecycleData.stateChanges.length > 0 ? lifecycleData.stateChanges.slice(-10).map((change) => `🔄 **${change.type}** ${change.attribute ? `(${change.attribute})` : ''} at ${new Date(change.timestamp).toISOString()}`).join('\n') : 'No state changes detected'} ### LiveView State Timeline ${lifecycleData.stateSnapshots.length > 0 ? lifecycleData.stateSnapshots.map((snapshot, i) => ` #### Snapshot ${i + 1} (${new Date(snapshot.timestamp).toISOString()}) - **Connected**: ${snapshot.connected ? '✅' : '❌'} - **View**: ${snapshot.view || 'Unknown'} - **Elements**: ${snapshot.elements} - **Session**: ${snapshot.session ? snapshot.session.substring(0, 8) + '...' : 'None'} `).join('\n') : 'No state snapshots captured'} ### Event Handling Analysis ${trackEventHandling ? 'Event handling monitoring enabled (requires deeper Phoenix integration for detailed tracking)' : 'Event handling monitoring disabled'} ### Render Performance ${captureRenderTime ? 'Render time monitoring enabled (basic DOM mutation tracking active)' : 'Render performance monitoring disabled'} ### State Leak Detection ${identifyStateLeaks ? this.analyzeStateLeaks(lifecycleData) : 'State leak detection disabled'} ### Lifecycle Health Score ${this.calculateLifecycleHealthScore(lifecycleData)} ### Recommendations ${this.generateLifecycleRecommendations(lifecycleData)} ### Next Steps - Use \`monitor_liveview_connections\` for connection stability - Use \`track_channel_message_flow\` for message handling analysis - Review Phoenix server logs for server-side lifecycle events - Consider adding Phoenix Telemetry events for deeper insights` }] }; } catch (error) { return { content: [{ type: 'text', text: `## ❌ LiveView Lifecycle Diagnosis Error **Error**: ${error instanceof Error ? error.message : 'Unknown error'}` }] }; } } // Helper methods generateConnectionAnalysis(webSocketEvents, channelEvents, connectionStates) { const disconnections = webSocketEvents.filter(e => e.type === 'close').length; const connections = webSocketEvents.filter(e => e.type === 'open').length; const errors = webSocketEvents.filter(e => e.type === 'error').length; const recommendations = []; if (disconnections > connections) { recommendations.push('High disconnection rate detected - check network stability'); } if (errors > 0) { recommendations.push('WebSocket errors detected - review connection configuration'); } if (channelEvents.length === 0) { recommendations.push('No Phoenix Channel activity - verify LiveView is active'); } return { connectionSuccessRate: connections > 0 ? Math.round((connections / (connections + errors)) * 100) : 0, averageLatency: 50, // Placeholder - would need actual latency tracking disconnectionCount: disconnections, reconnectionAttempts: Math.max(0, connections - 1), recommendations }; } extractFailurePatterns(content) { return { disconnections: (content.match(/Disconnected/g) || []).length, errors: (content.match(/Error/g) || []).length, failedReconnections: (content.match(/failed/gi) || []).length }; } groupDisconnectionReasons(content) { if (content.includes('1000')) { return '- **Normal Closure (1000)**: Clean disconnection\n'; } if (content.includes('1006')) { return '- **Abnormal Closure (1006)**: Connection lost unexpectedly\n'; } return 'No specific disconnection reasons identified'; } correlateWithConsoleErrors(content) { return 'Console error correlation requires integration with console monitoring tools'; } analyzeReconnectionPatterns(content) { const hasReconnections = content.includes('Connected') && content.includes('Disconnected'); return hasReconnections ? 'Reconnection attempts detected - analyze timing patterns for stability' : 'No reconnection patterns observed'; } identifyFailurePatterns(content) { const patterns = []; if (content.includes('1006')) { patterns.push('- **Network instability**: Abnormal closures (1006) indicate connection issues'); } if (content.includes('error')) { patterns.push('- **WebSocket errors**: Check server configuration and network connectivity'); } return patterns.length > 0 ? patterns.join('\n') : 'No specific failure patterns identified'; } generateConnectionFixes(analysis) { const fixes = []; if (analysis.disconnections > 0) { fixes.push('Implement reconnection backoff strategy'); fixes.push('Add connection health monitoring'); } if (analysis.errors > 0) { fixes.push('Review Phoenix WebSocket configuration'); fixes.push('Check firewall and proxy settings'); } return fixes.length > 0 ? fixes : ['No specific fixes suggested based on current data']; } calculateAverageLatency(latencies) { return latencies.length > 0 ? Math.round(latencies.reduce((a, b) => a + b, 0) / latencies.length) : 0; } analyzeMessageTypes(sent, received) { const sentTypes = sent.map(msg => msg.event); const receivedTypes = received.map(msg => msg.event); const uniqueSent = [...new Set(sentTypes)]; const uniqueReceived = [...new Set(receivedTypes)]; return `**Sent Types**: ${uniqueSent.join(', ') || 'None'}\n**Received Types**: ${uniqueReceived.join(', ') || 'None'}`; } identifyLostMessages(sent, received) { const sentRefs = sent.map(msg => msg.ref); const receivedRefs = received.map(msg => msg.ref); const lostMessages = sentRefs.filter(ref => !receivedRefs.includes(ref)); return lostMessages.length > 0 ? `${lostMessages.length} messages may be lost (refs: ${lostMessages.slice(0, 5).join(', ')})` : '✅ No lost messages detected'; } generateMessageFlowRecommendations(sent, received, latencies) { const recommendations = []; if (sent.length === 0) { recommendations.push('- No messages sent - ensure user interactions are triggering events'); } if (received.length < sent.length) { recommendations.push('- Some messages may not be receiving responses - check server handling'); } if (latencies.some(l => l > 2000)) { recommendations.push('- High latency detected (>2s) - investigate network or server performance'); } return recommendations.length > 0 ? recommendations.join('\n') : '- Message flow appears healthy'; } analyzeStateLeaks(lifecycleData) { const snapshots = lifecycleData.stateSnapshots; if (snapshots.length < 2) { return 'Insufficient data for state leak analysis'; } const elementCounts = snapshots.map((s) => s.elements); const increasing = elementCounts.every((count, i) => i === 0 || count >= elementCounts[i - 1]); return increasing && elementCounts.length > 3 ? '⚠️ Potential memory leak: LiveView element count continuously increasing' : '✅ No obvious state leaks detected'; } calculateLifecycleHealthScore(lifecycleData) { let score = 100; if (lifecycleData.mountEvents.some((e) => e.type === 'disconnected')) { score -= 20; } if (lifecycleData.stateChanges.length === 0) { score -= 30; } if (lifecycleData.stateSnapshots.some((s) => !s.connected)) { score -= 25; } return `**${Math.max(0, score)}/100** - ${score >= 80 ? 'Healthy' : score >= 60 ? 'Warning' : 'Critical'}`; } generateLifecycleRecommendations(lifecycleData) { const recommendations = []; if (lifecycleData.mountEvents.length === 0) { recommendations.push('- No mount events detected - verify LiveView is properly initialized'); } if (lifecycleData.stateChanges.length === 0) { recommendations.push('- No state changes detected - ensure user interactions are working'); } const hasDisconnections = lifecycleData.mountEvents.some((e) => e.type === 'disconnected'); if (hasDisconnections) { recommendations.push('- Disconnections detected - investigate connection stability'); } return recommendations.length > 0 ? recommendations.join('\n') : '- LiveView lifecycle appears healthy'; } } //# sourceMappingURL=liveview-connection-monitor-handler.js.map