@dynamicu/chromedebug-mcp 2.6.7 → 2.7.1
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 +17 -1
- package/README.md +1 -1
- package/chrome-extension/activation-manager.js +10 -10
- package/chrome-extension/background.js +1045 -736
- package/chrome-extension/browser-recording-manager.js +1 -1
- package/chrome-extension/chrome-debug-logger.js +168 -0
- package/chrome-extension/chrome-session-manager.js +5 -5
- package/chrome-extension/console-interception-library.js +430 -0
- package/chrome-extension/content.css +16 -16
- package/chrome-extension/content.js +739 -221
- package/chrome-extension/data-buffer.js +5 -5
- package/chrome-extension/dom-tracker.js +9 -9
- package/chrome-extension/extension-config.js +1 -1
- package/chrome-extension/firebase-client.js +13 -13
- package/chrome-extension/frame-capture.js +20 -38
- package/chrome-extension/license-helper.js +33 -7
- package/chrome-extension/manifest.free.json +3 -6
- package/chrome-extension/network-tracker.js +9 -9
- package/chrome-extension/options.html +10 -0
- package/chrome-extension/options.js +21 -8
- package/chrome-extension/performance-monitor.js +17 -17
- package/chrome-extension/popup.html +230 -193
- package/chrome-extension/popup.js +146 -458
- package/chrome-extension/pro/enhanced-capture.js +406 -0
- package/chrome-extension/pro/frame-editor.html +433 -0
- package/chrome-extension/pro/frame-editor.js +1567 -0
- package/chrome-extension/pro/function-tracker.js +843 -0
- package/chrome-extension/pro/jszip.min.js +13 -0
- package/chrome-extension/upload-manager.js +7 -7
- package/dist/chromedebug-extension-free.zip +0 -0
- package/package.json +3 -1
- package/scripts/webpack.config.free.cjs +8 -8
- package/scripts/webpack.config.pro.cjs +2 -0
- package/src/cli.js +2 -2
- package/src/database.js +55 -7
- package/src/index.js +9 -6
- package/src/mcp/server.js +2 -2
- package/src/services/process-manager.js +10 -6
- package/src/services/process-tracker.js +10 -5
- package/src/services/profile-manager.js +17 -2
- package/src/validation/schemas.js +12 -11
- package/src/index-direct.js +0 -157
- package/src/index-modular.js +0 -219
- package/src/index-monolithic-backup.js +0 -2230
- package/src/legacy/chrome-controller-old.js +0 -1406
- package/src/legacy/index-express.js +0 -625
- package/src/legacy/index-old.js +0 -977
- package/src/legacy/routes.js +0 -260
- package/src/legacy/shared-storage.js +0 -101
|
@@ -0,0 +1,843 @@
|
|
|
1
|
+
// Function Tracker for Chrome Debug Full Data Recording
|
|
2
|
+
// Wraps JavaScript functions to track execution flow and variable states
|
|
3
|
+
|
|
4
|
+
// Constants for batching and flushing
|
|
5
|
+
const DEFAULT_BATCH_SIZE = 100;
|
|
6
|
+
const FLUSH_INTERVAL_MS = 5000;
|
|
7
|
+
|
|
8
|
+
class FunctionTracker {
|
|
9
|
+
constructor(performanceMonitor, piiRedactor, onDataReady) {
|
|
10
|
+
this.isTracking = false;
|
|
11
|
+
this.trackedFunctions = new Map(); // Original function references
|
|
12
|
+
this.executionBuffer = [];
|
|
13
|
+
this.bufferFlushDelay = 3000; // Flush every 3 seconds
|
|
14
|
+
this.flushTimer = null;
|
|
15
|
+
this.recordingId = null;
|
|
16
|
+
this.callStack = []; // Track call hierarchy
|
|
17
|
+
|
|
18
|
+
// Configuration - LIGHTWEIGHT MODE BY DEFAULT
|
|
19
|
+
this.maxCallDepth = 5; // Reduced from 10
|
|
20
|
+
this.maxArgumentSize = 50; // DRASTICALLY reduced from 1024 - just capture basic info
|
|
21
|
+
this.trackReturnValues = false; // DON'T track return values
|
|
22
|
+
this.trackExecutionTime = true;
|
|
23
|
+
this.trackCallStack = false; // DON'T track full call stacks
|
|
24
|
+
this.lightweightMode = true; // NEW: Enable lightweight mode
|
|
25
|
+
this.batchSize = DEFAULT_BATCH_SIZE;
|
|
26
|
+
|
|
27
|
+
// Sensitive data patterns to redact
|
|
28
|
+
this.sensitivePatterns = [
|
|
29
|
+
'password', 'passwd', 'pwd',
|
|
30
|
+
'token', 'apikey', 'api_key', 'apiKey',
|
|
31
|
+
'secret', 'private', 'credential',
|
|
32
|
+
'authorization', 'auth',
|
|
33
|
+
'ssn', 'social_security',
|
|
34
|
+
'credit_card', 'creditcard', 'cc_number',
|
|
35
|
+
'cvv', 'cvc',
|
|
36
|
+
'pin', 'passcode'
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
// Callbacks and dependencies
|
|
40
|
+
this.onDataReady = onDataReady || null;
|
|
41
|
+
this.piiRedactor = piiRedactor || null;
|
|
42
|
+
this.performanceMonitor = performanceMonitor || null;
|
|
43
|
+
|
|
44
|
+
// Create bound flush method for event listeners
|
|
45
|
+
this.boundFlush = this.flush.bind(this);
|
|
46
|
+
this.flushInterval = null;
|
|
47
|
+
|
|
48
|
+
// Function categories to wrap
|
|
49
|
+
this.targetCategories = {
|
|
50
|
+
userDefined: true, // User-defined functions
|
|
51
|
+
eventHandlers: true, // Event handlers
|
|
52
|
+
asyncFunctions: true, // Promises, async/await
|
|
53
|
+
domMethods: false, // DOM manipulation (too noisy by default)
|
|
54
|
+
nativeAPIs: false // Native browser APIs (very noisy)
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
// Patterns for function identification
|
|
58
|
+
this.wrappingPatterns = {
|
|
59
|
+
// User-defined functions (detected by scope)
|
|
60
|
+
userFunctions: [],
|
|
61
|
+
|
|
62
|
+
// Event handling
|
|
63
|
+
eventMethods: [
|
|
64
|
+
'addEventListener',
|
|
65
|
+
'removeEventListener',
|
|
66
|
+
'onclick',
|
|
67
|
+
'onload',
|
|
68
|
+
'onchange',
|
|
69
|
+
'onsubmit'
|
|
70
|
+
],
|
|
71
|
+
|
|
72
|
+
// Async patterns
|
|
73
|
+
asyncMethods: [
|
|
74
|
+
'fetch',
|
|
75
|
+
'setTimeout',
|
|
76
|
+
'setInterval',
|
|
77
|
+
'requestAnimationFrame'
|
|
78
|
+
],
|
|
79
|
+
|
|
80
|
+
// DOM methods (optional)
|
|
81
|
+
domMethods: [
|
|
82
|
+
'querySelector',
|
|
83
|
+
'getElementById',
|
|
84
|
+
'createElement',
|
|
85
|
+
'appendChild',
|
|
86
|
+
'removeChild'
|
|
87
|
+
]
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
init(options = {}) {
|
|
92
|
+
this.recordingId = options.recordingId || 'default';
|
|
93
|
+
this.onDataReady = options.onDataReady;
|
|
94
|
+
this.piiRedactor = options.piiRedactor;
|
|
95
|
+
this.performanceMonitor = options.performanceMonitor;
|
|
96
|
+
|
|
97
|
+
// Override settings
|
|
98
|
+
if (options.maxCallDepth) this.maxCallDepth = options.maxCallDepth;
|
|
99
|
+
if (options.trackReturnValues !== undefined) this.trackReturnValues = options.trackReturnValues;
|
|
100
|
+
if (options.trackExecutionTime !== undefined) this.trackExecutionTime = options.trackExecutionTime;
|
|
101
|
+
if (options.bufferFlushDelay) this.bufferFlushDelay = options.bufferFlushDelay;
|
|
102
|
+
|
|
103
|
+
// Update target categories
|
|
104
|
+
if (options.targetCategories) {
|
|
105
|
+
this.targetCategories = { ...this.targetCategories, ...options.targetCategories };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// console.log('[FunctionTracker] Initialized with options:', {
|
|
109
|
+
// recordingId: this.recordingId,
|
|
110
|
+
// maxCallDepth: this.maxCallDepth,
|
|
111
|
+
// trackReturnValues: this.trackReturnValues,
|
|
112
|
+
// targetCategories: this.targetCategories
|
|
113
|
+
// });
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
startTracking() {
|
|
117
|
+
if (this.isTracking) {
|
|
118
|
+
console.warn('[FunctionTracker] Already tracking function calls');
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
try {
|
|
123
|
+
// Wrap different categories of functions
|
|
124
|
+
if (this.targetCategories.eventHandlers) {
|
|
125
|
+
this.wrapEventHandlers();
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (this.targetCategories.asyncFunctions) {
|
|
129
|
+
this.wrapAsyncFunctions();
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (this.targetCategories.domMethods) {
|
|
133
|
+
this.wrapDOMMethods();
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (this.targetCategories.userDefined) {
|
|
137
|
+
this.discoverAndWrapUserFunctions();
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
this.isTracking = true;
|
|
141
|
+
this.startBufferFlusher();
|
|
142
|
+
|
|
143
|
+
// Set up periodic flushing
|
|
144
|
+
this.flushInterval = setInterval(() => {
|
|
145
|
+
if (this.executionBuffer.length > 0) {
|
|
146
|
+
this.flush();
|
|
147
|
+
}
|
|
148
|
+
}, FLUSH_INTERVAL_MS);
|
|
149
|
+
|
|
150
|
+
// Set up pagehide listener for final flush
|
|
151
|
+
window.addEventListener('pagehide', this.boundFlush);
|
|
152
|
+
|
|
153
|
+
// console.log('[FunctionTracker] Started function execution tracking');
|
|
154
|
+
|
|
155
|
+
// Record performance event
|
|
156
|
+
if (this.performanceMonitor) {
|
|
157
|
+
this.performanceMonitor.recordEvent('function_tracking_started', {
|
|
158
|
+
timestamp: Date.now(),
|
|
159
|
+
categories: this.targetCategories
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
} catch (error) {
|
|
164
|
+
console.error('[FunctionTracker] Failed to start tracking:', error);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
stopTracking() {
|
|
169
|
+
if (!this.isTracking) return;
|
|
170
|
+
|
|
171
|
+
this.restoreAllFunctions();
|
|
172
|
+
this.stopBufferFlusher();
|
|
173
|
+
|
|
174
|
+
// Clear periodic flush interval
|
|
175
|
+
if (this.flushInterval) {
|
|
176
|
+
clearInterval(this.flushInterval);
|
|
177
|
+
this.flushInterval = null;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Remove pagehide listener
|
|
181
|
+
window.removeEventListener('pagehide', this.boundFlush);
|
|
182
|
+
|
|
183
|
+
this.flushBuffer(); // Flush any remaining data
|
|
184
|
+
|
|
185
|
+
this.isTracking = false;
|
|
186
|
+
// console.log('[FunctionTracker] Stopped function execution tracking');
|
|
187
|
+
|
|
188
|
+
// Record performance event
|
|
189
|
+
if (this.performanceMonitor) {
|
|
190
|
+
this.performanceMonitor.recordEvent('function_tracking_stopped', {
|
|
191
|
+
timestamp: Date.now(),
|
|
192
|
+
totalCalls: this.executionBuffer.length
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
wrapEventHandlers() {
|
|
198
|
+
// Wrap addEventListener to track event handlers
|
|
199
|
+
if (window.EventTarget && EventTarget.prototype.addEventListener) {
|
|
200
|
+
const originalAddEventListener = EventTarget.prototype.addEventListener;
|
|
201
|
+
const self = this; // Capture FunctionTracker instance
|
|
202
|
+
|
|
203
|
+
EventTarget.prototype.addEventListener = function(type, listener, options) {
|
|
204
|
+
if (typeof listener === 'function') {
|
|
205
|
+
const wrappedListener = self.wrapFunction(
|
|
206
|
+
listener,
|
|
207
|
+
`EventHandler:${type}`,
|
|
208
|
+
{ isEventHandler: true, eventType: type }
|
|
209
|
+
);
|
|
210
|
+
|
|
211
|
+
// Store reference for cleanup
|
|
212
|
+
if (!listener._chromePilotWrapped) {
|
|
213
|
+
listener._chromePilotWrapped = wrappedListener;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return originalAddEventListener.call(this, type, wrappedListener, options);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return originalAddEventListener.call(this, type, listener, options);
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
this.trackedFunctions.set('addEventListener', originalAddEventListener);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
wrapAsyncFunctions() {
|
|
227
|
+
// Wrap fetch API
|
|
228
|
+
if (window.fetch) {
|
|
229
|
+
const originalFetch = window.fetch;
|
|
230
|
+
|
|
231
|
+
window.fetch = async (...args) => {
|
|
232
|
+
const callId = this.generateCallId();
|
|
233
|
+
const startTime = Date.now();
|
|
234
|
+
|
|
235
|
+
this.recordFunctionCall({
|
|
236
|
+
callId,
|
|
237
|
+
functionName: 'fetch',
|
|
238
|
+
arguments: this.serializeArguments(args),
|
|
239
|
+
startTime,
|
|
240
|
+
category: 'async'
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
try {
|
|
244
|
+
const result = await originalFetch.apply(window, args);
|
|
245
|
+
|
|
246
|
+
this.recordFunctionReturn({
|
|
247
|
+
callId,
|
|
248
|
+
returnValue: this.serializeReturnValue({
|
|
249
|
+
status: result.status,
|
|
250
|
+
url: result.url,
|
|
251
|
+
headers: Object.fromEntries(result.headers.entries())
|
|
252
|
+
}),
|
|
253
|
+
executionTime: Date.now() - startTime,
|
|
254
|
+
success: true
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
return result;
|
|
258
|
+
} catch (error) {
|
|
259
|
+
this.recordFunctionReturn({
|
|
260
|
+
callId,
|
|
261
|
+
error: error.message,
|
|
262
|
+
executionTime: Date.now() - startTime,
|
|
263
|
+
success: false
|
|
264
|
+
});
|
|
265
|
+
throw error;
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
this.trackedFunctions.set('fetch', originalFetch);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Wrap setTimeout
|
|
273
|
+
if (window.setTimeout) {
|
|
274
|
+
const originalSetTimeout = window.setTimeout;
|
|
275
|
+
|
|
276
|
+
window.setTimeout = (callback, delay, ...args) => {
|
|
277
|
+
if (typeof callback === 'function') {
|
|
278
|
+
const wrappedCallback = this.wrapFunction(
|
|
279
|
+
callback,
|
|
280
|
+
'setTimeout_callback',
|
|
281
|
+
{ isAsync: true, delay }
|
|
282
|
+
);
|
|
283
|
+
return originalSetTimeout(wrappedCallback, delay, ...args);
|
|
284
|
+
}
|
|
285
|
+
return originalSetTimeout(callback, delay, ...args);
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
this.trackedFunctions.set('setTimeout', originalSetTimeout);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Wrap setInterval
|
|
292
|
+
if (window.setInterval) {
|
|
293
|
+
const originalSetInterval = window.setInterval;
|
|
294
|
+
|
|
295
|
+
window.setInterval = (callback, delay, ...args) => {
|
|
296
|
+
if (typeof callback === 'function') {
|
|
297
|
+
const wrappedCallback = this.wrapFunction(
|
|
298
|
+
callback,
|
|
299
|
+
'setInterval_callback',
|
|
300
|
+
{ isAsync: true, delay, repeating: true }
|
|
301
|
+
);
|
|
302
|
+
return originalSetInterval(wrappedCallback, delay, ...args);
|
|
303
|
+
}
|
|
304
|
+
return originalSetInterval(callback, delay, ...args);
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
this.trackedFunctions.set('setInterval', originalSetInterval);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
wrapDOMMethods() {
|
|
312
|
+
// Wrap commonly used DOM methods
|
|
313
|
+
const domMethods = ['querySelector', 'getElementById', 'createElement'];
|
|
314
|
+
|
|
315
|
+
domMethods.forEach(methodName => {
|
|
316
|
+
if (document[methodName]) {
|
|
317
|
+
const original = document[methodName];
|
|
318
|
+
|
|
319
|
+
document[methodName] = (...args) => {
|
|
320
|
+
const callId = this.generateCallId();
|
|
321
|
+
const startTime = Date.now();
|
|
322
|
+
|
|
323
|
+
this.recordFunctionCall({
|
|
324
|
+
callId,
|
|
325
|
+
functionName: `document.${methodName}`,
|
|
326
|
+
arguments: this.serializeArguments(args),
|
|
327
|
+
startTime,
|
|
328
|
+
category: 'dom'
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
try {
|
|
332
|
+
const result = original.apply(document, args);
|
|
333
|
+
|
|
334
|
+
this.recordFunctionReturn({
|
|
335
|
+
callId,
|
|
336
|
+
returnValue: this.serializeDOMElement(result),
|
|
337
|
+
executionTime: Date.now() - startTime,
|
|
338
|
+
success: true
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
return result;
|
|
342
|
+
} catch (error) {
|
|
343
|
+
this.recordFunctionReturn({
|
|
344
|
+
callId,
|
|
345
|
+
error: error.message,
|
|
346
|
+
executionTime: Date.now() - startTime,
|
|
347
|
+
success: false
|
|
348
|
+
});
|
|
349
|
+
throw error;
|
|
350
|
+
}
|
|
351
|
+
};
|
|
352
|
+
|
|
353
|
+
this.trackedFunctions.set(methodName, original);
|
|
354
|
+
}
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
discoverAndWrapUserFunctions() {
|
|
359
|
+
// Discover user-defined functions in global scope
|
|
360
|
+
const globalObj = window;
|
|
361
|
+
|
|
362
|
+
for (const prop in globalObj) {
|
|
363
|
+
try {
|
|
364
|
+
if (typeof globalObj[prop] === 'function' &&
|
|
365
|
+
!prop.startsWith('_') &&
|
|
366
|
+
!this.isNativeFunction(globalObj[prop]) &&
|
|
367
|
+
!this.trackedFunctions.has(prop)) {
|
|
368
|
+
|
|
369
|
+
const original = globalObj[prop];
|
|
370
|
+
const wrapped = this.wrapFunction(original, prop, { isUserDefined: true });
|
|
371
|
+
|
|
372
|
+
globalObj[prop] = wrapped;
|
|
373
|
+
this.trackedFunctions.set(prop, original);
|
|
374
|
+
}
|
|
375
|
+
} catch (error) {
|
|
376
|
+
// Skip properties that can't be accessed
|
|
377
|
+
continue;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
wrapFunction(originalFunction, functionName, metadata = {}) {
|
|
383
|
+
const tracker = this;
|
|
384
|
+
|
|
385
|
+
return function wrappedFunction(...args) {
|
|
386
|
+
// Prevent deep recursion tracking
|
|
387
|
+
if (tracker.callStack.length >= tracker.maxCallDepth) {
|
|
388
|
+
return originalFunction.apply(this, args);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
const callId = tracker.generateCallId();
|
|
392
|
+
const startTime = Date.now();
|
|
393
|
+
|
|
394
|
+
// Add to call stack
|
|
395
|
+
tracker.callStack.push({
|
|
396
|
+
callId,
|
|
397
|
+
functionName,
|
|
398
|
+
startTime
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
// Record function call
|
|
402
|
+
tracker.recordFunctionCall({
|
|
403
|
+
callId,
|
|
404
|
+
functionName,
|
|
405
|
+
arguments: tracker.serializeArguments(args),
|
|
406
|
+
startTime,
|
|
407
|
+
category: metadata.category || 'user',
|
|
408
|
+
metadata,
|
|
409
|
+
callDepth: tracker.callStack.length,
|
|
410
|
+
parentCallId: tracker.callStack.length > 1 ?
|
|
411
|
+
tracker.callStack[tracker.callStack.length - 2].callId : null
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
try {
|
|
415
|
+
const result = originalFunction.apply(this, args);
|
|
416
|
+
|
|
417
|
+
// Handle promises
|
|
418
|
+
if (result && typeof result.then === 'function') {
|
|
419
|
+
return result
|
|
420
|
+
.then(value => {
|
|
421
|
+
tracker.recordFunctionReturn({
|
|
422
|
+
callId,
|
|
423
|
+
returnValue: tracker.serializeReturnValue(value),
|
|
424
|
+
executionTime: Date.now() - startTime,
|
|
425
|
+
success: true,
|
|
426
|
+
isAsync: true
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
tracker.callStack.pop();
|
|
430
|
+
return value;
|
|
431
|
+
})
|
|
432
|
+
.catch(error => {
|
|
433
|
+
tracker.recordFunctionReturn({
|
|
434
|
+
callId,
|
|
435
|
+
error: error.message,
|
|
436
|
+
executionTime: Date.now() - startTime,
|
|
437
|
+
success: false,
|
|
438
|
+
isAsync: true
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
tracker.callStack.pop();
|
|
442
|
+
throw error;
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// Handle synchronous return
|
|
447
|
+
tracker.recordFunctionReturn({
|
|
448
|
+
callId,
|
|
449
|
+
returnValue: tracker.serializeReturnValue(result),
|
|
450
|
+
executionTime: Date.now() - startTime,
|
|
451
|
+
success: true
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
tracker.callStack.pop();
|
|
455
|
+
return result;
|
|
456
|
+
|
|
457
|
+
} catch (error) {
|
|
458
|
+
tracker.recordFunctionReturn({
|
|
459
|
+
callId,
|
|
460
|
+
error: error.message,
|
|
461
|
+
executionTime: Date.now() - startTime,
|
|
462
|
+
success: false
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
tracker.callStack.pop();
|
|
466
|
+
throw error;
|
|
467
|
+
}
|
|
468
|
+
};
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
recordFunctionCall(callData) {
|
|
472
|
+
// LIGHTWEIGHT MODE: Only essential info
|
|
473
|
+
let executionEvent;
|
|
474
|
+
|
|
475
|
+
if (this.lightweightMode) {
|
|
476
|
+
executionEvent = {
|
|
477
|
+
type: 'execution_trace',
|
|
478
|
+
timestamp: callData.startTime,
|
|
479
|
+
recording_id: this.recordingId,
|
|
480
|
+
function_name: callData.functionName,
|
|
481
|
+
// Just argument summary, not full values
|
|
482
|
+
arg_summary: callData.arguments ? `${callData.arguments.length} args` : '0 args'
|
|
483
|
+
};
|
|
484
|
+
} else {
|
|
485
|
+
// Original full tracking
|
|
486
|
+
executionEvent = {
|
|
487
|
+
type: 'execution_trace',
|
|
488
|
+
timestamp: callData.startTime,
|
|
489
|
+
recording_id: this.recordingId,
|
|
490
|
+
call_id: callData.callId,
|
|
491
|
+
function_name: callData.functionName,
|
|
492
|
+
arguments: callData.arguments,
|
|
493
|
+
call_depth: callData.callDepth || 0,
|
|
494
|
+
parent_call_id: callData.parentCallId,
|
|
495
|
+
category: callData.category,
|
|
496
|
+
metadata: callData.metadata
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
// Apply PII redaction (only if not in lightweight mode)
|
|
501
|
+
if (this.piiRedactor && !this.lightweightMode) {
|
|
502
|
+
executionEvent = this.piiRedactor.redactData(executionEvent, 'execution_trace');
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
this.executionBuffer.push(executionEvent);
|
|
506
|
+
|
|
507
|
+
// Check if we should flush based on batch size
|
|
508
|
+
if (this.executionBuffer.length >= this.batchSize) {
|
|
509
|
+
this.flush();
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
recordFunctionReturn(returnData) {
|
|
514
|
+
// In lightweight mode, don't track returns at all unless there's an error
|
|
515
|
+
if (this.lightweightMode && !returnData.error) {
|
|
516
|
+
return;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
if (!this.trackReturnValues && !returnData.error) {
|
|
520
|
+
return; // Skip return value tracking if disabled
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
let returnEvent = {
|
|
524
|
+
type: 'execution_trace',
|
|
525
|
+
timestamp: Date.now(),
|
|
526
|
+
recording_id: this.recordingId,
|
|
527
|
+
call_id: returnData.callId,
|
|
528
|
+
return_value: returnData.returnValue,
|
|
529
|
+
execution_time_ms: returnData.executionTime,
|
|
530
|
+
success: returnData.success,
|
|
531
|
+
error: returnData.error,
|
|
532
|
+
is_async: returnData.isAsync || false
|
|
533
|
+
};
|
|
534
|
+
|
|
535
|
+
// Apply PII redaction
|
|
536
|
+
if (this.piiRedactor) {
|
|
537
|
+
returnEvent = this.piiRedactor.redactData(returnEvent, 'execution_trace');
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
this.executionBuffer.push(returnEvent);
|
|
541
|
+
|
|
542
|
+
// Check if we should flush based on batch size
|
|
543
|
+
if (this.executionBuffer.length >= this.batchSize) {
|
|
544
|
+
this.flush();
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
serializeArguments(args) {
|
|
549
|
+
if (!args || args.length === 0) return [];
|
|
550
|
+
|
|
551
|
+
// LIGHTWEIGHT MODE: Just capture argument count and types
|
|
552
|
+
if (this.lightweightMode) {
|
|
553
|
+
try {
|
|
554
|
+
return Array.from(args).map(arg => {
|
|
555
|
+
if (arg === null) return 'null';
|
|
556
|
+
if (arg === undefined) return 'undefined';
|
|
557
|
+
if (typeof arg === 'function') return '[Function]';
|
|
558
|
+
if (arg instanceof Element) return `[DOM:${arg.tagName}]`;
|
|
559
|
+
if (typeof arg === 'object') return `[Object:${arg.constructor?.name || 'Object'}]`;
|
|
560
|
+
if (typeof arg === 'string') return arg.length > 20 ? `"${arg.substring(0, 20)}..."` : `"${arg}"`;
|
|
561
|
+
return String(arg).substring(0, 50);
|
|
562
|
+
});
|
|
563
|
+
} catch (error) {
|
|
564
|
+
return ['[Error]'];
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
// Original deep serialization (only if not in lightweight mode)
|
|
569
|
+
try {
|
|
570
|
+
return Array.from(args).map(arg => {
|
|
571
|
+
const serialized = this.serializeValue(arg);
|
|
572
|
+
return this.truncateValue(serialized, this.maxArgumentSize);
|
|
573
|
+
});
|
|
574
|
+
} catch (error) {
|
|
575
|
+
return ['[Serialization Error]'];
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
serializeReturnValue(value) {
|
|
580
|
+
if (!this.trackReturnValues) return null;
|
|
581
|
+
|
|
582
|
+
try {
|
|
583
|
+
const serialized = this.serializeValue(value);
|
|
584
|
+
return this.truncateValue(serialized, this.maxArgumentSize);
|
|
585
|
+
} catch (error) {
|
|
586
|
+
return '[Serialization Error]';
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
serializeValue(value) {
|
|
591
|
+
if (value === null || value === undefined) {
|
|
592
|
+
return value;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
if (typeof value === 'function') {
|
|
596
|
+
return '[Function]';
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
if (value instanceof Error) {
|
|
600
|
+
return {
|
|
601
|
+
name: value.name,
|
|
602
|
+
message: value.message,
|
|
603
|
+
stack: value.stack?.substring(0, 500)
|
|
604
|
+
};
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
if (value instanceof Element) {
|
|
608
|
+
return this.serializeDOMElement(value);
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
if (value && typeof value === 'object') {
|
|
612
|
+
// Handle circular references and sanitize
|
|
613
|
+
try {
|
|
614
|
+
const sanitized = this.sanitizeObject(value);
|
|
615
|
+
return JSON.parse(JSON.stringify(sanitized));
|
|
616
|
+
} catch (error) {
|
|
617
|
+
return '[Circular Reference]';
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
// Sanitize string values
|
|
622
|
+
if (typeof value === 'string') {
|
|
623
|
+
return this.sanitizeString(value);
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
return value;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
sanitizeObject(obj, depth = 0) {
|
|
630
|
+
if (depth > 5) {
|
|
631
|
+
return '[Max Depth Exceeded]';
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
if (Array.isArray(obj)) {
|
|
635
|
+
return obj.map(item => this.sanitizeValue(item, depth + 1));
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
const sanitized = {};
|
|
639
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
640
|
+
// Check if key contains sensitive pattern
|
|
641
|
+
const lowerKey = key.toLowerCase();
|
|
642
|
+
const isSensitive = this.sensitivePatterns.some(pattern =>
|
|
643
|
+
lowerKey.includes(pattern.toLowerCase())
|
|
644
|
+
);
|
|
645
|
+
|
|
646
|
+
if (isSensitive) {
|
|
647
|
+
sanitized[key] = '[REDACTED]';
|
|
648
|
+
} else {
|
|
649
|
+
sanitized[key] = this.sanitizeValue(value, depth + 1);
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
return sanitized;
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
sanitizeValue(value, depth = 0) {
|
|
657
|
+
if (value === null || value === undefined) {
|
|
658
|
+
return value;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
if (typeof value === 'string') {
|
|
662
|
+
return this.sanitizeString(value);
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
if (typeof value === 'object') {
|
|
666
|
+
return this.sanitizeObject(value, depth);
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
return value;
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
sanitizeString(str) {
|
|
673
|
+
// Check for common sensitive patterns in strings
|
|
674
|
+
if (str.length > 100) {
|
|
675
|
+
// Check if it looks like a token or key
|
|
676
|
+
if (/^[A-Za-z0-9+/=_-]{40,}$/.test(str)) {
|
|
677
|
+
return '[POSSIBLE TOKEN REDACTED]';
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
// Check for JWT pattern
|
|
681
|
+
if (/^eyJ[A-Za-z0-9_-]+\.eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/.test(str)) {
|
|
682
|
+
return '[JWT TOKEN REDACTED]';
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
// Check for credit card pattern
|
|
687
|
+
if (/\b(?:\d{4}[-\s]?){3}\d{4}\b/.test(str)) {
|
|
688
|
+
return str.replace(/\b(?:\d{4}[-\s]?){3}\d{4}\b/g, '[CREDIT CARD REDACTED]');
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
// Check for SSN pattern
|
|
692
|
+
if (/\b\d{3}-\d{2}-\d{4}\b/.test(str)) {
|
|
693
|
+
return str.replace(/\b\d{3}-\d{2}-\d{4}\b/g, '[SSN REDACTED]');
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
return str;
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
serializeDOMElement(element) {
|
|
700
|
+
if (!element || !element.tagName) return element;
|
|
701
|
+
|
|
702
|
+
return {
|
|
703
|
+
tagName: element.tagName.toLowerCase(),
|
|
704
|
+
id: element.id,
|
|
705
|
+
className: element.className,
|
|
706
|
+
textContent: element.textContent?.substring(0, 100)
|
|
707
|
+
};
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
truncateValue(value, maxSize) {
|
|
711
|
+
if (typeof value === 'string' && value.length > maxSize) {
|
|
712
|
+
return value.substring(0, maxSize) + '... [truncated]';
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
if (typeof value === 'object' && value !== null) {
|
|
716
|
+
const stringified = JSON.stringify(value);
|
|
717
|
+
if (stringified.length > maxSize) {
|
|
718
|
+
return JSON.stringify(value, null, 0).substring(0, maxSize) + '... [truncated]';
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
return value;
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
isNativeFunction(func) {
|
|
726
|
+
const funcStr = func.toString();
|
|
727
|
+
return funcStr.includes('[native code]') ||
|
|
728
|
+
funcStr.includes('[object ') ||
|
|
729
|
+
func.name.startsWith('bound ');
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
generateCallId() {
|
|
733
|
+
return `call_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
restoreAllFunctions() {
|
|
737
|
+
// Restore all wrapped functions
|
|
738
|
+
for (const [name, original] of this.trackedFunctions) {
|
|
739
|
+
try {
|
|
740
|
+
if (name === 'addEventListener' && EventTarget.prototype.addEventListener) {
|
|
741
|
+
EventTarget.prototype.addEventListener = original;
|
|
742
|
+
} else if (name === 'fetch' && window.fetch) {
|
|
743
|
+
window.fetch = original;
|
|
744
|
+
} else if (name === 'setTimeout' && window.setTimeout) {
|
|
745
|
+
window.setTimeout = original;
|
|
746
|
+
} else if (name === 'setInterval' && window.setInterval) {
|
|
747
|
+
window.setInterval = original;
|
|
748
|
+
} else if (document[name]) {
|
|
749
|
+
document[name] = original;
|
|
750
|
+
} else if (window[name]) {
|
|
751
|
+
window[name] = original;
|
|
752
|
+
}
|
|
753
|
+
} catch (error) {
|
|
754
|
+
console.error(`[FunctionTracker] Failed to restore ${name}:`, error);
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
this.trackedFunctions.clear();
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
startBufferFlusher() {
|
|
762
|
+
if (this.flushTimer) return;
|
|
763
|
+
|
|
764
|
+
this.flushTimer = setInterval(() => {
|
|
765
|
+
if (this.executionBuffer.length > 0) {
|
|
766
|
+
this.flushBuffer();
|
|
767
|
+
}
|
|
768
|
+
}, this.bufferFlushDelay);
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
stopBufferFlusher() {
|
|
772
|
+
if (this.flushTimer) {
|
|
773
|
+
clearInterval(this.flushTimer);
|
|
774
|
+
this.flushTimer = null;
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
flushBuffer() {
|
|
779
|
+
if (this.executionBuffer.length === 0) return;
|
|
780
|
+
|
|
781
|
+
const events = [...this.executionBuffer];
|
|
782
|
+
this.executionBuffer = [];
|
|
783
|
+
|
|
784
|
+
// console.log(`[FunctionTracker] Flushing ${events.length} execution traces`);
|
|
785
|
+
|
|
786
|
+
// Send data via callback
|
|
787
|
+
if (this.onDataReady) {
|
|
788
|
+
this.onDataReady(events);
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
// Record performance metrics
|
|
792
|
+
if (this.performanceMonitor) {
|
|
793
|
+
this.performanceMonitor.recordEvent('function_calls_flushed', {
|
|
794
|
+
count: events.length,
|
|
795
|
+
timestamp: Date.now()
|
|
796
|
+
});
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
getStats() {
|
|
801
|
+
return {
|
|
802
|
+
isTracking: this.isTracking,
|
|
803
|
+
bufferSize: this.executionBuffer.length,
|
|
804
|
+
trackedFunctionCount: this.trackedFunctions.size,
|
|
805
|
+
currentCallDepth: this.callStack.length,
|
|
806
|
+
recordingId: this.recordingId,
|
|
807
|
+
targetCategories: this.targetCategories
|
|
808
|
+
};
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
flush() {
|
|
812
|
+
if (this.executionBuffer.length === 0 || !this.onDataReady) {
|
|
813
|
+
return;
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
// Pass a copy of the buffer to the callback and reset the original
|
|
817
|
+
const eventsToFlush = [...this.executionBuffer];
|
|
818
|
+
this.executionBuffer = [];
|
|
819
|
+
|
|
820
|
+
try {
|
|
821
|
+
this.onDataReady(eventsToFlush);
|
|
822
|
+
// console.log(`[FunctionTracker] Flushed ${eventsToFlush.length} execution traces`);
|
|
823
|
+
} catch (error) {
|
|
824
|
+
console.error('[FunctionTracker] Error in onDataReady callback for function traces:', error);
|
|
825
|
+
// Optionally add events back for retry, but be careful of infinite loops
|
|
826
|
+
// this.executionBuffer.unshift(...eventsToFlush);
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
destroy() {
|
|
831
|
+
this.stopTracking();
|
|
832
|
+
this.executionBuffer = [];
|
|
833
|
+
this.callStack = [];
|
|
834
|
+
this.onDataReady = null;
|
|
835
|
+
this.piiRedactor = null;
|
|
836
|
+
this.performanceMonitor = null;
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
// Export for use in content script
|
|
841
|
+
if (typeof module !== 'undefined' && module.exports) {
|
|
842
|
+
module.exports = FunctionTracker;
|
|
843
|
+
}
|