@dynamicu/chromedebug-mcp 2.2.0
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/CLAUDE.md +344 -0
- package/LICENSE +21 -0
- package/README.md +250 -0
- package/chrome-extension/README.md +41 -0
- package/chrome-extension/background.js +3917 -0
- package/chrome-extension/chrome-session-manager.js +706 -0
- package/chrome-extension/content.css +181 -0
- package/chrome-extension/content.js +3022 -0
- package/chrome-extension/data-buffer.js +435 -0
- package/chrome-extension/dom-tracker.js +411 -0
- package/chrome-extension/extension-config.js +78 -0
- package/chrome-extension/firebase-client.js +278 -0
- package/chrome-extension/firebase-config.js +32 -0
- package/chrome-extension/firebase-config.module.js +22 -0
- package/chrome-extension/firebase-config.module.template.js +27 -0
- package/chrome-extension/firebase-config.template.js +36 -0
- package/chrome-extension/frame-capture.js +407 -0
- package/chrome-extension/icon128.png +1 -0
- package/chrome-extension/icon16.png +1 -0
- package/chrome-extension/icon48.png +1 -0
- package/chrome-extension/license-helper.js +181 -0
- package/chrome-extension/logger.js +23 -0
- package/chrome-extension/manifest.json +73 -0
- package/chrome-extension/network-tracker.js +510 -0
- package/chrome-extension/offscreen.html +10 -0
- package/chrome-extension/options.html +203 -0
- package/chrome-extension/options.js +282 -0
- package/chrome-extension/pako.min.js +2 -0
- package/chrome-extension/performance-monitor.js +533 -0
- package/chrome-extension/pii-redactor.js +405 -0
- package/chrome-extension/popup.html +532 -0
- package/chrome-extension/popup.js +2446 -0
- package/chrome-extension/upload-manager.js +323 -0
- package/chrome-extension/web-vitals.iife.js +1 -0
- package/config/api-keys.json +11 -0
- package/config/chrome-pilot-config.json +45 -0
- package/package.json +126 -0
- package/scripts/cleanup-processes.js +109 -0
- package/scripts/config-manager.js +280 -0
- package/scripts/generate-extension-config.js +53 -0
- package/scripts/setup-security.js +64 -0
- package/src/capture/architecture.js +426 -0
- package/src/capture/error-handling-tests.md +38 -0
- package/src/capture/error-handling-types.ts +360 -0
- package/src/capture/index.js +508 -0
- package/src/capture/interfaces.js +625 -0
- package/src/capture/memory-manager.js +713 -0
- package/src/capture/types.js +342 -0
- package/src/chrome-controller.js +2658 -0
- package/src/cli.js +19 -0
- package/src/config-loader.js +303 -0
- package/src/database.js +2178 -0
- package/src/firebase-license-manager.js +462 -0
- package/src/firebase-privacy-guard.js +397 -0
- package/src/http-server.js +1516 -0
- package/src/index-direct.js +157 -0
- package/src/index-modular.js +219 -0
- package/src/index-monolithic-backup.js +2230 -0
- package/src/index.js +305 -0
- package/src/legacy/chrome-controller-old.js +1406 -0
- package/src/legacy/index-express.js +625 -0
- package/src/legacy/index-old.js +977 -0
- package/src/legacy/routes.js +260 -0
- package/src/legacy/shared-storage.js +101 -0
- package/src/logger.js +10 -0
- package/src/mcp/handlers/chrome-tool-handler.js +306 -0
- package/src/mcp/handlers/element-tool-handler.js +51 -0
- package/src/mcp/handlers/frame-tool-handler.js +957 -0
- package/src/mcp/handlers/request-handler.js +104 -0
- package/src/mcp/handlers/workflow-tool-handler.js +636 -0
- package/src/mcp/server.js +68 -0
- package/src/mcp/tools/index.js +701 -0
- package/src/middleware/auth.js +371 -0
- package/src/middleware/security.js +267 -0
- package/src/port-discovery.js +258 -0
- package/src/routes/admin.js +182 -0
- package/src/services/browser-daemon.js +494 -0
- package/src/services/chrome-service.js +375 -0
- package/src/services/failover-manager.js +412 -0
- package/src/services/git-safety-service.js +675 -0
- package/src/services/heartbeat-manager.js +200 -0
- package/src/services/http-client.js +195 -0
- package/src/services/process-manager.js +318 -0
- package/src/services/process-tracker.js +574 -0
- package/src/services/profile-manager.js +449 -0
- package/src/services/project-manager.js +415 -0
- package/src/services/session-manager.js +497 -0
- package/src/services/session-registry.js +491 -0
- package/src/services/unified-session-manager.js +678 -0
- package/src/shared-storage-old.js +267 -0
- package/src/standalone-server.js +53 -0
- package/src/utils/extension-path.js +145 -0
- package/src/utils.js +187 -0
- package/src/validation/log-transformer.js +125 -0
- package/src/validation/schemas.js +391 -0
|
@@ -0,0 +1,510 @@
|
|
|
1
|
+
// Network Tracker for Chrome Debug Full Data Recording
|
|
2
|
+
// Wraps fetch and XMLHttpRequest to track all network activity
|
|
3
|
+
|
|
4
|
+
class NetworkTracker {
|
|
5
|
+
constructor() {
|
|
6
|
+
this.isTracking = false;
|
|
7
|
+
this.originalFetch = null;
|
|
8
|
+
this.originalXHROpen = null;
|
|
9
|
+
this.originalXHRSend = null;
|
|
10
|
+
this.requestMap = new Map(); // Track ongoing requests
|
|
11
|
+
this.eventBuffer = [];
|
|
12
|
+
this.recordingId = null;
|
|
13
|
+
|
|
14
|
+
// Configuration
|
|
15
|
+
this.captureRequestBody = true;
|
|
16
|
+
this.captureResponseBody = true;
|
|
17
|
+
this.maxBodySize = 10 * 1024; // 10KB limit for request/response bodies
|
|
18
|
+
this.bufferFlushDelay = 2000; // Flush every 2 seconds
|
|
19
|
+
this.flushTimer = null;
|
|
20
|
+
|
|
21
|
+
// Callbacks and dependencies
|
|
22
|
+
this.onDataReady = null;
|
|
23
|
+
this.piiRedactor = null;
|
|
24
|
+
this.performanceMonitor = null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
init(options = {}) {
|
|
28
|
+
this.recordingId = options.recordingId || 'default';
|
|
29
|
+
this.onDataReady = options.onDataReady;
|
|
30
|
+
this.piiRedactor = options.piiRedactor;
|
|
31
|
+
this.performanceMonitor = options.performanceMonitor;
|
|
32
|
+
|
|
33
|
+
// Override settings
|
|
34
|
+
if (options.captureRequestBody !== undefined) this.captureRequestBody = options.captureRequestBody;
|
|
35
|
+
if (options.captureResponseBody !== undefined) this.captureResponseBody = options.captureResponseBody;
|
|
36
|
+
if (options.maxBodySize) this.maxBodySize = options.maxBodySize;
|
|
37
|
+
if (options.bufferFlushDelay) this.bufferFlushDelay = options.bufferFlushDelay;
|
|
38
|
+
|
|
39
|
+
console.log('[NetworkTracker] Initialized with options:', {
|
|
40
|
+
recordingId: this.recordingId,
|
|
41
|
+
captureRequestBody: this.captureRequestBody,
|
|
42
|
+
captureResponseBody: this.captureResponseBody,
|
|
43
|
+
maxBodySize: this.maxBodySize
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
startTracking() {
|
|
48
|
+
if (this.isTracking) {
|
|
49
|
+
console.warn('[NetworkTracker] Already tracking network requests');
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
this.wrapFetch();
|
|
55
|
+
this.wrapXMLHttpRequest();
|
|
56
|
+
|
|
57
|
+
this.isTracking = true;
|
|
58
|
+
this.startBufferFlusher();
|
|
59
|
+
|
|
60
|
+
console.log('[NetworkTracker] Started network request tracking');
|
|
61
|
+
|
|
62
|
+
// Record performance event
|
|
63
|
+
if (this.performanceMonitor) {
|
|
64
|
+
this.performanceMonitor.recordEvent('network_tracking_started', {
|
|
65
|
+
timestamp: Date.now()
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
} catch (error) {
|
|
70
|
+
console.error('[NetworkTracker] Failed to start tracking:', error);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
stopTracking() {
|
|
75
|
+
if (!this.isTracking) return;
|
|
76
|
+
|
|
77
|
+
this.restoreFetch();
|
|
78
|
+
this.restoreXMLHttpRequest();
|
|
79
|
+
|
|
80
|
+
this.stopBufferFlusher();
|
|
81
|
+
this.flushBuffer(); // Flush any remaining data
|
|
82
|
+
|
|
83
|
+
this.isTracking = false;
|
|
84
|
+
console.log('[NetworkTracker] Stopped network request tracking');
|
|
85
|
+
|
|
86
|
+
// Record performance event
|
|
87
|
+
if (this.performanceMonitor) {
|
|
88
|
+
this.performanceMonitor.recordEvent('network_tracking_stopped', {
|
|
89
|
+
timestamp: Date.now(),
|
|
90
|
+
totalRequests: this.requestMap.size
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
wrapFetch() {
|
|
96
|
+
this.originalFetch = window.fetch;
|
|
97
|
+
|
|
98
|
+
window.fetch = async (input, init = {}) => {
|
|
99
|
+
const requestId = this.generateRequestId();
|
|
100
|
+
const startTime = Date.now();
|
|
101
|
+
|
|
102
|
+
let url, method, headers, body;
|
|
103
|
+
|
|
104
|
+
// Parse input (URL or Request object)
|
|
105
|
+
if (typeof input === 'string') {
|
|
106
|
+
url = input;
|
|
107
|
+
method = init.method || 'GET';
|
|
108
|
+
headers = init.headers || {};
|
|
109
|
+
body = init.body;
|
|
110
|
+
} else if (input instanceof Request) {
|
|
111
|
+
url = input.url;
|
|
112
|
+
method = input.method;
|
|
113
|
+
headers = Object.fromEntries(input.headers.entries());
|
|
114
|
+
body = init.body || input.body;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Create request data
|
|
118
|
+
const requestData = {
|
|
119
|
+
type: 'network_request',
|
|
120
|
+
timestamp: startTime,
|
|
121
|
+
recording_id: this.recordingId,
|
|
122
|
+
request_id: requestId,
|
|
123
|
+
method: method,
|
|
124
|
+
url: url,
|
|
125
|
+
headers: this.processHeaders(headers),
|
|
126
|
+
body: await this.processRequestBody(body),
|
|
127
|
+
fetch_type: 'fetch'
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
// Store request for later correlation with response
|
|
131
|
+
this.requestMap.set(requestId, {
|
|
132
|
+
startTime,
|
|
133
|
+
requestData
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
try {
|
|
137
|
+
// Make the actual request
|
|
138
|
+
const response = await this.originalFetch(input, init);
|
|
139
|
+
|
|
140
|
+
// Process response
|
|
141
|
+
await this.handleFetchResponse(requestId, response.clone());
|
|
142
|
+
|
|
143
|
+
return response;
|
|
144
|
+
} catch (error) {
|
|
145
|
+
// Handle fetch error
|
|
146
|
+
this.handleRequestError(requestId, error);
|
|
147
|
+
throw error;
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
wrapXMLHttpRequest() {
|
|
153
|
+
this.originalXHROpen = XMLHttpRequest.prototype.open;
|
|
154
|
+
this.originalXHRSend = XMLHttpRequest.prototype.send;
|
|
155
|
+
|
|
156
|
+
XMLHttpRequest.prototype.open = function(method, url, async, user, password) {
|
|
157
|
+
// Store request details
|
|
158
|
+
this._chromePilotRequestId = this._chromePilotRequestId || generateRequestId();
|
|
159
|
+
this._chromePilotMethod = method;
|
|
160
|
+
this._chromePilotUrl = url;
|
|
161
|
+
this._chromePilotStartTime = Date.now();
|
|
162
|
+
|
|
163
|
+
return this.originalXHROpen.call(this, method, url, async, user, password);
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
XMLHttpRequest.prototype.send = function(body) {
|
|
167
|
+
const requestId = this._chromePilotRequestId;
|
|
168
|
+
const tracker = window.networkTracker; // Reference to this instance
|
|
169
|
+
|
|
170
|
+
if (requestId && tracker) {
|
|
171
|
+
// Create request data
|
|
172
|
+
const requestData = {
|
|
173
|
+
type: 'network_request',
|
|
174
|
+
timestamp: this._chromePilotStartTime,
|
|
175
|
+
recording_id: tracker.recordingId,
|
|
176
|
+
request_id: requestId,
|
|
177
|
+
method: this._chromePilotMethod,
|
|
178
|
+
url: this._chromePilotUrl,
|
|
179
|
+
headers: tracker.getXHRHeaders(this),
|
|
180
|
+
body: tracker.processRequestBodySync(body),
|
|
181
|
+
fetch_type: 'xhr'
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
// Store request
|
|
185
|
+
tracker.requestMap.set(requestId, {
|
|
186
|
+
startTime: this._chromePilotStartTime,
|
|
187
|
+
requestData,
|
|
188
|
+
xhr: this
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
// Add event listeners for response
|
|
192
|
+
this.addEventListener('readystatechange', function() {
|
|
193
|
+
if (this.readyState === XMLHttpRequest.DONE) {
|
|
194
|
+
tracker.handleXHRResponse(requestId, this);
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
this.addEventListener('error', function() {
|
|
199
|
+
tracker.handleRequestError(requestId, new Error('XHR Error'));
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return tracker.originalXHRSend.call(this, body);
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
// Store reference for XHR access
|
|
207
|
+
window.networkTracker = this;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
async handleFetchResponse(requestId, response) {
|
|
211
|
+
const request = this.requestMap.get(requestId);
|
|
212
|
+
if (!request) return;
|
|
213
|
+
|
|
214
|
+
const endTime = Date.now();
|
|
215
|
+
const duration = endTime - request.startTime;
|
|
216
|
+
|
|
217
|
+
// Get response headers
|
|
218
|
+
const responseHeaders = Object.fromEntries(response.headers.entries());
|
|
219
|
+
|
|
220
|
+
// Get response body if configured
|
|
221
|
+
let responseBody = null;
|
|
222
|
+
if (this.captureResponseBody) {
|
|
223
|
+
try {
|
|
224
|
+
const contentType = response.headers.get('content-type') || '';
|
|
225
|
+
|
|
226
|
+
if (contentType.includes('application/json') ||
|
|
227
|
+
contentType.includes('text/') ||
|
|
228
|
+
contentType.includes('application/xml')) {
|
|
229
|
+
|
|
230
|
+
const text = await response.text();
|
|
231
|
+
responseBody = this.truncateData(text, this.maxBodySize);
|
|
232
|
+
}
|
|
233
|
+
} catch (error) {
|
|
234
|
+
console.warn('[NetworkTracker] Failed to read response body:', error);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Create complete request record
|
|
239
|
+
const networkEvent = {
|
|
240
|
+
...request.requestData,
|
|
241
|
+
response_status: response.status,
|
|
242
|
+
response_headers: this.processHeaders(responseHeaders),
|
|
243
|
+
response_body: responseBody,
|
|
244
|
+
duration_ms: duration,
|
|
245
|
+
completed_at: endTime
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
// Apply PII redaction
|
|
249
|
+
if (this.piiRedactor) {
|
|
250
|
+
networkEvent = this.piiRedactor.redactData(networkEvent, 'network_request');
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
this.eventBuffer.push(networkEvent);
|
|
254
|
+
this.requestMap.delete(requestId);
|
|
255
|
+
|
|
256
|
+
// Record performance metrics
|
|
257
|
+
if (this.performanceMonitor) {
|
|
258
|
+
this.performanceMonitor.recordEvent('network_request', {
|
|
259
|
+
method: networkEvent.method,
|
|
260
|
+
status: response.status,
|
|
261
|
+
duration: duration
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
handleXHRResponse(requestId, xhr) {
|
|
267
|
+
const request = this.requestMap.get(requestId);
|
|
268
|
+
if (!request) return;
|
|
269
|
+
|
|
270
|
+
const endTime = Date.now();
|
|
271
|
+
const duration = endTime - request.startTime;
|
|
272
|
+
|
|
273
|
+
// Get response data
|
|
274
|
+
let responseBody = null;
|
|
275
|
+
if (this.captureResponseBody && xhr.responseText) {
|
|
276
|
+
responseBody = this.truncateData(xhr.responseText, this.maxBodySize);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Create complete request record
|
|
280
|
+
const networkEvent = {
|
|
281
|
+
...request.requestData,
|
|
282
|
+
response_status: xhr.status,
|
|
283
|
+
response_headers: this.getXHRResponseHeaders(xhr),
|
|
284
|
+
response_body: responseBody,
|
|
285
|
+
duration_ms: duration,
|
|
286
|
+
completed_at: endTime
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
// Apply PII redaction
|
|
290
|
+
if (this.piiRedactor) {
|
|
291
|
+
networkEvent = this.piiRedactor.redactData(networkEvent, 'network_request');
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
this.eventBuffer.push(networkEvent);
|
|
295
|
+
this.requestMap.delete(requestId);
|
|
296
|
+
|
|
297
|
+
// Record performance metrics
|
|
298
|
+
if (this.performanceMonitor) {
|
|
299
|
+
this.performanceMonitor.recordEvent('network_request', {
|
|
300
|
+
method: networkEvent.method,
|
|
301
|
+
status: xhr.status,
|
|
302
|
+
duration: duration
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
handleRequestError(requestId, error) {
|
|
308
|
+
const request = this.requestMap.get(requestId);
|
|
309
|
+
if (!request) return;
|
|
310
|
+
|
|
311
|
+
const endTime = Date.now();
|
|
312
|
+
const duration = endTime - request.startTime;
|
|
313
|
+
|
|
314
|
+
const networkEvent = {
|
|
315
|
+
...request.requestData,
|
|
316
|
+
response_status: 0,
|
|
317
|
+
error: error.message,
|
|
318
|
+
duration_ms: duration,
|
|
319
|
+
completed_at: endTime
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
this.eventBuffer.push(networkEvent);
|
|
323
|
+
this.requestMap.delete(requestId);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
async processRequestBody(body) {
|
|
327
|
+
if (!this.captureRequestBody || !body) return null;
|
|
328
|
+
|
|
329
|
+
try {
|
|
330
|
+
if (typeof body === 'string') {
|
|
331
|
+
return this.truncateData(body, this.maxBodySize);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
if (body instanceof FormData) {
|
|
335
|
+
const formObject = {};
|
|
336
|
+
for (const [key, value] of body.entries()) {
|
|
337
|
+
formObject[key] = typeof value === 'string' ?
|
|
338
|
+
this.truncateData(value, this.maxBodySize) :
|
|
339
|
+
'[File]';
|
|
340
|
+
}
|
|
341
|
+
return formObject;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
if (body instanceof URLSearchParams) {
|
|
345
|
+
return Object.fromEntries(body.entries());
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
if (body instanceof ArrayBuffer || body instanceof Uint8Array) {
|
|
349
|
+
return `[Binary Data: ${body.byteLength} bytes]`;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// Try to stringify other objects
|
|
353
|
+
const stringified = JSON.stringify(body);
|
|
354
|
+
return this.truncateData(stringified, this.maxBodySize);
|
|
355
|
+
|
|
356
|
+
} catch (error) {
|
|
357
|
+
return '[Unable to serialize request body]';
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
processRequestBodySync(body) {
|
|
362
|
+
// Synchronous version for XHR
|
|
363
|
+
if (!this.captureRequestBody || !body) return null;
|
|
364
|
+
|
|
365
|
+
if (typeof body === 'string') {
|
|
366
|
+
return this.truncateData(body, this.maxBodySize);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
try {
|
|
370
|
+
const stringified = JSON.stringify(body);
|
|
371
|
+
return this.truncateData(stringified, this.maxBodySize);
|
|
372
|
+
} catch (error) {
|
|
373
|
+
return '[Unable to serialize request body]';
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
processHeaders(headers) {
|
|
378
|
+
if (!headers) return {};
|
|
379
|
+
|
|
380
|
+
const processed = {};
|
|
381
|
+
|
|
382
|
+
if (headers instanceof Headers) {
|
|
383
|
+
for (const [key, value] of headers.entries()) {
|
|
384
|
+
processed[key] = value;
|
|
385
|
+
}
|
|
386
|
+
} else if (typeof headers === 'object') {
|
|
387
|
+
Object.assign(processed, headers);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
return processed;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
getXHRHeaders(xhr) {
|
|
394
|
+
// XHR doesn't provide easy access to request headers
|
|
395
|
+
// This is a limitation of the XMLHttpRequest API
|
|
396
|
+
return {};
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
getXHRResponseHeaders(xhr) {
|
|
400
|
+
const headers = {};
|
|
401
|
+
const headerString = xhr.getAllResponseHeaders();
|
|
402
|
+
|
|
403
|
+
if (headerString) {
|
|
404
|
+
const lines = headerString.split('\r\n');
|
|
405
|
+
for (const line of lines) {
|
|
406
|
+
const parts = line.split(': ');
|
|
407
|
+
if (parts.length === 2) {
|
|
408
|
+
headers[parts[0].toLowerCase()] = parts[1];
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
return headers;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
truncateData(data, maxSize) {
|
|
417
|
+
if (!data || typeof data !== 'string') return data;
|
|
418
|
+
|
|
419
|
+
if (data.length <= maxSize) return data;
|
|
420
|
+
|
|
421
|
+
return data.substring(0, maxSize) + `... [Truncated: ${data.length} total chars]`;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
generateRequestId() {
|
|
425
|
+
return `req_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
restoreFetch() {
|
|
429
|
+
if (this.originalFetch) {
|
|
430
|
+
window.fetch = this.originalFetch;
|
|
431
|
+
this.originalFetch = null;
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
restoreXMLHttpRequest() {
|
|
436
|
+
if (this.originalXHROpen) {
|
|
437
|
+
XMLHttpRequest.prototype.open = this.originalXHROpen;
|
|
438
|
+
this.originalXHROpen = null;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
if (this.originalXHRSend) {
|
|
442
|
+
XMLHttpRequest.prototype.send = this.originalXHRSend;
|
|
443
|
+
this.originalXHRSend = null;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// Clean up global reference
|
|
447
|
+
if (window.networkTracker === this) {
|
|
448
|
+
delete window.networkTracker;
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
startBufferFlusher() {
|
|
453
|
+
if (this.flushTimer) return;
|
|
454
|
+
|
|
455
|
+
this.flushTimer = setInterval(() => {
|
|
456
|
+
if (this.eventBuffer.length > 0) {
|
|
457
|
+
this.flushBuffer();
|
|
458
|
+
}
|
|
459
|
+
}, this.bufferFlushDelay);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
stopBufferFlusher() {
|
|
463
|
+
if (this.flushTimer) {
|
|
464
|
+
clearInterval(this.flushTimer);
|
|
465
|
+
this.flushTimer = null;
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
flushBuffer() {
|
|
470
|
+
if (this.eventBuffer.length === 0) return;
|
|
471
|
+
|
|
472
|
+
const events = [...this.eventBuffer];
|
|
473
|
+
this.eventBuffer = [];
|
|
474
|
+
|
|
475
|
+
console.log(`[NetworkTracker] Flushing ${events.length} network requests`);
|
|
476
|
+
|
|
477
|
+
// Send data via callback
|
|
478
|
+
if (this.onDataReady) {
|
|
479
|
+
this.onDataReady(events);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
getStats() {
|
|
484
|
+
return {
|
|
485
|
+
isTracking: this.isTracking,
|
|
486
|
+
pendingRequests: this.requestMap.size,
|
|
487
|
+
bufferSize: this.eventBuffer.length,
|
|
488
|
+
recordingId: this.recordingId
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
destroy() {
|
|
493
|
+
this.stopTracking();
|
|
494
|
+
this.eventBuffer = [];
|
|
495
|
+
this.requestMap.clear();
|
|
496
|
+
this.onDataReady = null;
|
|
497
|
+
this.piiRedactor = null;
|
|
498
|
+
this.performanceMonitor = null;
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
// Helper function for XHR (global scope)
|
|
503
|
+
function generateRequestId() {
|
|
504
|
+
return `req_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
// Export for use in content script
|
|
508
|
+
if (typeof module !== 'undefined' && module.exports) {
|
|
509
|
+
module.exports = NetworkTracker;
|
|
510
|
+
}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<title>Chrome Debug Settings</title>
|
|
5
|
+
<style>
|
|
6
|
+
body {
|
|
7
|
+
font-family: system-ui, -apple-system, sans-serif;
|
|
8
|
+
max-width: 600px;
|
|
9
|
+
margin: 20px auto;
|
|
10
|
+
padding: 20px;
|
|
11
|
+
background: #f5f5f5;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.container {
|
|
15
|
+
background: white;
|
|
16
|
+
border-radius: 8px;
|
|
17
|
+
padding: 30px;
|
|
18
|
+
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
h1 {
|
|
22
|
+
margin-top: 0;
|
|
23
|
+
color: #333;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.setting-group {
|
|
27
|
+
margin-bottom: 25px;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
label {
|
|
31
|
+
display: block;
|
|
32
|
+
margin-bottom: 8px;
|
|
33
|
+
font-weight: 500;
|
|
34
|
+
color: #555;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
input[type="number"] {
|
|
38
|
+
width: 150px;
|
|
39
|
+
padding: 8px 12px;
|
|
40
|
+
border: 1px solid #ddd;
|
|
41
|
+
border-radius: 4px;
|
|
42
|
+
font-size: 14px;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.help-text {
|
|
46
|
+
margin-top: 5px;
|
|
47
|
+
font-size: 13px;
|
|
48
|
+
color: #666;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
button {
|
|
52
|
+
background: #2196F3;
|
|
53
|
+
color: white;
|
|
54
|
+
border: none;
|
|
55
|
+
padding: 10px 20px;
|
|
56
|
+
border-radius: 4px;
|
|
57
|
+
font-size: 14px;
|
|
58
|
+
cursor: pointer;
|
|
59
|
+
margin-right: 10px;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
button:hover {
|
|
63
|
+
background: #1976D2;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
button.secondary {
|
|
67
|
+
background: #757575;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
button.secondary:hover {
|
|
71
|
+
background: #616161;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.status {
|
|
75
|
+
margin-top: 20px;
|
|
76
|
+
padding: 10px 15px;
|
|
77
|
+
border-radius: 4px;
|
|
78
|
+
font-size: 14px;
|
|
79
|
+
display: none;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.status.success {
|
|
83
|
+
background: #E8F5E9;
|
|
84
|
+
color: #2E7D32;
|
|
85
|
+
border: 1px solid #81C784;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.status.error {
|
|
89
|
+
background: #FFEBEE;
|
|
90
|
+
color: #C62828;
|
|
91
|
+
border: 1px solid #EF5350;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.status.info {
|
|
95
|
+
background: #E3F2FD;
|
|
96
|
+
color: #1565C0;
|
|
97
|
+
border: 1px solid #64B5F6;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
h3 {
|
|
101
|
+
margin: 0 0 15px 0;
|
|
102
|
+
color: #333;
|
|
103
|
+
font-size: 16px;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
input[type="radio"] {
|
|
107
|
+
margin-right: 8px;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
label {
|
|
111
|
+
display: flex;
|
|
112
|
+
align-items: center;
|
|
113
|
+
margin-bottom: 12px;
|
|
114
|
+
cursor: pointer;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.site-section {
|
|
118
|
+
margin: 20px 0;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.site-section label {
|
|
122
|
+
display: block;
|
|
123
|
+
margin-bottom: 8px;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
textarea {
|
|
127
|
+
width: 100%;
|
|
128
|
+
padding: 10px;
|
|
129
|
+
border: 1px solid #ddd;
|
|
130
|
+
border-radius: 4px;
|
|
131
|
+
font-size: 13px;
|
|
132
|
+
font-family: 'Courier New', monospace;
|
|
133
|
+
resize: vertical;
|
|
134
|
+
min-height: 120px;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
textarea:focus {
|
|
138
|
+
border-color: #2196F3;
|
|
139
|
+
outline: none;
|
|
140
|
+
box-shadow: 0 0 0 2px rgba(33, 150, 243, 0.1);
|
|
141
|
+
}
|
|
142
|
+
</style>
|
|
143
|
+
</head>
|
|
144
|
+
<body>
|
|
145
|
+
<div class="container">
|
|
146
|
+
<h1>Chrome Debug Settings</h1>
|
|
147
|
+
|
|
148
|
+
<div class="setting-group">
|
|
149
|
+
<label for="server-port">HTTP Server Port</label>
|
|
150
|
+
<input type="number" id="server-port" min="1024" max="65535" placeholder="Auto">
|
|
151
|
+
<div class="help-text">
|
|
152
|
+
Leave empty for automatic port discovery. The server typically runs on port 3000.
|
|
153
|
+
</div>
|
|
154
|
+
</div>
|
|
155
|
+
|
|
156
|
+
<div class="setting-group">
|
|
157
|
+
<label>Server Status</label>
|
|
158
|
+
<button id="test-connection">Test Connection</button>
|
|
159
|
+
<button id="auto-detect" class="secondary">Auto Detect Port</button>
|
|
160
|
+
</div>
|
|
161
|
+
|
|
162
|
+
<div class="setting-group">
|
|
163
|
+
<h3>Site Restrictions</h3>
|
|
164
|
+
<label>
|
|
165
|
+
<input type="radio" name="site-mode" value="whitelist" checked>
|
|
166
|
+
Whitelist Mode (only run on specified sites)
|
|
167
|
+
</label>
|
|
168
|
+
<label>
|
|
169
|
+
<input type="radio" name="site-mode" value="blacklist">
|
|
170
|
+
Blacklist Mode (run everywhere except specified sites)
|
|
171
|
+
</label>
|
|
172
|
+
|
|
173
|
+
<div id="allowed-sites-section" class="site-section">
|
|
174
|
+
<label for="allowed-sites">Allowed Sites (Whitelist)</label>
|
|
175
|
+
<div class="help-text">Chrome Debug will only run on these sites. Use * for wildcards (e.g., *.example.com)</div>
|
|
176
|
+
<textarea id="allowed-sites" rows="6" placeholder="localhost:* 127.0.0.1:* *.test *.local *.dev example.com"></textarea>
|
|
177
|
+
</div>
|
|
178
|
+
|
|
179
|
+
<div id="blocked-sites-section" class="site-section" style="display: none;">
|
|
180
|
+
<label for="blocked-sites">Blocked Sites (Blacklist)</label>
|
|
181
|
+
<div class="help-text">Chrome Debug will NOT run on these sites. Use * for wildcards (e.g., *.youtube.com)</div>
|
|
182
|
+
<textarea id="blocked-sites" rows="6" placeholder="youtube.com *.youtube.com google.com *.google.com facebook.com twitter.com"></textarea>
|
|
183
|
+
</div>
|
|
184
|
+
|
|
185
|
+
<div style="margin-top: 15px;">
|
|
186
|
+
<button id="test-current-site" class="secondary">Test Current Tab</button>
|
|
187
|
+
<button id="add-current-site" class="secondary">Add Current Site</button>
|
|
188
|
+
<button id="import-export" class="secondary">Import/Export</button>
|
|
189
|
+
</div>
|
|
190
|
+
</div>
|
|
191
|
+
|
|
192
|
+
<div id="status" class="status"></div>
|
|
193
|
+
|
|
194
|
+
<div style="margin-top: 30px;">
|
|
195
|
+
<button id="save">Save Settings</button>
|
|
196
|
+
<button id="reset" class="secondary">Reset to Default</button>
|
|
197
|
+
</div>
|
|
198
|
+
</div>
|
|
199
|
+
|
|
200
|
+
<script src="extension-config.js"></script>
|
|
201
|
+
<script src="options.js"></script>
|
|
202
|
+
</body>
|
|
203
|
+
</html>
|