@dynamicu/chromedebug-mcp 2.6.7 → 2.7.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 +1 -1
- package/README.md +1 -1
- package/chrome-extension/background.js +611 -505
- package/chrome-extension/browser-recording-manager.js +1 -1
- package/chrome-extension/chrome-debug-logger.js +168 -0
- package/chrome-extension/console-interception-library.js +430 -0
- package/chrome-extension/content.css +16 -16
- package/chrome-extension/content.js +458 -126
- package/chrome-extension/extension-config.js +1 -1
- package/chrome-extension/license-helper.js +26 -0
- package/chrome-extension/manifest.free.json +0 -3
- package/chrome-extension/options.js +1 -1
- package/chrome-extension/popup.html +221 -191
- package/chrome-extension/popup.js +88 -379
- package/chrome-extension/pro/enhanced-capture.js +406 -0
- package/chrome-extension/pro/frame-editor.html +410 -0
- package/chrome-extension/pro/frame-editor.js +1496 -0
- package/chrome-extension/pro/function-tracker.js +843 -0
- package/chrome-extension/pro/jszip.min.js +13 -0
- 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 +2 -2
- 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
|
@@ -190,7 +190,7 @@ class BrowserRecordingManager {
|
|
|
190
190
|
|
|
191
191
|
// Generate filename
|
|
192
192
|
const date = new Date(recording.startTime).toISOString().split('T')[0];
|
|
193
|
-
const filename = `chrome-
|
|
193
|
+
const filename = `chrome-debug-recording-${date}-${sessionId.slice(-8)}.json`;
|
|
194
194
|
|
|
195
195
|
return {
|
|
196
196
|
blob,
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ChromeDebugLogger - Dedicated logging system for Chrome Debug internal diagnostics
|
|
3
|
+
* Bypasses console interception to prevent polluting user workflow recordings
|
|
4
|
+
*
|
|
5
|
+
* Key Features:
|
|
6
|
+
* - Preserves original console before interception
|
|
7
|
+
* - Auto-enables in development mode
|
|
8
|
+
* - Works in service workers (chrome.storage.local) and content scripts (localStorage)
|
|
9
|
+
* - Includes source location tracking
|
|
10
|
+
* - Default disabled in production
|
|
11
|
+
* - Consistent prefix: [🔧 ChromeDebug:ComponentName]
|
|
12
|
+
*/
|
|
13
|
+
class ChromeDebugLogger {
|
|
14
|
+
constructor() {
|
|
15
|
+
// CRITICAL: Save original console IMMEDIATELY (before interception occurs)
|
|
16
|
+
this.originalConsole = {
|
|
17
|
+
log: console.log.bind(console),
|
|
18
|
+
error: console.error.bind(console),
|
|
19
|
+
warn: console.warn.bind(console),
|
|
20
|
+
debug: console.debug.bind(console),
|
|
21
|
+
info: console.info.bind(console)
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
// Auto-enable in development mode
|
|
25
|
+
const isDev = typeof chrome !== 'undefined' &&
|
|
26
|
+
chrome.runtime?.getManifest?.()?.version_name?.includes('dev');
|
|
27
|
+
this.enabled = isDev || false;
|
|
28
|
+
|
|
29
|
+
this.prefix = '🔧 ChromeDebug';
|
|
30
|
+
|
|
31
|
+
// Load saved state (async, but don't block constructor)
|
|
32
|
+
this._loadState();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Enable Chrome Debug internal logging
|
|
37
|
+
*/
|
|
38
|
+
enable() {
|
|
39
|
+
this.enabled = true;
|
|
40
|
+
this._saveState();
|
|
41
|
+
this._log('Logger', 'Chrome Debug logger enabled', null, 'info');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Disable Chrome Debug internal logging
|
|
46
|
+
*/
|
|
47
|
+
disable() {
|
|
48
|
+
this._log('Logger', 'Chrome Debug logger disabled', null, 'info');
|
|
49
|
+
this.enabled = false;
|
|
50
|
+
this._saveState();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Check if logging is enabled
|
|
55
|
+
*/
|
|
56
|
+
isEnabled() {
|
|
57
|
+
return this.enabled;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Log informational message
|
|
62
|
+
*/
|
|
63
|
+
log(component, message, data = null) {
|
|
64
|
+
if (!this.enabled) return;
|
|
65
|
+
this._log(component, message, data, 'log');
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Log error message
|
|
70
|
+
*/
|
|
71
|
+
error(component, message, data = null) {
|
|
72
|
+
if (!this.enabled) return;
|
|
73
|
+
this._log(component, message, data, 'error');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Log warning message
|
|
78
|
+
*/
|
|
79
|
+
warn(component, message, data = null) {
|
|
80
|
+
if (!this.enabled) return;
|
|
81
|
+
this._log(component, message, data, 'warn');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Log debug message
|
|
86
|
+
*/
|
|
87
|
+
debug(component, message, data = null) {
|
|
88
|
+
if (!this.enabled) return;
|
|
89
|
+
this._log(component, message, data, 'debug');
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Internal logging implementation
|
|
94
|
+
*/
|
|
95
|
+
_log(component, message, data, level = 'log') {
|
|
96
|
+
try {
|
|
97
|
+
// Capture source location for debugging
|
|
98
|
+
const error = new Error();
|
|
99
|
+
const stack = error.stack?.split('\n')[3]; // Caller's location
|
|
100
|
+
const location = stack?.match(/\((.*?)\)/)?.[1] || '';
|
|
101
|
+
|
|
102
|
+
const prefix = `[${this.prefix}:${component}]`;
|
|
103
|
+
const suffix = location ? ` @${location.split('/').pop()}` : '';
|
|
104
|
+
|
|
105
|
+
if (data !== null && data !== undefined) {
|
|
106
|
+
this.originalConsole[level](prefix, message + suffix, data);
|
|
107
|
+
} else {
|
|
108
|
+
this.originalConsole[level](prefix, message + suffix);
|
|
109
|
+
}
|
|
110
|
+
} catch (e) {
|
|
111
|
+
// Fallback if logging fails
|
|
112
|
+
this.originalConsole.error('ChromeDebugLogger error:', e);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Save enabled state
|
|
118
|
+
*/
|
|
119
|
+
async _saveState() {
|
|
120
|
+
try {
|
|
121
|
+
if (typeof chrome !== 'undefined' && chrome.storage) {
|
|
122
|
+
// Service worker context
|
|
123
|
+
await chrome.storage.local.set({ chromedebug_logger_enabled: this.enabled });
|
|
124
|
+
} else if (typeof localStorage !== 'undefined') {
|
|
125
|
+
// Content/popup context
|
|
126
|
+
localStorage.setItem('chromedebug_logger_enabled', this.enabled.toString());
|
|
127
|
+
}
|
|
128
|
+
} catch (e) {
|
|
129
|
+
// Storage might not be available
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Load enabled state
|
|
135
|
+
*/
|
|
136
|
+
async _loadState() {
|
|
137
|
+
try {
|
|
138
|
+
if (typeof chrome !== 'undefined' && chrome.storage) {
|
|
139
|
+
// Service worker context
|
|
140
|
+
const result = await chrome.storage.local.get('chromedebug_logger_enabled');
|
|
141
|
+
if (result.chromedebug_logger_enabled !== undefined) {
|
|
142
|
+
this.enabled = result.chromedebug_logger_enabled;
|
|
143
|
+
}
|
|
144
|
+
} else if (typeof localStorage !== 'undefined') {
|
|
145
|
+
// Content/popup context
|
|
146
|
+
const saved = localStorage.getItem('chromedebug_logger_enabled');
|
|
147
|
+
if (saved !== null) {
|
|
148
|
+
this.enabled = saved === 'true';
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
} catch (e) {
|
|
152
|
+
// Storage might not be available
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Create global singleton instance
|
|
158
|
+
const chromeDebugLogger = new ChromeDebugLogger();
|
|
159
|
+
|
|
160
|
+
// Export for different contexts
|
|
161
|
+
if (typeof window !== 'undefined') {
|
|
162
|
+
window.chromeDebugLogger = chromeDebugLogger;
|
|
163
|
+
window.ChromeDebugLogger = ChromeDebugLogger;
|
|
164
|
+
}
|
|
165
|
+
if (typeof self !== 'undefined') {
|
|
166
|
+
self.chromeDebugLogger = chromeDebugLogger;
|
|
167
|
+
self.ChromeDebugLogger = ChromeDebugLogger;
|
|
168
|
+
}
|
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Console Interception Library
|
|
3
|
+
* Shared code for both Screen Recording and Workflow Recording
|
|
4
|
+
*
|
|
5
|
+
* This library provides console log interception and cleanup functionality
|
|
6
|
+
* that is used identically by both recording systems.
|
|
7
|
+
*
|
|
8
|
+
* DRY Principle: Don't Repeat Yourself
|
|
9
|
+
* - Reduces code duplication from 600+ lines to reusable functions
|
|
10
|
+
* - Single source of truth for console interception logic
|
|
11
|
+
* - Easier to maintain and update
|
|
12
|
+
*
|
|
13
|
+
* Usage Example:
|
|
14
|
+
* ```javascript
|
|
15
|
+
* // Screen recording config
|
|
16
|
+
* const screenConfig = {
|
|
17
|
+
* overrideFlagName: '__chromePilotConsoleOverridden',
|
|
18
|
+
* originalConsoleName: '__chromePilotOriginalConsole',
|
|
19
|
+
* relayFlagName: '__chromePilotConsoleRelay',
|
|
20
|
+
* messageType: 'chrome-debug-console-log',
|
|
21
|
+
* backgroundAction: 'consoleLog'
|
|
22
|
+
* };
|
|
23
|
+
*
|
|
24
|
+
* // Start interception
|
|
25
|
+
* await startConsoleInterception(tabId, screenConfig);
|
|
26
|
+
*
|
|
27
|
+
* // Stop interception
|
|
28
|
+
* await stopConsoleInterception(tabId, screenConfig);
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
// Console Interception Library - Direct function definitions for importScripts compatibility
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Injectable function for MAIN world console interception
|
|
36
|
+
* This function will be serialized and injected into the page's MAIN execution context
|
|
37
|
+
*
|
|
38
|
+
* @param {string} overrideFlagName - Window property to track if already overridden
|
|
39
|
+
* @param {string} originalConsoleName - Window property to store original console methods
|
|
40
|
+
* @param {string} messageType - The postMessage type for log relay
|
|
41
|
+
* @returns {string} Status message
|
|
42
|
+
*/
|
|
43
|
+
function injectConsoleInterceptor(overrideFlagName, originalConsoleName, messageType) {
|
|
44
|
+
// Check if we've already overridden console methods
|
|
45
|
+
if (window[overrideFlagName]) {
|
|
46
|
+
console.log('[Chrome Debug] Console already overridden, skipping');
|
|
47
|
+
return 'already_installed';
|
|
48
|
+
}
|
|
49
|
+
window[overrideFlagName] = true;
|
|
50
|
+
console.log('[Chrome Debug] Installing console interceptor');
|
|
51
|
+
|
|
52
|
+
// CRITICAL: Store original console methods in window for later restoration
|
|
53
|
+
window[originalConsoleName] = {
|
|
54
|
+
log: console.log,
|
|
55
|
+
error: console.error,
|
|
56
|
+
warn: console.warn,
|
|
57
|
+
info: console.info,
|
|
58
|
+
debug: console.debug,
|
|
59
|
+
trace: console.trace,
|
|
60
|
+
table: console.table,
|
|
61
|
+
dir: console.dir,
|
|
62
|
+
group: console.group,
|
|
63
|
+
groupEnd: console.groupEnd,
|
|
64
|
+
time: console.time,
|
|
65
|
+
timeEnd: console.timeEnd,
|
|
66
|
+
count: console.count
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
// Duck-type check for Error-like objects (handles cross-realm errors)
|
|
70
|
+
const isErrorLike = (obj) => {
|
|
71
|
+
if (obj instanceof Error) return true;
|
|
72
|
+
// Duck-type check for cross-realm errors (e.g., from iframes, web workers)
|
|
73
|
+
return obj !== null &&
|
|
74
|
+
typeof obj === 'object' &&
|
|
75
|
+
typeof obj.message === 'string' &&
|
|
76
|
+
(typeof obj.stack === 'string' || typeof obj.name === 'string');
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
// Serialize Error objects properly (message, stack, name are non-enumerable)
|
|
80
|
+
const serializeError = (err, depth = 0) => {
|
|
81
|
+
if (depth > 3) return '[Error: max depth reached]';
|
|
82
|
+
|
|
83
|
+
const result = {
|
|
84
|
+
name: err.name || 'Error',
|
|
85
|
+
message: err.message || '',
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
// Capture stack trace with intelligent truncation
|
|
89
|
+
if (err.stack) {
|
|
90
|
+
const stackLines = err.stack.split('\n');
|
|
91
|
+
let truncatedStack = [];
|
|
92
|
+
for (let i = 0; i < stackLines.length && i < 10; i++) {
|
|
93
|
+
truncatedStack.push(stackLines[i]);
|
|
94
|
+
// Stop at node_modules to reduce noise
|
|
95
|
+
if (stackLines[i].includes('node_modules')) {
|
|
96
|
+
truncatedStack.push(' ... (truncated at node_modules)');
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
result.stack = truncatedStack.join('\n');
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Capture custom enumerable properties (e.g., Firebase error.code)
|
|
104
|
+
for (const key of Object.keys(err)) {
|
|
105
|
+
if (!['name', 'message', 'stack'].includes(key)) {
|
|
106
|
+
try {
|
|
107
|
+
if (err[key] instanceof Error || isErrorLike(err[key])) {
|
|
108
|
+
result[key] = serializeError(err[key], depth + 1);
|
|
109
|
+
} else {
|
|
110
|
+
result[key] = JSON.parse(JSON.stringify(err[key]));
|
|
111
|
+
}
|
|
112
|
+
} catch (e) {
|
|
113
|
+
result[key] = '[Could not serialize]';
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// ES2022 error.cause support
|
|
119
|
+
if (err.cause) {
|
|
120
|
+
if (err.cause instanceof Error || isErrorLike(err.cause)) {
|
|
121
|
+
result.cause = serializeError(err.cause, depth + 1);
|
|
122
|
+
} else {
|
|
123
|
+
try {
|
|
124
|
+
result.cause = JSON.parse(JSON.stringify(err.cause));
|
|
125
|
+
} catch (e) {
|
|
126
|
+
result.cause = '[Could not serialize cause]';
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return result;
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
// Create sendLog function with truncation logic
|
|
135
|
+
const sendLog = (level, args) => {
|
|
136
|
+
try {
|
|
137
|
+
// Pre-serialize arguments to strings to avoid structured clone errors
|
|
138
|
+
const serializedArgs = args.map(arg => {
|
|
139
|
+
try {
|
|
140
|
+
if (arg === null) return 'null';
|
|
141
|
+
if (arg === undefined) return 'undefined';
|
|
142
|
+
if (typeof arg === 'function') return '[Function: ' + (arg.name || 'anonymous') + ']';
|
|
143
|
+
|
|
144
|
+
// IMPORTANT: Check for Error objects BEFORE generic object handler
|
|
145
|
+
// Error properties (message, stack, name) are non-enumerable and JSON.stringify returns "{}"
|
|
146
|
+
if (arg instanceof Error || isErrorLike(arg)) {
|
|
147
|
+
const errorObj = serializeError(arg);
|
|
148
|
+
return JSON.stringify(errorObj, null, 2);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Note: Element check removed - service workers have no DOM access, so this will never trigger
|
|
152
|
+
if (typeof arg === 'object') {
|
|
153
|
+
// Custom replacer to handle nested errors in objects
|
|
154
|
+
const errorReplacer = (key, value) => {
|
|
155
|
+
if (value instanceof Error || (value && isErrorLike(value))) {
|
|
156
|
+
return serializeError(value);
|
|
157
|
+
}
|
|
158
|
+
return value;
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
// Try to stringify, but limit depth to avoid circular references
|
|
162
|
+
let stringified = JSON.stringify(arg, errorReplacer, 2);
|
|
163
|
+
|
|
164
|
+
// Check if this looks like a base64 image and truncate it
|
|
165
|
+
if (stringified.includes('data:image/') && stringified.length > 1000) {
|
|
166
|
+
const match = stringified.match(/data:image\/([^;]+);base64,(.{0,100})/);
|
|
167
|
+
if (match) {
|
|
168
|
+
return `[Base64 Image: ${match[1]}, ${stringified.length} bytes total, truncated...]`;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Truncate any extremely large strings
|
|
173
|
+
const maxLength = 5000;
|
|
174
|
+
if (stringified.length > maxLength) {
|
|
175
|
+
return stringified.substring(0, maxLength) + `... [TRUNCATED: ${stringified.length} total bytes]`;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return stringified;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Also check for base64 strings directly
|
|
182
|
+
const strValue = String(arg);
|
|
183
|
+
if (strValue.includes('data:image/') && strValue.length > 1000) {
|
|
184
|
+
const match = strValue.match(/data:image\/([^;]+);base64,(.{0,100})/);
|
|
185
|
+
if (match) {
|
|
186
|
+
return `[Base64 Image: ${match[1]}, ${strValue.length} bytes total, truncated...]`;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Truncate any extremely large strings
|
|
191
|
+
if (strValue.length > 5000) {
|
|
192
|
+
return strValue.substring(0, 5000) + `... [TRUNCATED: ${strValue.length} total bytes]`;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return strValue;
|
|
196
|
+
} catch (e) {
|
|
197
|
+
return '[Object: could not serialize]';
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
// Build the message string
|
|
202
|
+
const messageString = serializedArgs.join(' ');
|
|
203
|
+
|
|
204
|
+
// Filter out internal Chrome Debug/ChromePilot logs
|
|
205
|
+
// These are internal debugging messages that should not be captured in recordings
|
|
206
|
+
if (messageString.startsWith('[Chrome Debug]') ||
|
|
207
|
+
messageString.startsWith('[ChromePilot]') ||
|
|
208
|
+
messageString.startsWith('[Console Injection]') ||
|
|
209
|
+
messageString.startsWith('[Console Cleanup]')) {
|
|
210
|
+
return; // Skip internal logs
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Post message to content script
|
|
214
|
+
window.postMessage({
|
|
215
|
+
type: messageType,
|
|
216
|
+
log: {
|
|
217
|
+
level,
|
|
218
|
+
message: messageString,
|
|
219
|
+
timestamp: Date.now()
|
|
220
|
+
}
|
|
221
|
+
}, '*');
|
|
222
|
+
} catch (e) {
|
|
223
|
+
// Ignore errors when sending logs
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
// Override console methods
|
|
228
|
+
const methods = ['log', 'error', 'warn', 'info', 'debug', 'trace', 'table', 'dir', 'group', 'groupEnd', 'time', 'timeEnd', 'count'];
|
|
229
|
+
methods.forEach(method => {
|
|
230
|
+
const original = window[originalConsoleName][method];
|
|
231
|
+
console[method] = (...args) => {
|
|
232
|
+
sendLog(method, args);
|
|
233
|
+
original.apply(console, args);
|
|
234
|
+
};
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
return 'console_installed';
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Injectable function for content script console relay
|
|
242
|
+
* Forwards console logs from MAIN world to background script
|
|
243
|
+
*
|
|
244
|
+
* @param {string} relayFlagName - Window property to track if relay is active
|
|
245
|
+
* @param {string} messageType - The postMessage type to listen for
|
|
246
|
+
* @param {string} backgroundAction - The action name to send to background script
|
|
247
|
+
*/
|
|
248
|
+
function injectConsoleRelay(relayFlagName, messageType, backgroundAction) {
|
|
249
|
+
// Check if relay is already active
|
|
250
|
+
if (window[relayFlagName]) {
|
|
251
|
+
console.log('[Chrome Debug] Console relay already active, skipping');
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Create and store the message handler
|
|
256
|
+
const messageHandler = (event) => {
|
|
257
|
+
if (event.data && event.data.type === messageType) {
|
|
258
|
+
// Forward to background script
|
|
259
|
+
chrome.runtime.sendMessage({
|
|
260
|
+
action: backgroundAction,
|
|
261
|
+
log: event.data.log
|
|
262
|
+
}).catch(() => {
|
|
263
|
+
// Background might not be available - that's OK
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
// Store reference for later removal
|
|
269
|
+
window[relayFlagName] = messageHandler;
|
|
270
|
+
|
|
271
|
+
// Listen for messages from main world
|
|
272
|
+
window.addEventListener('message', messageHandler);
|
|
273
|
+
console.log('[Chrome Debug] Console relay installed');
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Injectable function for MAIN world console cleanup
|
|
278
|
+
* Restores original console methods
|
|
279
|
+
*
|
|
280
|
+
* @param {string} overrideFlagName - Window property that tracks override status
|
|
281
|
+
* @param {string} originalConsoleName - Window property with stored original methods
|
|
282
|
+
* @returns {object} Status object
|
|
283
|
+
*/
|
|
284
|
+
function cleanupConsoleInterceptor(overrideFlagName, originalConsoleName) {
|
|
285
|
+
console.log('[Chrome Debug] ===== CLEANUP SCRIPT EXECUTING IN MAIN WORLD =====');
|
|
286
|
+
console.log('[Chrome Debug] ' + originalConsoleName + ' exists:', !!window[originalConsoleName]);
|
|
287
|
+
console.log('[Chrome Debug] ' + overrideFlagName + ':', window[overrideFlagName]);
|
|
288
|
+
|
|
289
|
+
if (window[originalConsoleName] && window[overrideFlagName]) {
|
|
290
|
+
console.log('[Chrome Debug] RESTORING original console methods');
|
|
291
|
+
|
|
292
|
+
// Restore all original console methods
|
|
293
|
+
Object.assign(console, window[originalConsoleName]);
|
|
294
|
+
|
|
295
|
+
// Clean up stored references
|
|
296
|
+
delete window[originalConsoleName];
|
|
297
|
+
delete window[overrideFlagName];
|
|
298
|
+
|
|
299
|
+
console.log('[Chrome Debug] ✅ Console methods restored successfully');
|
|
300
|
+
return { success: true, message: 'Console restored' };
|
|
301
|
+
} else {
|
|
302
|
+
console.warn('[Chrome Debug] ⚠️ No console override to restore');
|
|
303
|
+
return { success: false, message: 'No override found' };
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Injectable function for content script relay cleanup
|
|
309
|
+
* Removes the message event listener
|
|
310
|
+
*
|
|
311
|
+
* @param {string} relayFlagName - Window property with stored message handler
|
|
312
|
+
* @returns {object} Status object
|
|
313
|
+
*/
|
|
314
|
+
function cleanupConsoleRelay(relayFlagName) {
|
|
315
|
+
console.log('[Chrome Debug] ===== CLEANUP SCRIPT EXECUTING IN CONTENT SCRIPT =====');
|
|
316
|
+
console.log('[Chrome Debug] ' + relayFlagName + ' exists:', !!window[relayFlagName]);
|
|
317
|
+
|
|
318
|
+
if (window[relayFlagName]) {
|
|
319
|
+
console.log('[Chrome Debug] REMOVING console relay listener');
|
|
320
|
+
window.removeEventListener('message', window[relayFlagName]);
|
|
321
|
+
delete window[relayFlagName];
|
|
322
|
+
console.log('[Chrome Debug] ✅ Console relay removed successfully');
|
|
323
|
+
return { success: true, message: 'Relay removed' };
|
|
324
|
+
} else {
|
|
325
|
+
console.warn('[Chrome Debug] ⚠️ No relay to remove');
|
|
326
|
+
return { success: false, message: 'No relay found' };
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* High-level API for starting console interception
|
|
332
|
+
*
|
|
333
|
+
* @param {number} tabId - Chrome tab ID
|
|
334
|
+
* @param {object} config - Configuration object
|
|
335
|
+
* @param {string} config.overrideFlagName - Window property for override tracking
|
|
336
|
+
* @param {string} config.originalConsoleName - Window property for original methods
|
|
337
|
+
* @param {string} config.relayFlagName - Window property for relay handler
|
|
338
|
+
* @param {string} config.messageType - PostMessage type for log relay
|
|
339
|
+
* @param {string} config.backgroundAction - Background script action name
|
|
340
|
+
* @returns {Promise<boolean>} True if successful, false otherwise
|
|
341
|
+
*/
|
|
342
|
+
async function startConsoleInterception(tabId, config) {
|
|
343
|
+
try {
|
|
344
|
+
// Check if this tab allows content script injection
|
|
345
|
+
const tab = await chrome.tabs.get(tabId);
|
|
346
|
+
if (!tab.url || tab.url.startsWith('chrome://') || tab.url.startsWith('chrome-extension://') || tab.url.startsWith('moz-extension://')) {
|
|
347
|
+
console.log('Cannot inject console logger into restricted URL:', tab.url);
|
|
348
|
+
console.warn('WARNING: Console logs cannot be captured on restricted pages');
|
|
349
|
+
return false;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// Inject MAIN world console interceptor
|
|
353
|
+
const results = await chrome.scripting.executeScript({
|
|
354
|
+
target: { tabId: tabId },
|
|
355
|
+
world: 'MAIN',
|
|
356
|
+
func: injectConsoleInterceptor,
|
|
357
|
+
args: [config.overrideFlagName, config.originalConsoleName, config.messageType]
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
console.log('[Console Injection] MAIN world script injected:', results);
|
|
361
|
+
|
|
362
|
+
// Inject content script relay
|
|
363
|
+
await chrome.scripting.executeScript({
|
|
364
|
+
target: { tabId: tabId },
|
|
365
|
+
func: injectConsoleRelay,
|
|
366
|
+
args: [config.relayFlagName, config.messageType, config.backgroundAction]
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
console.log('[Console Injection] Content script relay injected successfully');
|
|
370
|
+
return true;
|
|
371
|
+
} catch (error) {
|
|
372
|
+
console.error('[Console Injection] Failed:', error);
|
|
373
|
+
return false;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* High-level API for stopping console interception and cleanup
|
|
379
|
+
*
|
|
380
|
+
* @param {number} tabId - Chrome tab ID
|
|
381
|
+
* @param {object} config - Configuration object (same as startConsoleInterception)
|
|
382
|
+
* @returns {Promise<boolean>} True if successful, false otherwise
|
|
383
|
+
*/
|
|
384
|
+
async function stopConsoleInterception(tabId, config) {
|
|
385
|
+
try {
|
|
386
|
+
console.log('[Console Cleanup] Starting cleanup for tab:', tabId);
|
|
387
|
+
|
|
388
|
+
// Check if this tab allows content script injection
|
|
389
|
+
const tab = await chrome.tabs.get(tabId);
|
|
390
|
+
if (!tab.url || tab.url.startsWith('chrome://') || tab.url.startsWith('chrome-extension://')) {
|
|
391
|
+
console.warn('[Console Cleanup] Cannot inject into restricted URL:', tab.url);
|
|
392
|
+
return false;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// Restore console methods in MAIN world
|
|
396
|
+
const mainWorldResult = await chrome.scripting.executeScript({
|
|
397
|
+
target: { tabId: tabId },
|
|
398
|
+
world: 'MAIN',
|
|
399
|
+
func: cleanupConsoleInterceptor,
|
|
400
|
+
args: [config.overrideFlagName, config.originalConsoleName]
|
|
401
|
+
});
|
|
402
|
+
console.log('[Console Cleanup] MAIN world result:', mainWorldResult[0]?.result);
|
|
403
|
+
|
|
404
|
+
// Remove relay in content script
|
|
405
|
+
const contentScriptResult = await chrome.scripting.executeScript({
|
|
406
|
+
target: { tabId: tabId },
|
|
407
|
+
func: cleanupConsoleRelay,
|
|
408
|
+
args: [config.relayFlagName]
|
|
409
|
+
});
|
|
410
|
+
console.log('[Console Cleanup] Content script result:', contentScriptResult[0]?.result);
|
|
411
|
+
|
|
412
|
+
console.log('[Console Cleanup] Cleanup complete');
|
|
413
|
+
return true;
|
|
414
|
+
} catch (error) {
|
|
415
|
+
console.error('[Console Cleanup] Failed:', error);
|
|
416
|
+
return false;
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// Export for use in Chrome extension background script (service worker context)
|
|
421
|
+
// Direct assignment without wrapper to avoid importScripts NetworkError
|
|
422
|
+
self.ConsoleInterceptionLibrary = {
|
|
423
|
+
injectConsoleInterceptor,
|
|
424
|
+
injectConsoleRelay,
|
|
425
|
+
cleanupConsoleInterceptor,
|
|
426
|
+
cleanupConsoleRelay,
|
|
427
|
+
startConsoleInterception,
|
|
428
|
+
stopConsoleInterception
|
|
429
|
+
};
|
|
430
|
+
|