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
JavaScript
/**
* 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