dev3000 0.0.38 → 0.0.41
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.
- package/dist/cdp-monitor.d.ts +1 -1
- package/dist/cdp-monitor.d.ts.map +1 -1
- package/dist/cdp-monitor.js +340 -405
- package/dist/cdp-monitor.js.map +1 -1
- package/dist/dev-environment.d.ts +3 -1
- package/dist/dev-environment.d.ts.map +1 -1
- package/dist/dev-environment.js +122 -70
- package/dist/dev-environment.js.map +1 -1
- package/dist/src/loading.html +206 -0
- package/mcp-server/app/logs/LogsClient.tsx +249 -12
- package/package.json +11 -10
package/dist/cdp-monitor.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { spawn } from
|
|
2
|
-
import { WebSocket } from
|
|
3
|
-
import { writeFileSync, existsSync, mkdirSync } from
|
|
4
|
-
import { join } from
|
|
5
|
-
import { tmpdir } from
|
|
1
|
+
import { spawn } from "child_process";
|
|
2
|
+
import { WebSocket } from "ws";
|
|
3
|
+
import { writeFileSync, existsSync, mkdirSync, readFileSync } from "fs";
|
|
4
|
+
import { join, dirname } from "path";
|
|
5
|
+
import { tmpdir } from "os";
|
|
6
|
+
import { fileURLToPath } from "url";
|
|
6
7
|
export class CDPMonitor {
|
|
7
8
|
browser = null;
|
|
8
9
|
connection = null;
|
|
@@ -28,132 +29,49 @@ export class CDPMonitor {
|
|
|
28
29
|
}
|
|
29
30
|
async start() {
|
|
30
31
|
// Launch Chrome with CDP enabled
|
|
31
|
-
this.debugLog(
|
|
32
|
+
this.debugLog("Starting Chrome launch process");
|
|
32
33
|
await this.launchChrome();
|
|
33
|
-
this.debugLog(
|
|
34
|
+
this.debugLog("Chrome launch completed");
|
|
34
35
|
// Connect to Chrome DevTools Protocol
|
|
35
|
-
this.debugLog(
|
|
36
|
+
this.debugLog("Starting CDP connection");
|
|
36
37
|
await this.connectToCDP();
|
|
37
|
-
this.debugLog(
|
|
38
|
+
this.debugLog("CDP connection completed");
|
|
38
39
|
// Enable all the CDP domains we need for comprehensive monitoring
|
|
39
|
-
this.debugLog(
|
|
40
|
+
this.debugLog("Starting CDP domain enablement");
|
|
40
41
|
await this.enableCDPDomains();
|
|
41
|
-
this.debugLog(
|
|
42
|
+
this.debugLog("CDP domain enablement completed");
|
|
42
43
|
// Setup event handlers for comprehensive logging
|
|
43
|
-
this.debugLog(
|
|
44
|
+
this.debugLog("Setting up CDP event handlers");
|
|
44
45
|
this.setupEventHandlers();
|
|
45
|
-
this.debugLog(
|
|
46
|
+
this.debugLog("CDP event handlers setup completed");
|
|
46
47
|
}
|
|
47
48
|
createLoadingPage() {
|
|
48
|
-
const loadingDir = join(tmpdir(),
|
|
49
|
+
const loadingDir = join(tmpdir(), "dev3000-loading");
|
|
49
50
|
if (!existsSync(loadingDir)) {
|
|
50
51
|
mkdirSync(loadingDir, { recursive: true });
|
|
51
52
|
}
|
|
52
|
-
const loadingPath = join(loadingDir,
|
|
53
|
-
|
|
53
|
+
const loadingPath = join(loadingDir, "loading.html");
|
|
54
|
+
// Read the loading HTML from the source file
|
|
55
|
+
const currentFile = fileURLToPath(import.meta.url);
|
|
56
|
+
const currentDir = dirname(currentFile);
|
|
57
|
+
const loadingHtmlPath = join(currentDir, "src/loading.html");
|
|
58
|
+
let loadingHtml;
|
|
59
|
+
try {
|
|
60
|
+
loadingHtml = readFileSync(loadingHtmlPath, 'utf-8');
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
// Fallback to a simple loading page if file not found
|
|
64
|
+
loadingHtml = `<!DOCTYPE html>
|
|
54
65
|
<html>
|
|
55
|
-
<head>
|
|
56
|
-
|
|
57
|
-
<style>
|
|
58
|
-
* {
|
|
59
|
-
box-sizing: border-box;
|
|
60
|
-
}
|
|
61
|
-
body {
|
|
62
|
-
margin: 0;
|
|
63
|
-
padding: 0;
|
|
64
|
-
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
|
|
65
|
-
background: #000000;
|
|
66
|
-
display: flex;
|
|
67
|
-
align-items: center;
|
|
68
|
-
justify-content: center;
|
|
69
|
-
height: 100vh;
|
|
70
|
-
color: #fafafa;
|
|
71
|
-
}
|
|
72
|
-
.container {
|
|
73
|
-
text-align: center;
|
|
74
|
-
padding: 48px;
|
|
75
|
-
max-width: 400px;
|
|
76
|
-
}
|
|
77
|
-
.logo-container {
|
|
78
|
-
margin-bottom: 32px;
|
|
79
|
-
position: relative;
|
|
80
|
-
}
|
|
81
|
-
.triangle {
|
|
82
|
-
font-size: 48px;
|
|
83
|
-
color: #fafafa;
|
|
84
|
-
margin-bottom: 24px;
|
|
85
|
-
display: block;
|
|
86
|
-
animation: pulse 2s ease-in-out infinite;
|
|
87
|
-
line-height: 1;
|
|
88
|
-
}
|
|
89
|
-
.spinner-ring {
|
|
90
|
-
position: absolute;
|
|
91
|
-
top: 50%;
|
|
92
|
-
left: 50%;
|
|
93
|
-
transform: translate(-50%, -50%);
|
|
94
|
-
width: 80px;
|
|
95
|
-
height: 80px;
|
|
96
|
-
border: 2px solid rgba(250, 250, 250, 0.1);
|
|
97
|
-
border-top: 2px solid #fafafa;
|
|
98
|
-
border-radius: 50%;
|
|
99
|
-
animation: spin 1.5s linear infinite;
|
|
100
|
-
}
|
|
101
|
-
@keyframes spin {
|
|
102
|
-
0% { transform: translate(-50%, -50%) rotate(0deg); }
|
|
103
|
-
100% { transform: translate(-50%, -50%) rotate(360deg); }
|
|
104
|
-
}
|
|
105
|
-
@keyframes pulse {
|
|
106
|
-
0%, 100% { opacity: 1; transform: scale(1); }
|
|
107
|
-
50% { opacity: 0.8; transform: scale(1.05); }
|
|
108
|
-
}
|
|
109
|
-
h1 {
|
|
110
|
-
margin: 0 0 16px;
|
|
111
|
-
font-size: 28px;
|
|
112
|
-
font-weight: 500;
|
|
113
|
-
letter-spacing: -0.5px;
|
|
114
|
-
}
|
|
115
|
-
.tagline {
|
|
116
|
-
margin: 0 0 8px;
|
|
117
|
-
font-size: 16px;
|
|
118
|
-
color: #a1a1aa;
|
|
119
|
-
font-weight: 400;
|
|
120
|
-
}
|
|
121
|
-
.subtitle {
|
|
122
|
-
margin: 0;
|
|
123
|
-
font-size: 14px;
|
|
124
|
-
color: #71717a;
|
|
125
|
-
font-weight: 400;
|
|
126
|
-
}
|
|
127
|
-
.footer {
|
|
128
|
-
position: absolute;
|
|
129
|
-
bottom: 24px;
|
|
130
|
-
left: 50%;
|
|
131
|
-
transform: translateX(-50%);
|
|
132
|
-
font-size: 12px;
|
|
133
|
-
color: #52525b;
|
|
134
|
-
opacity: 0.8;
|
|
135
|
-
}
|
|
136
|
-
.triangle-small {
|
|
137
|
-
font-size: 10px;
|
|
138
|
-
margin-right: 6px;
|
|
139
|
-
}
|
|
140
|
-
</style>
|
|
141
|
-
</head>
|
|
142
|
-
<body>
|
|
143
|
-
<div class="container">
|
|
144
|
-
<div class="logo-container">
|
|
145
|
-
<div class="triangle">▲</div>
|
|
146
|
-
<div class="spinner-ring"></div>
|
|
147
|
-
</div>
|
|
66
|
+
<head><title>dev3000 - Starting...</title></head>
|
|
67
|
+
<body style="font-family: system-ui; background: #1e1e1e; color: #fff; display: flex; align-items: center; justify-content: center; height: 100vh; margin: 0;">
|
|
68
|
+
<div style="text-align: center;">
|
|
148
69
|
<h1>dev3000</h1>
|
|
149
|
-
<p
|
|
150
|
-
<p class="subtitle">Getting ready to capture everything 📸</p>
|
|
151
|
-
</div>
|
|
152
|
-
<div class="footer">
|
|
153
|
-
<span class="triangle-small">▲</span>Powered by Vercel Labs
|
|
70
|
+
<p>Starting development environment...</p>
|
|
154
71
|
</div>
|
|
155
72
|
</body>
|
|
156
73
|
</html>`;
|
|
74
|
+
}
|
|
157
75
|
writeFileSync(loadingPath, loadingHtml);
|
|
158
76
|
return `file://${loadingPath}`;
|
|
159
77
|
}
|
|
@@ -161,10 +79,10 @@ export class CDPMonitor {
|
|
|
161
79
|
return new Promise((resolve, reject) => {
|
|
162
80
|
// Try different Chrome executables based on platform
|
|
163
81
|
const chromeCommands = [
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
82
|
+
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
|
|
83
|
+
"google-chrome",
|
|
84
|
+
"chrome",
|
|
85
|
+
"chromium",
|
|
168
86
|
];
|
|
169
87
|
this.debugLog(`Attempting to launch Chrome for CDP monitoring on port ${this.debugPort}`);
|
|
170
88
|
this.debugLog(`Profile directory: ${this.profileDir}`);
|
|
@@ -173,31 +91,31 @@ export class CDPMonitor {
|
|
|
173
91
|
this.browser = spawn(chromePath, [
|
|
174
92
|
`--remote-debugging-port=${this.debugPort}`,
|
|
175
93
|
`--user-data-dir=${this.profileDir}`,
|
|
176
|
-
|
|
177
|
-
this.createLoadingPage()
|
|
94
|
+
"--no-first-run",
|
|
95
|
+
this.createLoadingPage(),
|
|
178
96
|
], {
|
|
179
|
-
stdio:
|
|
180
|
-
detached: false
|
|
97
|
+
stdio: "pipe",
|
|
98
|
+
detached: false,
|
|
181
99
|
});
|
|
182
100
|
if (!this.browser) {
|
|
183
|
-
reject(new Error(
|
|
101
|
+
reject(new Error("Failed to launch Chrome"));
|
|
184
102
|
return;
|
|
185
103
|
}
|
|
186
|
-
this.browser.on(
|
|
104
|
+
this.browser.on("error", (error) => {
|
|
187
105
|
this.debugLog(`Chrome launch error: ${error.message}`);
|
|
188
106
|
if (!this.isShuttingDown) {
|
|
189
107
|
reject(error);
|
|
190
108
|
}
|
|
191
109
|
});
|
|
192
|
-
this.browser.stderr?.on(
|
|
110
|
+
this.browser.stderr?.on("data", (data) => {
|
|
193
111
|
this.debugLog(`Chrome stderr: ${data.toString().trim()}`);
|
|
194
112
|
});
|
|
195
|
-
this.browser.stdout?.on(
|
|
113
|
+
this.browser.stdout?.on("data", (data) => {
|
|
196
114
|
this.debugLog(`Chrome stdout: ${data.toString().trim()}`);
|
|
197
115
|
});
|
|
198
116
|
// Give Chrome time to start up
|
|
199
117
|
setTimeout(() => {
|
|
200
|
-
this.debugLog(
|
|
118
|
+
this.debugLog("Chrome startup timeout reached, assuming success");
|
|
201
119
|
resolve();
|
|
202
120
|
}, 3000);
|
|
203
121
|
});
|
|
@@ -213,53 +131,53 @@ export class CDPMonitor {
|
|
|
213
131
|
const targetsResponse = await fetch(`http://localhost:${this.debugPort}/json`);
|
|
214
132
|
const targets = await targetsResponse.json();
|
|
215
133
|
// Find the first page target (tab)
|
|
216
|
-
const pageTarget = targets.find((target) => target.type ===
|
|
134
|
+
const pageTarget = targets.find((target) => target.type === "page");
|
|
217
135
|
if (!pageTarget) {
|
|
218
|
-
throw new Error(
|
|
136
|
+
throw new Error("No page target found in Chrome");
|
|
219
137
|
}
|
|
220
138
|
const wsUrl = pageTarget.webSocketDebuggerUrl;
|
|
221
|
-
this.debugLog(`Found page target: ${pageTarget.title ||
|
|
139
|
+
this.debugLog(`Found page target: ${pageTarget.title || "Unknown"} - ${pageTarget.url}`);
|
|
222
140
|
this.debugLog(`Got CDP WebSocket URL: ${wsUrl}`);
|
|
223
141
|
return new Promise((resolve, reject) => {
|
|
224
142
|
this.debugLog(`Creating WebSocket connection to: ${wsUrl}`);
|
|
225
143
|
const ws = new WebSocket(wsUrl);
|
|
226
144
|
// Increase max listeners to prevent warnings
|
|
227
145
|
ws.setMaxListeners(20);
|
|
228
|
-
ws.on(
|
|
229
|
-
this.debugLog(
|
|
146
|
+
ws.on("open", () => {
|
|
147
|
+
this.debugLog("WebSocket connection opened successfully");
|
|
230
148
|
this.connection = {
|
|
231
149
|
ws,
|
|
232
150
|
sessionId: null,
|
|
233
|
-
nextId: 1
|
|
151
|
+
nextId: 1,
|
|
234
152
|
};
|
|
235
153
|
resolve();
|
|
236
154
|
});
|
|
237
|
-
ws.on(
|
|
155
|
+
ws.on("error", (error) => {
|
|
238
156
|
this.debugLog(`WebSocket connection error: ${error}`);
|
|
239
157
|
reject(error);
|
|
240
158
|
});
|
|
241
|
-
ws.on(
|
|
159
|
+
ws.on("message", (data) => {
|
|
242
160
|
try {
|
|
243
161
|
const message = JSON.parse(data.toString());
|
|
244
162
|
this.handleCDPMessage(message);
|
|
245
163
|
}
|
|
246
164
|
catch (error) {
|
|
247
|
-
this.logger(
|
|
165
|
+
this.logger("browser", `[CDP ERROR] Failed to parse message: ${error}`);
|
|
248
166
|
}
|
|
249
167
|
});
|
|
250
|
-
ws.on(
|
|
168
|
+
ws.on("close", (code, reason) => {
|
|
251
169
|
this.debugLog(`WebSocket closed with code ${code}, reason: ${reason}`);
|
|
252
170
|
if (!this.isShuttingDown) {
|
|
253
|
-
this.logger(
|
|
171
|
+
this.logger("browser", `[CDP] Connection closed unexpectedly (code: ${code}, reason: ${reason})`);
|
|
254
172
|
}
|
|
255
173
|
});
|
|
256
174
|
// Connection timeout
|
|
257
175
|
setTimeout(() => {
|
|
258
176
|
this.debugLog(`WebSocket readyState: ${ws.readyState} (CONNECTING=0, OPEN=1, CLOSING=2, CLOSED=3)`);
|
|
259
177
|
if (ws.readyState === WebSocket.CONNECTING) {
|
|
260
|
-
this.debugLog(
|
|
178
|
+
this.debugLog("WebSocket connection timed out, closing");
|
|
261
179
|
ws.close();
|
|
262
|
-
reject(new Error(
|
|
180
|
+
reject(new Error("CDP connection timeout"));
|
|
263
181
|
}
|
|
264
182
|
}, 5000);
|
|
265
183
|
});
|
|
@@ -273,13 +191,13 @@ export class CDPMonitor {
|
|
|
273
191
|
// Exponential backoff
|
|
274
192
|
const delay = Math.min(1000 * Math.pow(2, retryCount - 1), 5000);
|
|
275
193
|
this.debugLog(`Retrying CDP connection in ${delay}ms`);
|
|
276
|
-
await new Promise(resolve => setTimeout(resolve, delay));
|
|
194
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
277
195
|
}
|
|
278
196
|
}
|
|
279
197
|
}
|
|
280
198
|
async sendCDPCommand(method, params = {}) {
|
|
281
199
|
if (!this.connection) {
|
|
282
|
-
throw new Error(
|
|
200
|
+
throw new Error("No CDP connection available");
|
|
283
201
|
}
|
|
284
202
|
return new Promise((resolve, reject) => {
|
|
285
203
|
const id = this.connection.nextId++;
|
|
@@ -292,7 +210,7 @@ export class CDPMonitor {
|
|
|
292
210
|
try {
|
|
293
211
|
const message = JSON.parse(data.toString());
|
|
294
212
|
if (message.id === id) {
|
|
295
|
-
this.connection.ws.removeListener(
|
|
213
|
+
this.connection.ws.removeListener("message", messageHandler);
|
|
296
214
|
if (message.error) {
|
|
297
215
|
reject(new Error(message.error.message));
|
|
298
216
|
}
|
|
@@ -302,14 +220,14 @@ export class CDPMonitor {
|
|
|
302
220
|
}
|
|
303
221
|
}
|
|
304
222
|
catch (error) {
|
|
305
|
-
this.connection.ws.removeListener(
|
|
223
|
+
this.connection.ws.removeListener("message", messageHandler);
|
|
306
224
|
reject(error);
|
|
307
225
|
}
|
|
308
226
|
};
|
|
309
|
-
this.connection.ws.on(
|
|
227
|
+
this.connection.ws.on("message", messageHandler);
|
|
310
228
|
// Command timeout
|
|
311
229
|
const timeout = setTimeout(() => {
|
|
312
|
-
this.connection.ws.removeListener(
|
|
230
|
+
this.connection.ws.removeListener("message", messageHandler);
|
|
313
231
|
reject(new Error(`CDP command timeout: ${method}`));
|
|
314
232
|
}, 10000);
|
|
315
233
|
// Clear timeout if command succeeds/fails
|
|
@@ -328,76 +246,97 @@ export class CDPMonitor {
|
|
|
328
246
|
}
|
|
329
247
|
async enableCDPDomains() {
|
|
330
248
|
const domains = [
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
249
|
+
"Runtime", // Console logs, exceptions
|
|
250
|
+
"Network", // Network requests/responses
|
|
251
|
+
"Page", // Page events, navigation
|
|
252
|
+
"DOM", // DOM mutations
|
|
253
|
+
"Performance", // Performance metrics
|
|
254
|
+
"Security", // Security events
|
|
255
|
+
"Log", // Browser console logs
|
|
256
|
+
"Input", // Mouse and keyboard input events
|
|
338
257
|
];
|
|
339
258
|
for (const domain of domains) {
|
|
340
259
|
try {
|
|
341
260
|
this.debugLog(`Enabling CDP domain: ${domain}`);
|
|
342
261
|
await this.sendCDPCommand(`${domain}.enable`);
|
|
343
262
|
this.debugLog(`Successfully enabled CDP domain: ${domain}`);
|
|
344
|
-
this.logger(
|
|
263
|
+
this.logger("browser", `[CDP] Enabled ${domain} domain`);
|
|
345
264
|
}
|
|
346
265
|
catch (error) {
|
|
347
266
|
this.debugLog(`Failed to enable CDP domain ${domain}: ${error}`);
|
|
348
|
-
this.logger(
|
|
267
|
+
this.logger("browser", `[CDP ERROR] Failed to enable ${domain}: ${error}`);
|
|
349
268
|
// Continue with other domains instead of throwing
|
|
350
269
|
}
|
|
351
270
|
}
|
|
352
|
-
this.debugLog(
|
|
353
|
-
await this.sendCDPCommand(
|
|
354
|
-
this.debugLog(
|
|
355
|
-
await this.sendCDPCommand(
|
|
356
|
-
await this.sendCDPCommand(
|
|
357
|
-
|
|
271
|
+
this.debugLog("Setting up input event capturing");
|
|
272
|
+
await this.sendCDPCommand("Input.setIgnoreInputEvents", { ignore: false });
|
|
273
|
+
this.debugLog("Enabling runtime for console and exception capture");
|
|
274
|
+
await this.sendCDPCommand("Runtime.enable");
|
|
275
|
+
await this.sendCDPCommand("Runtime.setAsyncCallStackDepth", {
|
|
276
|
+
maxDepth: 32,
|
|
277
|
+
});
|
|
278
|
+
this.debugLog("CDP domains enabled successfully");
|
|
358
279
|
}
|
|
359
280
|
setupEventHandlers() {
|
|
360
281
|
// Console messages with full context
|
|
361
|
-
this.onCDPEvent(
|
|
282
|
+
this.onCDPEvent("Runtime.consoleAPICalled", (event) => {
|
|
362
283
|
this.debugLog(`Runtime.consoleAPICalled event received: ${event.params.type}`);
|
|
363
284
|
const { type, args, stackTrace } = event.params;
|
|
285
|
+
// Debug: Log all console messages to see if tracking script is working
|
|
286
|
+
if (args.length > 0) {
|
|
287
|
+
this.debugLog(`Console message value: ${args[0].value}`);
|
|
288
|
+
this.debugLog(`Console message full arg: ${JSON.stringify(args[0])}`);
|
|
289
|
+
}
|
|
364
290
|
// Check if this is our interaction tracking
|
|
365
|
-
if (args.length > 0 && args[0].value?.includes(
|
|
366
|
-
const interaction = args[0].value.replace(
|
|
367
|
-
this.logger(
|
|
291
|
+
if (args.length > 0 && args[0].value?.includes("[DEV3000_INTERACTION]")) {
|
|
292
|
+
const interaction = args[0].value.replace("[DEV3000_INTERACTION] ", "");
|
|
293
|
+
this.logger("browser", `[INTERACTION] ${interaction}`);
|
|
368
294
|
return;
|
|
369
295
|
}
|
|
296
|
+
// Debug: Log all console messages to see if tracking script is even running
|
|
297
|
+
if (args.length > 0 &&
|
|
298
|
+
args[0].value?.includes("CDP tracking initialized")) {
|
|
299
|
+
this.logger("browser", `[DEBUG] Interaction tracking script loaded successfully`);
|
|
300
|
+
}
|
|
370
301
|
// Log regular console messages with enhanced context
|
|
371
|
-
const values = args
|
|
372
|
-
|
|
302
|
+
const values = args
|
|
303
|
+
.map((arg) => {
|
|
304
|
+
if (arg.type === "object" && arg.preview) {
|
|
373
305
|
return JSON.stringify(arg.preview);
|
|
374
306
|
}
|
|
375
|
-
return arg.value ||
|
|
376
|
-
})
|
|
307
|
+
return arg.value || "[object]";
|
|
308
|
+
})
|
|
309
|
+
.join(" ");
|
|
377
310
|
let logMsg = `[CONSOLE ${type.toUpperCase()}] ${values}`;
|
|
378
311
|
// Add stack trace for errors
|
|
379
|
-
if (stackTrace && (type ===
|
|
380
|
-
logMsg += `\n[STACK] ${stackTrace.callFrames
|
|
312
|
+
if (stackTrace && (type === "error" || type === "assert")) {
|
|
313
|
+
logMsg += `\n[STACK] ${stackTrace.callFrames
|
|
314
|
+
.slice(0, 3)
|
|
315
|
+
.map((frame) => `${frame.functionName || "anonymous"}@${frame.url}:${frame.lineNumber}`)
|
|
316
|
+
.join(" -> ")}`;
|
|
381
317
|
}
|
|
382
|
-
this.logger(
|
|
318
|
+
this.logger("browser", logMsg);
|
|
383
319
|
});
|
|
384
320
|
// Runtime exceptions with full stack traces
|
|
385
|
-
this.onCDPEvent(
|
|
386
|
-
this.debugLog(
|
|
321
|
+
this.onCDPEvent("Runtime.exceptionThrown", (event) => {
|
|
322
|
+
this.debugLog("Runtime.exceptionThrown event received");
|
|
387
323
|
const { exceptionDetails } = event.params;
|
|
388
324
|
const { text, lineNumber, columnNumber, url, stackTrace } = exceptionDetails;
|
|
389
325
|
let errorMsg = `[RUNTIME ERROR] ${text}`;
|
|
390
326
|
if (url)
|
|
391
327
|
errorMsg += ` at ${url}:${lineNumber}:${columnNumber}`;
|
|
392
328
|
if (stackTrace) {
|
|
393
|
-
errorMsg += `\n[STACK] ${stackTrace.callFrames
|
|
329
|
+
errorMsg += `\n[STACK] ${stackTrace.callFrames
|
|
330
|
+
.slice(0, 5)
|
|
331
|
+
.map((frame) => `${frame.functionName || "anonymous"}@${frame.url}:${frame.lineNumber}`)
|
|
332
|
+
.join(" -> ")}`;
|
|
394
333
|
}
|
|
395
|
-
this.logger(
|
|
334
|
+
this.logger("browser", errorMsg);
|
|
396
335
|
// Take screenshot immediately on errors (no delay needed)
|
|
397
|
-
this.takeScreenshot(
|
|
336
|
+
this.takeScreenshot("error");
|
|
398
337
|
});
|
|
399
338
|
// Browser console logs via Log domain (additional capture method)
|
|
400
|
-
this.onCDPEvent(
|
|
339
|
+
this.onCDPEvent("Log.entryAdded", (event) => {
|
|
401
340
|
const { entry } = event.params;
|
|
402
341
|
const { level, text, url, lineNumber } = entry;
|
|
403
342
|
let logMsg = `[CONSOLE ${level.toUpperCase()}] ${text}`;
|
|
@@ -405,12 +344,12 @@ export class CDPMonitor {
|
|
|
405
344
|
logMsg += ` at ${url}:${lineNumber}`;
|
|
406
345
|
}
|
|
407
346
|
// Only log if it's an error/warning or if we're not already capturing it via Runtime
|
|
408
|
-
if (level ===
|
|
409
|
-
this.logger(
|
|
347
|
+
if (level === "error" || level === "warning") {
|
|
348
|
+
this.logger("browser", logMsg);
|
|
410
349
|
}
|
|
411
350
|
});
|
|
412
351
|
// Network requests with full details
|
|
413
|
-
this.onCDPEvent(
|
|
352
|
+
this.onCDPEvent("Network.requestWillBeSent", (event) => {
|
|
414
353
|
const { request, type, initiator } = event.params;
|
|
415
354
|
const { url, method, headers, postData } = request;
|
|
416
355
|
let logMsg = `[NETWORK REQUEST] ${method} ${url}`;
|
|
@@ -419,19 +358,19 @@ export class CDPMonitor {
|
|
|
419
358
|
if (initiator?.type)
|
|
420
359
|
logMsg += ` initiated by ${initiator.type}`;
|
|
421
360
|
// Log important headers
|
|
422
|
-
const importantHeaders = [
|
|
361
|
+
const importantHeaders = ["content-type", "authorization", "cookie"];
|
|
423
362
|
const headerInfo = importantHeaders
|
|
424
|
-
.filter(h => headers[h])
|
|
425
|
-
.map(h => `${h}: ${headers[h].slice(0, 50)}${headers[h].length > 50 ?
|
|
426
|
-
.join(
|
|
363
|
+
.filter((h) => headers[h])
|
|
364
|
+
.map((h) => `${h}: ${headers[h].slice(0, 50)}${headers[h].length > 50 ? "..." : ""}`)
|
|
365
|
+
.join(", ");
|
|
427
366
|
if (headerInfo)
|
|
428
367
|
logMsg += ` [${headerInfo}]`;
|
|
429
368
|
if (postData)
|
|
430
|
-
logMsg += ` body: ${postData.slice(0, 100)}${postData.length > 100 ?
|
|
431
|
-
this.logger(
|
|
369
|
+
logMsg += ` body: ${postData.slice(0, 100)}${postData.length > 100 ? "..." : ""}`;
|
|
370
|
+
this.logger("browser", logMsg);
|
|
432
371
|
});
|
|
433
372
|
// Network responses with full details
|
|
434
|
-
this.onCDPEvent(
|
|
373
|
+
this.onCDPEvent("Network.responseReceived", (event) => {
|
|
435
374
|
const { response, type } = event.params;
|
|
436
375
|
const { url, status, statusText, mimeType } = response;
|
|
437
376
|
let logMsg = `[NETWORK RESPONSE] ${status} ${statusText} ${url}`;
|
|
@@ -446,52 +385,64 @@ export class CDPMonitor {
|
|
|
446
385
|
if (totalTime > 0)
|
|
447
386
|
logMsg += ` (${totalTime}ms)`;
|
|
448
387
|
}
|
|
449
|
-
this.logger(
|
|
388
|
+
this.logger("browser", logMsg);
|
|
450
389
|
});
|
|
451
390
|
// Page navigation with full context
|
|
452
|
-
this.onCDPEvent(
|
|
391
|
+
this.onCDPEvent("Page.frameNavigated", (event) => {
|
|
453
392
|
const { frame } = event.params;
|
|
454
393
|
if (frame.parentId)
|
|
455
394
|
return; // Only log main frame navigation
|
|
456
|
-
this.logger(
|
|
395
|
+
this.logger("browser", `[NAVIGATION] ${frame.url}`);
|
|
396
|
+
// Take screenshot on navigation to catch initial render
|
|
397
|
+
setTimeout(() => {
|
|
398
|
+
this.takeScreenshot("frame-navigated");
|
|
399
|
+
}, 200);
|
|
457
400
|
});
|
|
458
401
|
// Page load events for better screenshot timing
|
|
459
|
-
this.onCDPEvent(
|
|
460
|
-
this.logger(
|
|
461
|
-
this.takeScreenshot(
|
|
402
|
+
this.onCDPEvent("Page.loadEventFired", async (event) => {
|
|
403
|
+
this.logger("browser", "[PAGE] Load event fired");
|
|
404
|
+
this.takeScreenshot("page-loaded");
|
|
405
|
+
// Reinject interaction tracking on page load
|
|
406
|
+
await this.setupInteractionTracking();
|
|
462
407
|
});
|
|
463
|
-
this.onCDPEvent(
|
|
464
|
-
this.logger(
|
|
465
|
-
//
|
|
408
|
+
this.onCDPEvent("Page.domContentEventFired", async (event) => {
|
|
409
|
+
this.logger("browser", "[PAGE] DOM content loaded");
|
|
410
|
+
// Take screenshot on DOM content loaded too for earlier capture
|
|
411
|
+
this.takeScreenshot("dom-content-loaded");
|
|
412
|
+
// Reinject interaction tracking on DOM content loaded
|
|
413
|
+
await this.setupInteractionTracking();
|
|
466
414
|
});
|
|
467
415
|
// Network activity tracking for better screenshot timing
|
|
468
|
-
this.onCDPEvent(
|
|
416
|
+
this.onCDPEvent("Network.requestWillBeSent", (event) => {
|
|
469
417
|
this.pendingRequests++;
|
|
470
418
|
if (this.networkIdleTimer) {
|
|
471
419
|
clearTimeout(this.networkIdleTimer);
|
|
472
420
|
this.networkIdleTimer = null;
|
|
473
421
|
}
|
|
474
422
|
});
|
|
475
|
-
this.onCDPEvent(
|
|
423
|
+
this.onCDPEvent("Network.loadingFinished", (event) => {
|
|
476
424
|
this.pendingRequests--;
|
|
477
425
|
this.scheduleNetworkIdleScreenshot();
|
|
478
426
|
});
|
|
479
|
-
this.onCDPEvent(
|
|
427
|
+
this.onCDPEvent("Network.loadingFailed", (event) => {
|
|
480
428
|
this.pendingRequests--;
|
|
481
429
|
this.scheduleNetworkIdleScreenshot();
|
|
482
430
|
});
|
|
483
431
|
// DOM mutations for interaction context
|
|
484
|
-
this.onCDPEvent(
|
|
432
|
+
this.onCDPEvent("DOM.documentUpdated", () => {
|
|
485
433
|
// Document structure changed - useful for SPA routing
|
|
486
|
-
this.logger(
|
|
434
|
+
this.logger("browser", "[DOM] Document updated");
|
|
487
435
|
});
|
|
436
|
+
// Note: Input.dispatchMouseEvent and Input.dispatchKeyEvent are for SENDING events, not capturing them
|
|
437
|
+
// We need to rely on JavaScript injection for user input capture since CDP doesn't have
|
|
438
|
+
// direct "user input monitoring" events - it's designed for automation, not monitoring
|
|
488
439
|
// Performance metrics - disabled to reduce log noise
|
|
489
440
|
// this.onCDPEvent('Performance.metrics', (event) => {
|
|
490
441
|
// const metrics = event.params.metrics;
|
|
491
|
-
// const importantMetrics = metrics.filter((m: any) =>
|
|
442
|
+
// const importantMetrics = metrics.filter((m: any) =>
|
|
492
443
|
// ['JSHeapUsedSize', 'JSHeapTotalSize', 'Nodes', 'Documents'].includes(m.name)
|
|
493
444
|
// );
|
|
494
|
-
//
|
|
445
|
+
//
|
|
495
446
|
// if (importantMetrics.length > 0) {
|
|
496
447
|
// const metricsStr = importantMetrics
|
|
497
448
|
// .map((m: any) => `${m.name}:${Math.round(m.value)}`)
|
|
@@ -511,7 +462,7 @@ export class CDPMonitor {
|
|
|
511
462
|
method: message.method,
|
|
512
463
|
params: message.params || {},
|
|
513
464
|
timestamp: Date.now(),
|
|
514
|
-
sessionId: message.sessionId
|
|
465
|
+
sessionId: message.sessionId,
|
|
515
466
|
};
|
|
516
467
|
handler(event);
|
|
517
468
|
}
|
|
@@ -519,190 +470,174 @@ export class CDPMonitor {
|
|
|
519
470
|
}
|
|
520
471
|
async navigateToApp(port) {
|
|
521
472
|
if (!this.connection) {
|
|
522
|
-
throw new Error(
|
|
473
|
+
throw new Error("No CDP connection available");
|
|
523
474
|
}
|
|
524
475
|
this.debugLog(`Navigating to http://localhost:${port}`);
|
|
525
476
|
// Navigate to the app
|
|
526
|
-
await this.sendCDPCommand(
|
|
527
|
-
url: `http://localhost:${port}
|
|
477
|
+
await this.sendCDPCommand("Page.navigate", {
|
|
478
|
+
url: `http://localhost:${port}`,
|
|
528
479
|
});
|
|
529
|
-
this.debugLog(
|
|
530
|
-
|
|
531
|
-
|
|
480
|
+
this.debugLog("Navigation command sent successfully");
|
|
481
|
+
// Take an immediate screenshot after navigation command
|
|
482
|
+
setTimeout(() => {
|
|
483
|
+
this.takeScreenshot("navigation-immediate");
|
|
484
|
+
}, 100);
|
|
485
|
+
// Take backup screenshots with increasing delays to catch different loading states
|
|
486
|
+
setTimeout(() => {
|
|
487
|
+
this.takeScreenshot("navigation-1s");
|
|
488
|
+
}, 1000);
|
|
489
|
+
setTimeout(() => {
|
|
490
|
+
this.takeScreenshot("navigation-3s");
|
|
491
|
+
}, 3000);
|
|
492
|
+
this.debugLog("Setting up interaction tracking");
|
|
493
|
+
// Enable interaction tracking via Runtime.evaluate - inject with delays to ensure it works
|
|
532
494
|
await this.setupInteractionTracking();
|
|
533
|
-
|
|
534
|
-
|
|
495
|
+
// Also inject on DOM content loaded and page loaded events to ensure it gets set up
|
|
496
|
+
setTimeout(async () => {
|
|
497
|
+
await this.setupInteractionTracking();
|
|
498
|
+
}, 1000);
|
|
499
|
+
setTimeout(async () => {
|
|
500
|
+
await this.setupInteractionTracking();
|
|
501
|
+
}, 2000);
|
|
502
|
+
this.debugLog("Interaction tracking setup completed");
|
|
503
|
+
// Multiple screenshot triggers will ensure we catch the initial page load
|
|
535
504
|
}
|
|
536
505
|
async setupInteractionTracking() {
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
elementY: event.clientY - rect.top
|
|
557
|
-
},
|
|
558
|
-
target: {
|
|
559
|
-
selector: target.tagName.toLowerCase() +
|
|
560
|
-
(target.id ? '#' + target.id : '') +
|
|
561
|
-
(target.className ? '.' + target.className.split(' ').join('.') : ''),
|
|
562
|
-
text: target.textContent?.slice(0, 100) || null,
|
|
563
|
-
attributes: {
|
|
564
|
-
id: target.id || null,
|
|
565
|
-
className: target.className || null,
|
|
566
|
-
type: target.type || null,
|
|
567
|
-
href: target.href || null
|
|
568
|
-
},
|
|
569
|
-
bounds: {
|
|
570
|
-
x: Math.round(rect.left),
|
|
571
|
-
y: Math.round(rect.top),
|
|
572
|
-
width: Math.round(rect.width),
|
|
573
|
-
height: Math.round(rect.height)
|
|
506
|
+
try {
|
|
507
|
+
// Full interaction tracking script with element details for replay
|
|
508
|
+
const trackingScript = `
|
|
509
|
+
try {
|
|
510
|
+
if (!window.__dev3000_cdp_tracking) {
|
|
511
|
+
window.__dev3000_cdp_tracking = true;
|
|
512
|
+
|
|
513
|
+
// Helper function to generate CSS selector for element
|
|
514
|
+
function getElementSelector(el) {
|
|
515
|
+
if (!el || el === document) return 'document';
|
|
516
|
+
|
|
517
|
+
// Try ID first (most reliable)
|
|
518
|
+
if (el.id) return '#' + el.id;
|
|
519
|
+
|
|
520
|
+
// Build path with tag + classes
|
|
521
|
+
let selector = el.tagName.toLowerCase();
|
|
522
|
+
if (el.className && typeof el.className === 'string') {
|
|
523
|
+
let classes = el.className.trim().split(/\\\\s+/).filter(c => c.length > 0);
|
|
524
|
+
if (classes.length > 0) selector += '.' + classes.join('.');
|
|
574
525
|
}
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
526
|
+
|
|
527
|
+
// Add nth-child if needed to make unique
|
|
528
|
+
if (el.parentNode) {
|
|
529
|
+
let siblings = Array.from(el.parentNode.children).filter(child =>
|
|
530
|
+
child.tagName === el.tagName &&
|
|
531
|
+
child.className === el.className
|
|
532
|
+
);
|
|
533
|
+
if (siblings.length > 1) {
|
|
534
|
+
let index = siblings.indexOf(el) + 1;
|
|
535
|
+
selector += ':nth-child(' + index + ')';
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
return selector;
|
|
589
540
|
}
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
code: event.code,
|
|
605
|
-
target: {
|
|
606
|
-
selector: target.tagName.toLowerCase() +
|
|
607
|
-
(target.id ? '#' + target.id : '') +
|
|
608
|
-
(target.className ? '.' + target.className.split(' ').join('.') : ''),
|
|
609
|
-
value: target.value?.slice(0, 50) || null,
|
|
610
|
-
attributes: {
|
|
611
|
-
type: target.type || null,
|
|
612
|
-
placeholder: target.placeholder || null
|
|
541
|
+
|
|
542
|
+
// Helper to get element details for replay
|
|
543
|
+
function getElementDetails(el) {
|
|
544
|
+
let details = {
|
|
545
|
+
selector: getElementSelector(el),
|
|
546
|
+
tag: el.tagName.toLowerCase(),
|
|
547
|
+
text: el.textContent ? el.textContent.trim().substring(0, 50) : '',
|
|
548
|
+
id: el.id || '',
|
|
549
|
+
className: el.className || '',
|
|
550
|
+
name: el.name || '',
|
|
551
|
+
type: el.type || '',
|
|
552
|
+
value: el.value || ''
|
|
553
|
+
};
|
|
554
|
+
return JSON.stringify(details);
|
|
613
555
|
}
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
556
|
+
|
|
557
|
+
// Scroll coalescing variables
|
|
558
|
+
let scrollTimeout = null;
|
|
559
|
+
let lastScrollX = window.scrollX;
|
|
560
|
+
let lastScrollY = window.scrollY;
|
|
561
|
+
let scrollStartX = window.scrollX;
|
|
562
|
+
let scrollStartY = window.scrollY;
|
|
563
|
+
let scrollTarget = 'document';
|
|
564
|
+
|
|
565
|
+
// Add click tracking with element details
|
|
566
|
+
document.addEventListener('click', function(e) {
|
|
567
|
+
let details = getElementDetails(e.target);
|
|
568
|
+
console.log('[DEV3000_INTERACTION] CLICK at ' + e.clientX + ',' + e.clientY + ' on ' + details);
|
|
569
|
+
});
|
|
570
|
+
|
|
571
|
+
// Add key tracking with element details
|
|
572
|
+
document.addEventListener('keydown', function(e) {
|
|
573
|
+
let details = getElementDetails(e.target);
|
|
574
|
+
console.log('[DEV3000_INTERACTION] KEY ' + e.key + ' in ' + details);
|
|
575
|
+
});
|
|
576
|
+
|
|
577
|
+
// Add coalesced scroll tracking
|
|
578
|
+
document.addEventListener('scroll', function(e) {
|
|
579
|
+
let target = e.target === document ? 'document' : getElementSelector(e.target);
|
|
580
|
+
|
|
581
|
+
// If this is the first scroll event or different target, reset
|
|
582
|
+
if (scrollTimeout === null) {
|
|
583
|
+
scrollStartX = lastScrollX;
|
|
584
|
+
scrollStartY = lastScrollY;
|
|
585
|
+
scrollTarget = target;
|
|
586
|
+
} else {
|
|
587
|
+
clearTimeout(scrollTimeout);
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
// Update current position
|
|
591
|
+
lastScrollX = window.scrollX;
|
|
592
|
+
lastScrollY = window.scrollY;
|
|
593
|
+
|
|
594
|
+
// Set timeout to log scroll after 150ms of no scrolling
|
|
595
|
+
scrollTimeout = setTimeout(function() {
|
|
596
|
+
console.log('[DEV3000_INTERACTION] SCROLL from ' + scrollStartX + ',' + scrollStartY + ' to ' + lastScrollX + ',' + lastScrollY + ' in ' + target);
|
|
597
|
+
scrollTimeout = null;
|
|
598
|
+
}, 150);
|
|
599
|
+
});
|
|
620
600
|
}
|
|
621
|
-
}
|
|
622
|
-
|
|
623
|
-
// Only log special keys and form interactions
|
|
624
|
-
if (event.key.length > 1 || target.tagName === 'INPUT' || target.tagName === 'TEXTAREA') {
|
|
625
|
-
console.log('[DEV3000_INTERACTION] ' + JSON.stringify(interactionData));
|
|
601
|
+
} catch (err) {
|
|
602
|
+
console.log('[DEV3000_INTERACTION] ERROR: ' + err.message);
|
|
626
603
|
}
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
604
|
+
`;
|
|
605
|
+
this.debugLog("About to inject tracking script...");
|
|
606
|
+
// Validate JavaScript syntax before injection
|
|
607
|
+
try {
|
|
608
|
+
new Function(trackingScript);
|
|
609
|
+
this.debugLog("JavaScript syntax validation passed");
|
|
610
|
+
}
|
|
611
|
+
catch (syntaxError) {
|
|
612
|
+
const errorMessage = syntaxError instanceof Error ? syntaxError.message : String(syntaxError);
|
|
613
|
+
this.debugLog(`JavaScript syntax error detected: ${errorMessage}`);
|
|
614
|
+
this.logger("browser", `[CDP ERROR] Tracking script syntax error: ${errorMessage}`);
|
|
615
|
+
throw new Error(`Invalid tracking script syntax: ${errorMessage}`);
|
|
616
|
+
}
|
|
617
|
+
// First try a simple test
|
|
618
|
+
const simpleTest = `console.log('DEV3000_TEST: Simple script execution working!');`;
|
|
619
|
+
const testResult = await this.sendCDPCommand("Runtime.evaluate", {
|
|
620
|
+
expression: simpleTest,
|
|
621
|
+
includeCommandLineAPI: false,
|
|
622
|
+
});
|
|
623
|
+
this.debugLog(`Simple test result: ${JSON.stringify(testResult)}`);
|
|
624
|
+
this.logger("browser", `[DEBUG] Simple test result: ${testResult.result?.value || "undefined"}`);
|
|
625
|
+
const result = await this.sendCDPCommand("Runtime.evaluate", {
|
|
626
|
+
expression: trackingScript,
|
|
627
|
+
includeCommandLineAPI: false,
|
|
628
|
+
});
|
|
629
|
+
this.debugLog(`Interaction tracking script injected. Result: ${JSON.stringify(result)}`);
|
|
630
|
+
this.logger("browser", `[DEBUG] Script injection result: ${result.result?.value || "undefined"}`);
|
|
631
|
+
// Log any errors from the script injection
|
|
632
|
+
if (result.exceptionDetails) {
|
|
633
|
+
this.debugLog(`Script injection exception: ${JSON.stringify(result.exceptionDetails)}`);
|
|
634
|
+
this.logger("browser", `[DEBUG] Script injection exception: ${result.exceptionDetails.exception?.description || "Unknown error"}`);
|
|
635
|
+
}
|
|
640
636
|
}
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
scrollTimeout = setTimeout(() => {
|
|
645
|
-
const endPos = { x: window.scrollX, y: window.scrollY };
|
|
646
|
-
const distance = Math.round(Math.sqrt(
|
|
647
|
-
Math.pow(endPos.x - lastScrollPos.x, 2) +
|
|
648
|
-
Math.pow(endPos.y - lastScrollPos.y, 2)
|
|
649
|
-
));
|
|
650
|
-
|
|
651
|
-
if (distance > 10) {
|
|
652
|
-
const direction = endPos.y > lastScrollPos.y ? 'DOWN' :
|
|
653
|
-
endPos.y < lastScrollPos.y ? 'UP' :
|
|
654
|
-
endPos.x > lastScrollPos.x ? 'RIGHT' : 'LEFT';
|
|
655
|
-
|
|
656
|
-
const interactionData = {
|
|
657
|
-
type: 'SCROLL',
|
|
658
|
-
timestamp: now,
|
|
659
|
-
direction,
|
|
660
|
-
distance,
|
|
661
|
-
duration: now - scrollStartTime,
|
|
662
|
-
from: lastScrollPos,
|
|
663
|
-
to: endPos,
|
|
664
|
-
viewport: {
|
|
665
|
-
width: window.innerWidth,
|
|
666
|
-
height: window.innerHeight
|
|
667
|
-
}
|
|
668
|
-
};
|
|
669
|
-
|
|
670
|
-
console.log('[DEV3000_INTERACTION] ' + JSON.stringify(interactionData));
|
|
671
|
-
lastScrollPos = endPos;
|
|
672
|
-
}
|
|
673
|
-
}, 150); // Wait for scroll to finish
|
|
674
|
-
}, true);
|
|
675
|
-
|
|
676
|
-
// Track form submissions
|
|
677
|
-
document.addEventListener('submit', (event) => {
|
|
678
|
-
const form = event.target;
|
|
679
|
-
const formData = new FormData(form);
|
|
680
|
-
const fields = {};
|
|
681
|
-
|
|
682
|
-
for (const [key, value] of formData.entries()) {
|
|
683
|
-
// Don't log actual values, just field names for privacy
|
|
684
|
-
fields[key] = typeof value === 'string' ? \`<\${value.length} chars>\` : '<file>';
|
|
637
|
+
catch (error) {
|
|
638
|
+
this.debugLog(`Failed to inject interaction tracking: ${error}`);
|
|
639
|
+
this.logger("browser", `[CDP ERROR] Interaction tracking failed: ${error}`);
|
|
685
640
|
}
|
|
686
|
-
|
|
687
|
-
const interactionData = {
|
|
688
|
-
type: 'FORM_SUBMIT',
|
|
689
|
-
timestamp: Date.now(),
|
|
690
|
-
target: {
|
|
691
|
-
action: form.action || window.location.href,
|
|
692
|
-
method: form.method || 'GET',
|
|
693
|
-
fields: Object.keys(fields)
|
|
694
|
-
}
|
|
695
|
-
};
|
|
696
|
-
|
|
697
|
-
console.log('[DEV3000_INTERACTION] ' + JSON.stringify(interactionData));
|
|
698
|
-
}, true);
|
|
699
|
-
|
|
700
|
-
console.log('[DEV3000_INTERACTION] CDP tracking initialized');
|
|
701
|
-
`;
|
|
702
|
-
await this.sendCDPCommand('Runtime.evaluate', {
|
|
703
|
-
expression: trackingScript,
|
|
704
|
-
includeCommandLineAPI: false
|
|
705
|
-
});
|
|
706
641
|
}
|
|
707
642
|
scheduleNetworkIdleScreenshot() {
|
|
708
643
|
// Only schedule if we have 0 pending requests
|
|
@@ -712,80 +647,80 @@ export class CDPMonitor {
|
|
|
712
647
|
}
|
|
713
648
|
// Wait 500ms of network idle before taking screenshot
|
|
714
649
|
this.networkIdleTimer = setTimeout(() => {
|
|
715
|
-
this.takeScreenshot(
|
|
650
|
+
this.takeScreenshot("network-idle");
|
|
716
651
|
this.networkIdleTimer = null;
|
|
717
652
|
}, 500);
|
|
718
653
|
}
|
|
719
654
|
}
|
|
720
655
|
async takeScreenshot(event) {
|
|
721
656
|
try {
|
|
722
|
-
const result = await this.sendCDPCommand(
|
|
723
|
-
format:
|
|
657
|
+
const result = await this.sendCDPCommand("Page.captureScreenshot", {
|
|
658
|
+
format: "png",
|
|
724
659
|
quality: 80,
|
|
725
660
|
clip: undefined, // Full viewport
|
|
726
|
-
fromSurface: true
|
|
661
|
+
fromSurface: true,
|
|
727
662
|
});
|
|
728
|
-
const timestamp = new Date().toISOString().replace(/[:.]/g,
|
|
663
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
729
664
|
const filename = `${timestamp}-${event}.png`;
|
|
730
665
|
const screenshotPath = join(this.screenshotDir, filename);
|
|
731
666
|
// Save the base64 image
|
|
732
|
-
const buffer = Buffer.from(result.data,
|
|
667
|
+
const buffer = Buffer.from(result.data, "base64");
|
|
733
668
|
writeFileSync(screenshotPath, buffer);
|
|
734
669
|
// Log screenshot with proper format that dev3000 expects
|
|
735
|
-
this.logger(
|
|
670
|
+
this.logger("browser", `[SCREENSHOT] ${filename}`);
|
|
736
671
|
return filename;
|
|
737
672
|
}
|
|
738
673
|
catch (error) {
|
|
739
|
-
this.logger(
|
|
674
|
+
this.logger("browser", `[CDP ERROR] Screenshot failed: ${error}`);
|
|
740
675
|
return null;
|
|
741
676
|
}
|
|
742
677
|
}
|
|
743
678
|
// Enhanced replay functionality using CDP
|
|
744
679
|
async executeInteraction(interaction) {
|
|
745
680
|
if (!this.connection) {
|
|
746
|
-
throw new Error(
|
|
681
|
+
throw new Error("No CDP connection available");
|
|
747
682
|
}
|
|
748
683
|
try {
|
|
749
684
|
switch (interaction.type) {
|
|
750
|
-
case
|
|
751
|
-
await this.sendCDPCommand(
|
|
752
|
-
type:
|
|
685
|
+
case "CLICK":
|
|
686
|
+
await this.sendCDPCommand("Input.dispatchMouseEvent", {
|
|
687
|
+
type: "mousePressed",
|
|
753
688
|
x: interaction.coordinates.x,
|
|
754
689
|
y: interaction.coordinates.y,
|
|
755
|
-
button:
|
|
756
|
-
clickCount: 1
|
|
690
|
+
button: "left",
|
|
691
|
+
clickCount: 1,
|
|
757
692
|
});
|
|
758
|
-
await this.sendCDPCommand(
|
|
759
|
-
type:
|
|
693
|
+
await this.sendCDPCommand("Input.dispatchMouseEvent", {
|
|
694
|
+
type: "mouseReleased",
|
|
760
695
|
x: interaction.coordinates.x,
|
|
761
696
|
y: interaction.coordinates.y,
|
|
762
|
-
button:
|
|
763
|
-
clickCount: 1
|
|
697
|
+
button: "left",
|
|
698
|
+
clickCount: 1,
|
|
764
699
|
});
|
|
765
700
|
break;
|
|
766
|
-
case
|
|
767
|
-
await this.sendCDPCommand(
|
|
768
|
-
type:
|
|
701
|
+
case "KEYDOWN":
|
|
702
|
+
await this.sendCDPCommand("Input.dispatchKeyEvent", {
|
|
703
|
+
type: "keyDown",
|
|
769
704
|
key: interaction.key,
|
|
770
705
|
code: interaction.code,
|
|
771
|
-
...interaction.modifiers
|
|
706
|
+
...interaction.modifiers,
|
|
772
707
|
});
|
|
773
708
|
break;
|
|
774
|
-
case
|
|
775
|
-
await this.sendCDPCommand(
|
|
776
|
-
type:
|
|
709
|
+
case "SCROLL":
|
|
710
|
+
await this.sendCDPCommand("Input.dispatchMouseEvent", {
|
|
711
|
+
type: "mouseWheel",
|
|
777
712
|
x: interaction.to.x,
|
|
778
713
|
y: interaction.to.y,
|
|
779
714
|
deltaX: interaction.to.x - interaction.from.x,
|
|
780
|
-
deltaY: interaction.to.y - interaction.from.y
|
|
715
|
+
deltaY: interaction.to.y - interaction.from.y,
|
|
781
716
|
});
|
|
782
717
|
break;
|
|
783
718
|
default:
|
|
784
|
-
this.logger(
|
|
719
|
+
this.logger("browser", `[REPLAY] Unknown interaction type: ${interaction.type}`);
|
|
785
720
|
}
|
|
786
721
|
}
|
|
787
722
|
catch (error) {
|
|
788
|
-
this.logger(
|
|
723
|
+
this.logger("browser", `[REPLAY ERROR] Failed to execute ${interaction.type}: ${error}`);
|
|
789
724
|
}
|
|
790
725
|
}
|
|
791
726
|
async shutdown() {
|
|
@@ -797,11 +732,11 @@ export class CDPMonitor {
|
|
|
797
732
|
}
|
|
798
733
|
// Close browser
|
|
799
734
|
if (this.browser) {
|
|
800
|
-
this.browser.kill(
|
|
735
|
+
this.browser.kill("SIGTERM");
|
|
801
736
|
// Force kill after 2 seconds if not closed
|
|
802
737
|
setTimeout(() => {
|
|
803
738
|
if (this.browser) {
|
|
804
|
-
this.browser.kill(
|
|
739
|
+
this.browser.kill("SIGKILL");
|
|
805
740
|
}
|
|
806
741
|
}, 2000);
|
|
807
742
|
this.browser = null;
|