dev3000 0.0.24 → 0.0.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,697 @@
1
+ import { spawn } from 'child_process';
2
+ import { WebSocket } from 'ws';
3
+ import { writeFileSync, existsSync, mkdirSync } from 'fs';
4
+ import { join } from 'path';
5
+ import { tmpdir } from 'os';
6
+ export class CDPMonitor {
7
+ browser = null;
8
+ connection = null;
9
+ debugPort = 9222;
10
+ eventHandlers = new Map();
11
+ profileDir;
12
+ logger;
13
+ debug = false;
14
+ isShuttingDown = false;
15
+ constructor(profileDir, logger, debug = false) {
16
+ this.profileDir = profileDir;
17
+ this.logger = logger;
18
+ this.debug = debug;
19
+ }
20
+ debugLog(message) {
21
+ if (this.debug) {
22
+ console.log(`[CDP DEBUG] ${message}`);
23
+ }
24
+ }
25
+ async start() {
26
+ // Launch Chrome with CDP enabled
27
+ this.debugLog('Starting Chrome launch process');
28
+ await this.launchChrome();
29
+ this.debugLog('Chrome launch completed');
30
+ // Connect to Chrome DevTools Protocol
31
+ this.debugLog('Starting CDP connection');
32
+ await this.connectToCDP();
33
+ this.debugLog('CDP connection completed');
34
+ // Enable all the CDP domains we need for comprehensive monitoring
35
+ this.debugLog('Starting CDP domain enablement');
36
+ await this.enableCDPDomains();
37
+ this.debugLog('CDP domain enablement completed');
38
+ // Setup event handlers for comprehensive logging
39
+ this.debugLog('Setting up CDP event handlers');
40
+ this.setupEventHandlers();
41
+ this.debugLog('CDP event handlers setup completed');
42
+ }
43
+ createLoadingPage() {
44
+ const loadingDir = join(tmpdir(), 'dev3000-loading');
45
+ if (!existsSync(loadingDir)) {
46
+ mkdirSync(loadingDir, { recursive: true });
47
+ }
48
+ const loadingPath = join(loadingDir, 'loading.html');
49
+ const loadingHtml = `<!DOCTYPE html>
50
+ <html>
51
+ <head>
52
+ <title>dev3000 - Starting...</title>
53
+ <style>
54
+ body {
55
+ margin: 0;
56
+ padding: 0;
57
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
58
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
59
+ display: flex;
60
+ align-items: center;
61
+ justify-content: center;
62
+ height: 100vh;
63
+ color: white;
64
+ }
65
+ .container {
66
+ text-align: center;
67
+ padding: 40px;
68
+ border-radius: 12px;
69
+ background: rgba(255,255,255,0.1);
70
+ backdrop-filter: blur(10px);
71
+ box-shadow: 0 8px 32px rgba(0,0,0,0.2);
72
+ }
73
+ .spinner {
74
+ width: 40px;
75
+ height: 40px;
76
+ border: 3px solid rgba(255,255,255,0.3);
77
+ border-top: 3px solid white;
78
+ border-radius: 50%;
79
+ animation: spin 1s linear infinite;
80
+ margin: 0 auto 20px;
81
+ }
82
+ @keyframes spin {
83
+ 0% { transform: rotate(0deg); }
84
+ 100% { transform: rotate(360deg); }
85
+ }
86
+ h1 { margin: 0 0 10px; font-size: 24px; font-weight: 600; }
87
+ p { margin: 0; opacity: 0.9; font-size: 16px; }
88
+ </style>
89
+ </head>
90
+ <body>
91
+ <div class="container">
92
+ <div class="spinner"></div>
93
+ <h1>dev3000</h1>
94
+ <p>Starting your development environment...</p>
95
+ </div>
96
+ </body>
97
+ </html>`;
98
+ writeFileSync(loadingPath, loadingHtml);
99
+ return `file://${loadingPath}`;
100
+ }
101
+ async launchChrome() {
102
+ return new Promise((resolve, reject) => {
103
+ // Try different Chrome executables based on platform
104
+ const chromeCommands = [
105
+ '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
106
+ 'google-chrome',
107
+ 'chrome',
108
+ 'chromium'
109
+ ];
110
+ this.debugLog(`Attempting to launch Chrome for CDP monitoring on port ${this.debugPort}`);
111
+ this.debugLog(`Profile directory: ${this.profileDir}`);
112
+ let chromePath = chromeCommands[0]; // Default to macOS path
113
+ this.debugLog(`Using Chrome path: ${chromePath}`);
114
+ this.browser = spawn(chromePath, [
115
+ `--remote-debugging-port=${this.debugPort}`,
116
+ `--user-data-dir=${this.profileDir}`,
117
+ '--no-first-run',
118
+ this.createLoadingPage()
119
+ ], {
120
+ stdio: 'pipe',
121
+ detached: false
122
+ });
123
+ if (!this.browser) {
124
+ reject(new Error('Failed to launch Chrome'));
125
+ return;
126
+ }
127
+ this.browser.on('error', (error) => {
128
+ this.debugLog(`Chrome launch error: ${error.message}`);
129
+ if (!this.isShuttingDown) {
130
+ reject(error);
131
+ }
132
+ });
133
+ this.browser.stderr?.on('data', (data) => {
134
+ this.debugLog(`Chrome stderr: ${data.toString().trim()}`);
135
+ });
136
+ this.browser.stdout?.on('data', (data) => {
137
+ this.debugLog(`Chrome stdout: ${data.toString().trim()}`);
138
+ });
139
+ // Give Chrome time to start up
140
+ setTimeout(() => {
141
+ this.debugLog('Chrome startup timeout reached, assuming success');
142
+ resolve();
143
+ }, 3000);
144
+ });
145
+ }
146
+ async connectToCDP() {
147
+ this.debugLog(`Attempting to connect to CDP on port ${this.debugPort}`);
148
+ // Retry connection with exponential backoff
149
+ let retryCount = 0;
150
+ const maxRetries = 5;
151
+ while (retryCount < maxRetries) {
152
+ try {
153
+ // Get the WebSocket URL from Chrome's debug endpoint
154
+ const targetsResponse = await fetch(`http://localhost:${this.debugPort}/json`);
155
+ const targets = await targetsResponse.json();
156
+ // Find the first page target (tab)
157
+ const pageTarget = targets.find((target) => target.type === 'page');
158
+ if (!pageTarget) {
159
+ throw new Error('No page target found in Chrome');
160
+ }
161
+ const wsUrl = pageTarget.webSocketDebuggerUrl;
162
+ this.debugLog(`Found page target: ${pageTarget.title || 'Unknown'} - ${pageTarget.url}`);
163
+ this.debugLog(`Got CDP WebSocket URL: ${wsUrl}`);
164
+ return new Promise((resolve, reject) => {
165
+ this.debugLog(`Creating WebSocket connection to: ${wsUrl}`);
166
+ const ws = new WebSocket(wsUrl);
167
+ // Increase max listeners to prevent warnings
168
+ ws.setMaxListeners(20);
169
+ ws.on('open', () => {
170
+ this.debugLog('WebSocket connection opened successfully');
171
+ this.connection = {
172
+ ws,
173
+ sessionId: null,
174
+ nextId: 1
175
+ };
176
+ resolve();
177
+ });
178
+ ws.on('error', (error) => {
179
+ this.debugLog(`WebSocket connection error: ${error}`);
180
+ reject(error);
181
+ });
182
+ ws.on('message', (data) => {
183
+ try {
184
+ const message = JSON.parse(data.toString());
185
+ this.handleCDPMessage(message);
186
+ }
187
+ catch (error) {
188
+ this.logger('browser', `[CDP ERROR] Failed to parse message: ${error}`);
189
+ }
190
+ });
191
+ ws.on('close', (code, reason) => {
192
+ this.debugLog(`WebSocket closed with code ${code}, reason: ${reason}`);
193
+ if (!this.isShuttingDown) {
194
+ this.logger('browser', `[CDP] Connection closed unexpectedly (code: ${code}, reason: ${reason})`);
195
+ }
196
+ });
197
+ // Connection timeout
198
+ setTimeout(() => {
199
+ this.debugLog(`WebSocket readyState: ${ws.readyState} (CONNECTING=0, OPEN=1, CLOSING=2, CLOSED=3)`);
200
+ if (ws.readyState === WebSocket.CONNECTING) {
201
+ this.debugLog('WebSocket connection timed out, closing');
202
+ ws.close();
203
+ reject(new Error('CDP connection timeout'));
204
+ }
205
+ }, 5000);
206
+ });
207
+ }
208
+ catch (error) {
209
+ retryCount++;
210
+ this.debugLog(`CDP connection attempt ${retryCount} failed: ${error}`);
211
+ if (retryCount >= maxRetries) {
212
+ throw new Error(`Failed to connect to CDP after ${maxRetries} attempts: ${error}`);
213
+ }
214
+ // Exponential backoff
215
+ const delay = Math.min(1000 * Math.pow(2, retryCount - 1), 5000);
216
+ this.debugLog(`Retrying CDP connection in ${delay}ms`);
217
+ await new Promise(resolve => setTimeout(resolve, delay));
218
+ }
219
+ }
220
+ }
221
+ async sendCDPCommand(method, params = {}) {
222
+ if (!this.connection) {
223
+ throw new Error('No CDP connection available');
224
+ }
225
+ return new Promise((resolve, reject) => {
226
+ const id = this.connection.nextId++;
227
+ const command = {
228
+ id,
229
+ method,
230
+ params,
231
+ };
232
+ const messageHandler = (data) => {
233
+ try {
234
+ const message = JSON.parse(data.toString());
235
+ if (message.id === id) {
236
+ this.connection.ws.removeListener('message', messageHandler);
237
+ if (message.error) {
238
+ reject(new Error(message.error.message));
239
+ }
240
+ else {
241
+ resolve(message.result);
242
+ }
243
+ }
244
+ }
245
+ catch (error) {
246
+ this.connection.ws.removeListener('message', messageHandler);
247
+ reject(error);
248
+ }
249
+ };
250
+ this.connection.ws.on('message', messageHandler);
251
+ // Command timeout
252
+ const timeout = setTimeout(() => {
253
+ this.connection.ws.removeListener('message', messageHandler);
254
+ reject(new Error(`CDP command timeout: ${method}`));
255
+ }, 10000);
256
+ // Clear timeout if command succeeds/fails
257
+ const originalResolve = resolve;
258
+ const originalReject = reject;
259
+ resolve = (value) => {
260
+ clearTimeout(timeout);
261
+ originalResolve(value);
262
+ };
263
+ reject = (reason) => {
264
+ clearTimeout(timeout);
265
+ originalReject(reason);
266
+ };
267
+ this.connection.ws.send(JSON.stringify(command));
268
+ });
269
+ }
270
+ async enableCDPDomains() {
271
+ const domains = [
272
+ 'Runtime', // Console logs, exceptions
273
+ 'Network', // Network requests/responses
274
+ 'Page', // Page events, navigation
275
+ 'DOM', // DOM mutations
276
+ 'Performance', // Performance metrics
277
+ 'Security' // Security events
278
+ ];
279
+ for (const domain of domains) {
280
+ try {
281
+ this.debugLog(`Enabling CDP domain: ${domain}`);
282
+ await this.sendCDPCommand(`${domain}.enable`);
283
+ this.debugLog(`Successfully enabled CDP domain: ${domain}`);
284
+ this.logger('browser', `[CDP] Enabled ${domain} domain`);
285
+ }
286
+ catch (error) {
287
+ this.debugLog(`Failed to enable CDP domain ${domain}: ${error}`);
288
+ this.logger('browser', `[CDP ERROR] Failed to enable ${domain}: ${error}`);
289
+ // Continue with other domains instead of throwing
290
+ }
291
+ }
292
+ this.debugLog('Setting up input event capturing');
293
+ await this.sendCDPCommand('Input.setIgnoreInputEvents', { ignore: false });
294
+ this.debugLog('Enabling runtime for console and exception capture');
295
+ await this.sendCDPCommand('Runtime.enable');
296
+ await this.sendCDPCommand('Runtime.setAsyncCallStackDepth', { maxDepth: 32 });
297
+ this.debugLog('CDP domains enabled successfully');
298
+ }
299
+ setupEventHandlers() {
300
+ // Console messages with full context
301
+ this.onCDPEvent('Runtime.consoleAPICalled', (event) => {
302
+ const { type, args, stackTrace } = event.params;
303
+ // Check if this is our interaction tracking
304
+ if (args.length > 0 && args[0].value?.includes('[DEV3000_INTERACTION]')) {
305
+ const interaction = args[0].value.replace('[DEV3000_INTERACTION] ', '');
306
+ this.logger('browser', `[INTERACTION] ${interaction}`);
307
+ return;
308
+ }
309
+ // Log regular console messages with enhanced context
310
+ const values = args.map((arg) => {
311
+ if (arg.type === 'object' && arg.preview) {
312
+ return JSON.stringify(arg.preview);
313
+ }
314
+ return arg.value || '[object]';
315
+ }).join(' ');
316
+ let logMsg = `[CONSOLE ${type.toUpperCase()}] ${values}`;
317
+ // Add stack trace for errors
318
+ if (stackTrace && (type === 'error' || type === 'assert')) {
319
+ logMsg += `\n[STACK] ${stackTrace.callFrames.slice(0, 3).map((frame) => `${frame.functionName || 'anonymous'}@${frame.url}:${frame.lineNumber}`).join(' -> ')}`;
320
+ }
321
+ this.logger('browser', logMsg);
322
+ });
323
+ // Runtime exceptions with full stack traces
324
+ this.onCDPEvent('Runtime.exceptionThrown', (event) => {
325
+ const { exceptionDetails } = event.params;
326
+ const { text, lineNumber, columnNumber, url, stackTrace } = exceptionDetails;
327
+ let errorMsg = `[RUNTIME ERROR] ${text}`;
328
+ if (url)
329
+ errorMsg += ` at ${url}:${lineNumber}:${columnNumber}`;
330
+ if (stackTrace) {
331
+ errorMsg += `\n[STACK] ${stackTrace.callFrames.slice(0, 5).map((frame) => `${frame.functionName || 'anonymous'}@${frame.url}:${frame.lineNumber}`).join(' -> ')}`;
332
+ }
333
+ this.logger('browser', errorMsg);
334
+ });
335
+ // Network requests with full details
336
+ this.onCDPEvent('Network.requestWillBeSent', (event) => {
337
+ const { request, type, initiator } = event.params;
338
+ const { url, method, headers, postData } = request;
339
+ let logMsg = `[NETWORK REQUEST] ${method} ${url}`;
340
+ if (type)
341
+ logMsg += ` (${type})`;
342
+ if (initiator?.type)
343
+ logMsg += ` initiated by ${initiator.type}`;
344
+ // Log important headers
345
+ const importantHeaders = ['content-type', 'authorization', 'cookie'];
346
+ const headerInfo = importantHeaders
347
+ .filter(h => headers[h])
348
+ .map(h => `${h}: ${headers[h].slice(0, 50)}${headers[h].length > 50 ? '...' : ''}`)
349
+ .join(', ');
350
+ if (headerInfo)
351
+ logMsg += ` [${headerInfo}]`;
352
+ if (postData)
353
+ logMsg += ` body: ${postData.slice(0, 100)}${postData.length > 100 ? '...' : ''}`;
354
+ this.logger('browser', logMsg);
355
+ });
356
+ // Network responses with full details
357
+ this.onCDPEvent('Network.responseReceived', (event) => {
358
+ const { response, type } = event.params;
359
+ const { url, status, statusText, mimeType } = response;
360
+ let logMsg = `[NETWORK RESPONSE] ${status} ${statusText} ${url}`;
361
+ if (type)
362
+ logMsg += ` (${type})`;
363
+ if (mimeType)
364
+ logMsg += ` [${mimeType}]`;
365
+ // Add timing info if available
366
+ const timing = response.timing;
367
+ if (timing) {
368
+ const totalTime = Math.round(timing.receiveHeadersEnd - timing.requestTime);
369
+ if (totalTime > 0)
370
+ logMsg += ` (${totalTime}ms)`;
371
+ }
372
+ this.logger('browser', logMsg);
373
+ });
374
+ // Page navigation with full context
375
+ this.onCDPEvent('Page.frameNavigated', (event) => {
376
+ const { frame } = event.params;
377
+ if (frame.parentId)
378
+ return; // Only log main frame navigation
379
+ this.logger('browser', `[NAVIGATION] ${frame.url}`);
380
+ // Take screenshot after navigation
381
+ setTimeout(() => {
382
+ this.takeScreenshot('navigation');
383
+ }, 1000);
384
+ });
385
+ // DOM mutations for interaction context
386
+ this.onCDPEvent('DOM.documentUpdated', () => {
387
+ // Document structure changed - useful for SPA routing
388
+ this.logger('browser', '[DOM] Document updated');
389
+ });
390
+ // Performance metrics - disabled to reduce log noise
391
+ // this.onCDPEvent('Performance.metrics', (event) => {
392
+ // const metrics = event.params.metrics;
393
+ // const importantMetrics = metrics.filter((m: any) =>
394
+ // ['JSHeapUsedSize', 'JSHeapTotalSize', 'Nodes', 'Documents'].includes(m.name)
395
+ // );
396
+ //
397
+ // if (importantMetrics.length > 0) {
398
+ // const metricsStr = importantMetrics
399
+ // .map((m: any) => `${m.name}:${Math.round(m.value)}`)
400
+ // .join(' ');
401
+ // this.logger('browser', `[PERFORMANCE] ${metricsStr}`);
402
+ // }
403
+ // });
404
+ }
405
+ onCDPEvent(method, handler) {
406
+ this.eventHandlers.set(method, handler);
407
+ }
408
+ handleCDPMessage(message) {
409
+ if (message.method) {
410
+ const handler = this.eventHandlers.get(message.method);
411
+ if (handler) {
412
+ const event = {
413
+ method: message.method,
414
+ params: message.params || {},
415
+ timestamp: Date.now(),
416
+ sessionId: message.sessionId
417
+ };
418
+ handler(event);
419
+ }
420
+ }
421
+ }
422
+ async navigateToApp(port) {
423
+ if (!this.connection) {
424
+ throw new Error('No CDP connection available');
425
+ }
426
+ this.debugLog(`Navigating to http://localhost:${port}`);
427
+ // Navigate to the app
428
+ await this.sendCDPCommand('Page.navigate', {
429
+ url: `http://localhost:${port}`
430
+ });
431
+ this.debugLog('Navigation command sent successfully');
432
+ this.debugLog('Setting up interaction tracking');
433
+ // Enable interaction tracking via Runtime.evaluate
434
+ await this.setupInteractionTracking();
435
+ this.debugLog('Interaction tracking setup completed');
436
+ }
437
+ async setupInteractionTracking() {
438
+ // Inject comprehensive interaction tracking
439
+ const trackingScript = `
440
+ // Only inject once
441
+ if (window.__dev3000_cdp_tracking) return;
442
+ window.__dev3000_cdp_tracking = true;
443
+
444
+ // Track all mouse events
445
+ ['click', 'mousedown', 'mouseup', 'mousemove'].forEach(eventType => {
446
+ document.addEventListener(eventType, (event) => {
447
+ const target = event.target;
448
+ const rect = target.getBoundingClientRect();
449
+
450
+ const interactionData = {
451
+ type: eventType.toUpperCase(),
452
+ timestamp: Date.now(),
453
+ coordinates: {
454
+ x: event.clientX,
455
+ y: event.clientY,
456
+ elementX: event.clientX - rect.left,
457
+ elementY: event.clientY - rect.top
458
+ },
459
+ target: {
460
+ selector: target.tagName.toLowerCase() +
461
+ (target.id ? '#' + target.id : '') +
462
+ (target.className ? '.' + target.className.split(' ').join('.') : ''),
463
+ text: target.textContent?.slice(0, 100) || null,
464
+ attributes: {
465
+ id: target.id || null,
466
+ className: target.className || null,
467
+ type: target.type || null,
468
+ href: target.href || null
469
+ },
470
+ bounds: {
471
+ x: Math.round(rect.left),
472
+ y: Math.round(rect.top),
473
+ width: Math.round(rect.width),
474
+ height: Math.round(rect.height)
475
+ }
476
+ },
477
+ viewport: {
478
+ width: window.innerWidth,
479
+ height: window.innerHeight
480
+ },
481
+ scroll: {
482
+ x: window.scrollX,
483
+ y: window.scrollY
484
+ },
485
+ modifiers: {
486
+ ctrl: event.ctrlKey,
487
+ alt: event.altKey,
488
+ shift: event.shiftKey,
489
+ meta: event.metaKey
490
+ }
491
+ };
492
+
493
+ console.log('[DEV3000_INTERACTION] ' + JSON.stringify(interactionData));
494
+ }, true);
495
+ });
496
+
497
+ // Track keyboard events with enhanced context
498
+ document.addEventListener('keydown', (event) => {
499
+ const target = event.target;
500
+
501
+ const interactionData = {
502
+ type: 'KEYDOWN',
503
+ timestamp: Date.now(),
504
+ key: event.key,
505
+ code: event.code,
506
+ target: {
507
+ selector: target.tagName.toLowerCase() +
508
+ (target.id ? '#' + target.id : '') +
509
+ (target.className ? '.' + target.className.split(' ').join('.') : ''),
510
+ value: target.value?.slice(0, 50) || null,
511
+ attributes: {
512
+ type: target.type || null,
513
+ placeholder: target.placeholder || null
514
+ }
515
+ },
516
+ modifiers: {
517
+ ctrl: event.ctrlKey,
518
+ alt: event.altKey,
519
+ shift: event.shiftKey,
520
+ meta: event.metaKey
521
+ }
522
+ };
523
+
524
+ // Only log special keys and form interactions
525
+ if (event.key.length > 1 || target.tagName === 'INPUT' || target.tagName === 'TEXTAREA') {
526
+ console.log('[DEV3000_INTERACTION] ' + JSON.stringify(interactionData));
527
+ }
528
+ }, true);
529
+
530
+ // Track scroll events with momentum detection
531
+ let scrollTimeout;
532
+ let lastScrollTime = 0;
533
+ let scrollStartTime = 0;
534
+ let lastScrollPos = { x: window.scrollX, y: window.scrollY };
535
+
536
+ document.addEventListener('scroll', () => {
537
+ const now = Date.now();
538
+
539
+ if (now - lastScrollTime > 100) { // New scroll session
540
+ scrollStartTime = now;
541
+ }
542
+ lastScrollTime = now;
543
+
544
+ clearTimeout(scrollTimeout);
545
+ scrollTimeout = setTimeout(() => {
546
+ const endPos = { x: window.scrollX, y: window.scrollY };
547
+ const distance = Math.round(Math.sqrt(
548
+ Math.pow(endPos.x - lastScrollPos.x, 2) +
549
+ Math.pow(endPos.y - lastScrollPos.y, 2)
550
+ ));
551
+
552
+ if (distance > 10) {
553
+ const direction = endPos.y > lastScrollPos.y ? 'DOWN' :
554
+ endPos.y < lastScrollPos.y ? 'UP' :
555
+ endPos.x > lastScrollPos.x ? 'RIGHT' : 'LEFT';
556
+
557
+ const interactionData = {
558
+ type: 'SCROLL',
559
+ timestamp: now,
560
+ direction,
561
+ distance,
562
+ duration: now - scrollStartTime,
563
+ from: lastScrollPos,
564
+ to: endPos,
565
+ viewport: {
566
+ width: window.innerWidth,
567
+ height: window.innerHeight
568
+ }
569
+ };
570
+
571
+ console.log('[DEV3000_INTERACTION] ' + JSON.stringify(interactionData));
572
+ lastScrollPos = endPos;
573
+ }
574
+ }, 150); // Wait for scroll to finish
575
+ }, true);
576
+
577
+ // Track form submissions
578
+ document.addEventListener('submit', (event) => {
579
+ const form = event.target;
580
+ const formData = new FormData(form);
581
+ const fields = {};
582
+
583
+ for (const [key, value] of formData.entries()) {
584
+ // Don't log actual values, just field names for privacy
585
+ fields[key] = typeof value === 'string' ? \`<\${value.length} chars>\` : '<file>';
586
+ }
587
+
588
+ const interactionData = {
589
+ type: 'FORM_SUBMIT',
590
+ timestamp: Date.now(),
591
+ target: {
592
+ action: form.action || window.location.href,
593
+ method: form.method || 'GET',
594
+ fields: Object.keys(fields)
595
+ }
596
+ };
597
+
598
+ console.log('[DEV3000_INTERACTION] ' + JSON.stringify(interactionData));
599
+ }, true);
600
+
601
+ console.log('[DEV3000_INTERACTION] CDP tracking initialized');
602
+ `;
603
+ await this.sendCDPCommand('Runtime.evaluate', {
604
+ expression: trackingScript,
605
+ includeCommandLineAPI: false
606
+ });
607
+ }
608
+ async takeScreenshot(event) {
609
+ try {
610
+ const result = await this.sendCDPCommand('Page.captureScreenshot', {
611
+ format: 'png',
612
+ quality: 80,
613
+ clip: undefined, // Full viewport
614
+ fromSurface: true
615
+ });
616
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
617
+ const filename = `${timestamp}-${event}.png`;
618
+ const screenshotPath = `/tmp/dev3000-screenshot-${filename}`;
619
+ // Save the base64 image
620
+ const buffer = Buffer.from(result.data, 'base64');
621
+ writeFileSync(screenshotPath, buffer);
622
+ return filename;
623
+ }
624
+ catch (error) {
625
+ this.logger('browser', `[CDP ERROR] Screenshot failed: ${error}`);
626
+ return null;
627
+ }
628
+ }
629
+ // Enhanced replay functionality using CDP
630
+ async executeInteraction(interaction) {
631
+ if (!this.connection) {
632
+ throw new Error('No CDP connection available');
633
+ }
634
+ try {
635
+ switch (interaction.type) {
636
+ case 'CLICK':
637
+ await this.sendCDPCommand('Input.dispatchMouseEvent', {
638
+ type: 'mousePressed',
639
+ x: interaction.coordinates.x,
640
+ y: interaction.coordinates.y,
641
+ button: 'left',
642
+ clickCount: 1
643
+ });
644
+ await this.sendCDPCommand('Input.dispatchMouseEvent', {
645
+ type: 'mouseReleased',
646
+ x: interaction.coordinates.x,
647
+ y: interaction.coordinates.y,
648
+ button: 'left',
649
+ clickCount: 1
650
+ });
651
+ break;
652
+ case 'KEYDOWN':
653
+ await this.sendCDPCommand('Input.dispatchKeyEvent', {
654
+ type: 'keyDown',
655
+ key: interaction.key,
656
+ code: interaction.code,
657
+ ...interaction.modifiers
658
+ });
659
+ break;
660
+ case 'SCROLL':
661
+ await this.sendCDPCommand('Input.dispatchMouseEvent', {
662
+ type: 'mouseWheel',
663
+ x: interaction.to.x,
664
+ y: interaction.to.y,
665
+ deltaX: interaction.to.x - interaction.from.x,
666
+ deltaY: interaction.to.y - interaction.from.y
667
+ });
668
+ break;
669
+ default:
670
+ this.logger('browser', `[REPLAY] Unknown interaction type: ${interaction.type}`);
671
+ }
672
+ }
673
+ catch (error) {
674
+ this.logger('browser', `[REPLAY ERROR] Failed to execute ${interaction.type}: ${error}`);
675
+ }
676
+ }
677
+ async shutdown() {
678
+ this.isShuttingDown = true;
679
+ // Close CDP connection
680
+ if (this.connection) {
681
+ this.connection.ws.close();
682
+ this.connection = null;
683
+ }
684
+ // Close browser
685
+ if (this.browser) {
686
+ this.browser.kill('SIGTERM');
687
+ // Force kill after 2 seconds if not closed
688
+ setTimeout(() => {
689
+ if (this.browser) {
690
+ this.browser.kill('SIGKILL');
691
+ }
692
+ }, 2000);
693
+ this.browser = null;
694
+ }
695
+ }
696
+ }
697
+ //# sourceMappingURL=cdp-monitor.js.map