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