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

216 lines • 7.31 kB
import { nanoid } from 'nanoid'; export class DebugSessionRecorder { sessionId; events = []; _startTime = 0; _isRecording = false; page; options; eventBuffer = []; maxBufferSize; sessionMetadata = null; constructor(page, options = {}) { this.sessionId = nanoid(); this.page = page; this.options = options; this.maxBufferSize = options.bufferSize || 1000; } get isRecording() { return this._isRecording; } get startTime() { return this._startTime; } async startRecording(metadata) { if (this._isRecording) { throw new Error('Recording already in progress'); } this._isRecording = true; this._startTime = Date.now(); this.events = []; this.eventBuffer = []; this.sessionMetadata = metadata; // Attach event listeners this.attachBrowserListeners(); this.attachNetworkListeners(); this.attachConsoleListeners(); this.attachErrorListeners(); // Initial state capture await this.captureInitialState(); } attachBrowserListeners() { // Navigation events (valid Playwright event) this.page.on('framenavigated', (frame) => { if (frame === this.page.mainFrame()) { this.recordEvent({ type: 'navigation', timestamp: Date.now() - this._startTime, data: { url: this.filterUrl(frame.url()), method: 'navigation' } }); } }); // Page close event (valid Playwright event) this.page.on('close', () => { this._isRecording = false; }); // For click and input events, we'd need to inject client-side listeners // This is a simplified implementation for testing purposes // In a real implementation, we'd use page.evaluate() to inject listeners } attachNetworkListeners() { this.page.on('request', (request) => { this.recordEvent({ type: 'network_request', timestamp: Date.now() - this._startTime, data: { url: this.filterUrl(request.url()), method: request.method(), headers: this.filterHeaders(request.headers()), requestBody: this.filterBody(request.postData()) } }); }); this.page.on('response', (response) => { this.recordEvent({ type: 'network_response', timestamp: Date.now() - this._startTime, data: { url: this.filterUrl(response.url()), status: response.status(), headers: this.filterHeaders(response.headers()) } }); }); } attachConsoleListeners() { this.page.on('console', (msg) => { this.recordEvent({ type: 'console_log', timestamp: Date.now() - this._startTime, data: { level: msg.type(), text: msg.text(), location: msg.location() } }); }); } attachErrorListeners() { this.page.on('pageerror', (error) => { this.recordEvent({ type: 'error', timestamp: Date.now() - this._startTime, data: { error: error.message, stack: error.stack } }); }); } async captureInitialState() { try { const viewport = await this.page.viewportSize(); this.recordEvent({ type: 'navigation', timestamp: 0, data: { url: this.page.url(), method: 'initial', viewport: viewport || { width: 1920, height: 1080 } } }); } catch (error) { // Silently ignore initial state capture errors } } async getElementInfo(event) { // Mock implementation for testing - in real implementation this would // extract element information from the event return { selector: event.target?.id ? `#${event.target.id}` : event.target?.tagName?.toLowerCase() || 'unknown', tag: event.target?.tagName || 'UNKNOWN', text: event.target?.textContent || '', attributes: event.target?.attributes || {}, inputType: event.target?.type || '' }; } recordEvent(event) { // Add to buffer with size limit this.eventBuffer.push(event); // Maintain buffer size limit if (this.eventBuffer.length > this.maxBufferSize) { this.eventBuffer = this.eventBuffer.slice(-this.maxBufferSize); } } getEvents() { return [...this.eventBuffer]; } filterValue(value, fieldType) { if (!this.options.privacy?.redactSensitiveData) { return value; } const sensitiveFields = this.options.privacy.sensitiveFields || [ 'password', 'passwd', 'pwd', 'secret', 'token', 'key', 'ssn', 'social' ]; if (sensitiveFields.some(field => fieldType.toLowerCase().includes(field))) { return '[REDACTED]'; } return value; } filterUrl(url) { if (!this.options.privacy?.excludedDomains) { return url; } try { const urlObj = new URL(url); const domain = urlObj.hostname; if (this.options.privacy.excludedDomains.includes(domain)) { return `[FILTERED_DOMAIN]${urlObj.pathname}${urlObj.search}`; } } catch (error) { // Return original URL if parsing fails } return url; } filterHeaders(headers) { const filtered = { ...headers }; // Remove sensitive headers const sensitiveHeaders = ['authorization', 'cookie', 'x-api-key', 'x-auth-token']; sensitiveHeaders.forEach(header => { if (filtered[header]) { filtered[header] = '[REDACTED]'; } }); return filtered; } filterBody(body) { if (!body) return null; // In a real implementation, this would parse and filter sensitive data // For now, just return the body return body; } async stopRecording() { if (!this._isRecording) { throw new Error('No recording in progress'); } this._isRecording = false; const endTime = Date.now(); const duration = endTime - this._startTime; const session = { id: this.sessionId, timestamp: new Date(this._startTime), duration, url: this.page.url(), events: [...this.eventBuffer], metadata: this.sessionMetadata || { testIntent: 'Unknown' } }; return session; } } //# sourceMappingURL=debug-session-recorder.js.map