@dynamicu/chromedebug-mcp 2.7.1 → 2.7.2
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/chrome-extension/background.js +22 -8
- package/chrome-extension/manifest.free.json +1 -1
- package/chrome-extension/options.html +2 -2
- package/chrome-extension/options.js +4 -4
- package/dist/chromedebug-extension-free.zip +0 -0
- package/package.json +1 -1
- package/src/database.js +69 -0
- package/src/validation/schemas.js +9 -4
|
@@ -1375,12 +1375,26 @@ chrome.runtime.onInstalled.addListener(async (details) => {
|
|
|
1375
1375
|
chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
|
|
1376
1376
|
// Inject console logging on navigation during recording
|
|
1377
1377
|
if (changeInfo.status === 'loading') {
|
|
1378
|
-
//
|
|
1378
|
+
// Skip restricted URLs
|
|
1379
|
+
if (!tab.url || tab.url.startsWith('chrome://') || tab.url.startsWith('chrome-extension://')) {
|
|
1380
|
+
return;
|
|
1381
|
+
}
|
|
1382
|
+
|
|
1383
|
+
// Check for WORKFLOW recording first (takes priority)
|
|
1384
|
+
if (workflowRecordingTabs.has(tabId) && workflowIncludeLogs.get(tabId)) {
|
|
1385
|
+
// console.log('[Navigation] Re-injecting workflow console logging for tab:', tabId);
|
|
1386
|
+
self.ConsoleInterceptionLibrary.startConsoleInterception(tabId, WORKFLOW_RECORDING_CONSOLE_CONFIG).catch(err => {
|
|
1387
|
+
console.error('Failed to re-inject workflow console logging:', err);
|
|
1388
|
+
});
|
|
1389
|
+
return; // Don't also inject screen recording logs
|
|
1390
|
+
}
|
|
1391
|
+
|
|
1392
|
+
// Check for SCREEN recording
|
|
1379
1393
|
if (sessionManager) {
|
|
1380
1394
|
isCurrentlyRecordingAsync().then(async (recording) => {
|
|
1381
1395
|
const currentTabId = await getCurrentTabIdAsync();
|
|
1382
|
-
if (recording && tabId === currentTabId
|
|
1383
|
-
// console.log('Re-injecting console logging for
|
|
1396
|
+
if (recording && tabId === currentTabId) {
|
|
1397
|
+
// console.log('[Navigation] Re-injecting screen recording console logging for tab:', tabId);
|
|
1384
1398
|
startCapturingLogs(tabId).catch(err => {
|
|
1385
1399
|
console.error('Failed to re-inject console logging:', err);
|
|
1386
1400
|
});
|
|
@@ -1389,9 +1403,9 @@ chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
|
|
|
1389
1403
|
console.error('Error checking recording state for navigation:', error);
|
|
1390
1404
|
});
|
|
1391
1405
|
} else {
|
|
1392
|
-
// Legacy fallback
|
|
1393
|
-
if (tabId === recordingTabId && isCurrentlyRecording
|
|
1394
|
-
// console.log('Re-injecting console logging for
|
|
1406
|
+
// Legacy fallback for screen recording
|
|
1407
|
+
if (tabId === recordingTabId && isCurrentlyRecording) {
|
|
1408
|
+
// console.log('[Navigation] Re-injecting screen recording console logging (legacy) for tab:', tabId);
|
|
1395
1409
|
startCapturingLogs(tabId).catch(err => {
|
|
1396
1410
|
console.error('Failed to re-inject console logging:', err);
|
|
1397
1411
|
});
|
|
@@ -1522,7 +1536,7 @@ let frameCounter = new Map(); // DEPRECATED: Session manager handles frame count
|
|
|
1522
1536
|
// =============================================================================
|
|
1523
1537
|
// INACTIVITY AUTO-STOP - Stop recording if user is inactive
|
|
1524
1538
|
// =============================================================================
|
|
1525
|
-
const DEFAULT_INACTIVITY_TIMEOUT_MS =
|
|
1539
|
+
const DEFAULT_INACTIVITY_TIMEOUT_MS = 60 * 1000; // 60 seconds default (1 minute)
|
|
1526
1540
|
let lastUserActivityTime = Date.now();
|
|
1527
1541
|
let inactivityCheckInterval = null;
|
|
1528
1542
|
let currentInactivityTimeout = DEFAULT_INACTIVITY_TIMEOUT_MS;
|
|
@@ -1546,7 +1560,7 @@ async function startInactivityMonitoring(tabId) {
|
|
|
1546
1560
|
|
|
1547
1561
|
// Load user's configured timeout from storage
|
|
1548
1562
|
const settings = await chrome.storage.sync.get(['inactivityTimeout']);
|
|
1549
|
-
currentInactivityTimeout = (settings.inactivityTimeout ||
|
|
1563
|
+
currentInactivityTimeout = (settings.inactivityTimeout || 60) * 1000; // Convert seconds to ms (default 60s)
|
|
1550
1564
|
|
|
1551
1565
|
lastUserActivityTime = Date.now();
|
|
1552
1566
|
|
|
@@ -156,10 +156,10 @@
|
|
|
156
156
|
<div class="setting-group">
|
|
157
157
|
<h3>Recording Safety</h3>
|
|
158
158
|
<label for="inactivity-timeout">Auto-Stop After Inactivity (seconds)</label>
|
|
159
|
-
<input type="number" id="inactivity-timeout" min="5" max="3600" value="
|
|
159
|
+
<input type="number" id="inactivity-timeout" min="5" max="3600" value="60">
|
|
160
160
|
<div class="help-text">
|
|
161
161
|
Screen recording will automatically stop if no mouse or keyboard activity is detected.
|
|
162
|
-
Default:
|
|
162
|
+
Default: 60 seconds. Adjust for long demo recordings or presentations.
|
|
163
163
|
</div>
|
|
164
164
|
</div>
|
|
165
165
|
|
|
@@ -9,8 +9,8 @@ chrome.storage.sync.get(['serverPort', 'chromePilotMode', 'chromePilotAllowedSit
|
|
|
9
9
|
document.getElementById('server-port').value = data.serverPort;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
// Load inactivity timeout (default
|
|
13
|
-
document.getElementById('inactivity-timeout').value = data.inactivityTimeout ||
|
|
12
|
+
// Load inactivity timeout (default 60 seconds)
|
|
13
|
+
document.getElementById('inactivity-timeout').value = data.inactivityTimeout || 60;
|
|
14
14
|
|
|
15
15
|
// Load site restriction settings
|
|
16
16
|
const mode = data.chromePilotMode || 'whitelist';
|
|
@@ -38,7 +38,7 @@ document.getElementById('save').addEventListener('click', async () => {
|
|
|
38
38
|
|
|
39
39
|
// Get inactivity timeout
|
|
40
40
|
const inactivityTimeoutInput = document.getElementById('inactivity-timeout');
|
|
41
|
-
const inactivityTimeout = parseInt(inactivityTimeoutInput.value) ||
|
|
41
|
+
const inactivityTimeout = parseInt(inactivityTimeoutInput.value) || 60;
|
|
42
42
|
if (inactivityTimeout < 5 || inactivityTimeout > 3600) {
|
|
43
43
|
showStatus('error', 'Inactivity timeout must be between 5 and 3600 seconds');
|
|
44
44
|
return;
|
|
@@ -68,7 +68,7 @@ document.getElementById('save').addEventListener('click', async () => {
|
|
|
68
68
|
// Reset to default
|
|
69
69
|
document.getElementById('reset').addEventListener('click', async () => {
|
|
70
70
|
document.getElementById('server-port').value = '';
|
|
71
|
-
document.getElementById('inactivity-timeout').value = '
|
|
71
|
+
document.getElementById('inactivity-timeout').value = '60';
|
|
72
72
|
document.querySelector('input[value="whitelist"]').checked = true;
|
|
73
73
|
document.getElementById('allowed-sites').value = 'localhost:*\n127.0.0.1:*\n*.local\n*.test\n*.dev';
|
|
74
74
|
document.getElementById('blocked-sites').value = 'youtube.com\n*.youtube.com\ngoogle.com\n*.google.com\nfacebook.com\ntwitter.com\nx.com';
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dynamicu/chromedebug-mcp",
|
|
3
|
-
"version": "2.7.
|
|
3
|
+
"version": "2.7.2",
|
|
4
4
|
"description": "ChromeDebug MCP - MCP server that provides full control over a Chrome browser instance for debugging and automation with AI assistants like Claude Code",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"type": "module",
|
package/src/database.js
CHANGED
|
@@ -132,9 +132,20 @@ class ChromeDebugDatabase {
|
|
|
132
132
|
// Migrate from old location if needed
|
|
133
133
|
this.migrateIfNeeded();
|
|
134
134
|
|
|
135
|
+
// Check if database exists and is corrupted
|
|
136
|
+
if (fs.existsSync(this.dbPath)) {
|
|
137
|
+
const integrityStatus = this.checkDatabaseIntegrity();
|
|
138
|
+
if (!integrityStatus.ok) {
|
|
139
|
+
logger.warn(`[Database] Database corruption detected: ${integrityStatus.error}`);
|
|
140
|
+
this.handleCorruptedDatabase();
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
135
144
|
// Open database connection
|
|
136
145
|
this.db = new Database(this.dbPath);
|
|
137
146
|
this.db.pragma('journal_mode = WAL'); // Enable WAL mode for better concurrent access
|
|
147
|
+
this.db.pragma('synchronous = NORMAL'); // Balance between safety and performance
|
|
148
|
+
this.db.pragma('foreign_keys = ON'); // Enforce foreign key constraints
|
|
138
149
|
|
|
139
150
|
// Create tables
|
|
140
151
|
this.createTables();
|
|
@@ -142,6 +153,64 @@ class ChromeDebugDatabase {
|
|
|
142
153
|
logger.debug(`ChromeDebug MCP database initialized at: ${this.dbPath}`);
|
|
143
154
|
}
|
|
144
155
|
|
|
156
|
+
/**
|
|
157
|
+
* Check database integrity before opening
|
|
158
|
+
* @returns {{ ok: boolean, error?: string }}
|
|
159
|
+
*/
|
|
160
|
+
checkDatabaseIntegrity() {
|
|
161
|
+
try {
|
|
162
|
+
const tempDb = new Database(this.dbPath, { readonly: true });
|
|
163
|
+
const result = tempDb.pragma('integrity_check');
|
|
164
|
+
tempDb.close();
|
|
165
|
+
|
|
166
|
+
if (result && result[0] && result[0].integrity_check === 'ok') {
|
|
167
|
+
return { ok: true };
|
|
168
|
+
} else {
|
|
169
|
+
const errorMsg = result?.[0]?.integrity_check || 'Unknown integrity error';
|
|
170
|
+
return { ok: false, error: errorMsg };
|
|
171
|
+
}
|
|
172
|
+
} catch (error) {
|
|
173
|
+
return { ok: false, error: error.message };
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Handle corrupted database by backing it up and creating a fresh one
|
|
179
|
+
*/
|
|
180
|
+
handleCorruptedDatabase() {
|
|
181
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
182
|
+
const backupPath = `${this.dbPath}.corrupted_${timestamp}`;
|
|
183
|
+
|
|
184
|
+
logger.warn(`[Database] Backing up corrupted database to: ${backupPath}`);
|
|
185
|
+
|
|
186
|
+
try {
|
|
187
|
+
// Move corrupted database and associated files
|
|
188
|
+
if (fs.existsSync(this.dbPath)) {
|
|
189
|
+
fs.renameSync(this.dbPath, backupPath);
|
|
190
|
+
}
|
|
191
|
+
if (fs.existsSync(`${this.dbPath}-shm`)) {
|
|
192
|
+
fs.renameSync(`${this.dbPath}-shm`, `${backupPath}-shm`);
|
|
193
|
+
}
|
|
194
|
+
if (fs.existsSync(`${this.dbPath}-wal`)) {
|
|
195
|
+
fs.renameSync(`${this.dbPath}-wal`, `${backupPath}-wal`);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
logger.info(`[Database] Corrupted database backed up. A fresh database will be created.`);
|
|
199
|
+
} catch (error) {
|
|
200
|
+
logger.error(`[Database] Failed to backup corrupted database: ${error.message}`);
|
|
201
|
+
// Try to delete instead of rename
|
|
202
|
+
try {
|
|
203
|
+
fs.unlinkSync(this.dbPath);
|
|
204
|
+
if (fs.existsSync(`${this.dbPath}-shm`)) fs.unlinkSync(`${this.dbPath}-shm`);
|
|
205
|
+
if (fs.existsSync(`${this.dbPath}-wal`)) fs.unlinkSync(`${this.dbPath}-wal`);
|
|
206
|
+
logger.info(`[Database] Corrupted database removed. A fresh database will be created.`);
|
|
207
|
+
} catch (deleteError) {
|
|
208
|
+
logger.error(`[Database] Failed to remove corrupted database: ${deleteError.message}`);
|
|
209
|
+
throw new Error(`Cannot recover from database corruption: ${deleteError.message}`);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
145
214
|
createTables() {
|
|
146
215
|
// Recordings table for frame capture sessions
|
|
147
216
|
this.db.exec(`
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import Joi from 'joi';
|
|
2
2
|
|
|
3
|
+
// Valid console log levels - must match what Chrome extension captures
|
|
4
|
+
// Chrome extension captures: log, error, warn, info, debug, trace, table, dir, group, groupEnd, time, timeEnd, count
|
|
5
|
+
// See: chrome-extension/console-interception-library.js line 228
|
|
6
|
+
const VALID_LOG_LEVELS = ['log', 'info', 'warn', 'error', 'debug', 'trace', 'table', 'dir', 'group', 'groupEnd', 'time', 'timeEnd', 'count'];
|
|
7
|
+
|
|
3
8
|
// Common validation patterns
|
|
4
9
|
const patterns = {
|
|
5
10
|
sessionId: Joi.string().pattern(/^[a-zA-Z0-9_-]+$/).min(1).max(100),
|
|
@@ -53,7 +58,7 @@ export const workflowRecordingSchema = Joi.object({
|
|
|
53
58
|
).required(),
|
|
54
59
|
logs: Joi.array().items(
|
|
55
60
|
Joi.object({
|
|
56
|
-
level: Joi.string().valid(
|
|
61
|
+
level: Joi.string().valid(...VALID_LOG_LEVELS).required(),
|
|
57
62
|
message: Joi.string().required(),
|
|
58
63
|
timestamp: Joi.number().required(),
|
|
59
64
|
args: Joi.array().optional()
|
|
@@ -93,7 +98,7 @@ export const frameBatchSchema = Joi.object({
|
|
|
93
98
|
index: Joi.number().integer().min(0).optional(), // Chrome extension sends this
|
|
94
99
|
logs: Joi.array().items(
|
|
95
100
|
Joi.object({
|
|
96
|
-
level: Joi.string().valid(
|
|
101
|
+
level: Joi.string().valid(...VALID_LOG_LEVELS).required(),
|
|
97
102
|
message: Joi.string().required(),
|
|
98
103
|
timestamp: Joi.number().required(),
|
|
99
104
|
args: Joi.array().optional()
|
|
@@ -121,7 +126,7 @@ export const associateLogsSchema = Joi.object({
|
|
|
121
126
|
sessionId: patterns.sessionId.required(),
|
|
122
127
|
logs: Joi.array().items(
|
|
123
128
|
Joi.object({
|
|
124
|
-
level: Joi.string().valid(
|
|
129
|
+
level: Joi.string().valid(...VALID_LOG_LEVELS).required(),
|
|
125
130
|
message: Joi.string().required(),
|
|
126
131
|
timestamp: Joi.number().required(),
|
|
127
132
|
args: Joi.array().optional()
|
|
@@ -134,7 +139,7 @@ export const streamLogsSchema = Joi.object({
|
|
|
134
139
|
sessionId: patterns.sessionId.required(),
|
|
135
140
|
logs: Joi.array().items(
|
|
136
141
|
Joi.object({
|
|
137
|
-
level: Joi.string().valid(
|
|
142
|
+
level: Joi.string().valid(...VALID_LOG_LEVELS).required(),
|
|
138
143
|
message: Joi.string().required(),
|
|
139
144
|
timestamp: Joi.number().required(),
|
|
140
145
|
sequence: Joi.number().integer().min(0).required(),
|