@dynamicu/chromedebug-mcp 2.2.2 → 2.3.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/chrome-extension/README.md +41 -0
- package/chrome-extension/background.js +5 -5
- package/chrome-extension/manifest.free.json +72 -0
- package/dist/chromedebug-extension-free.zip +0 -0
- package/package.json +10 -3
- package/scripts/package-extension.js +73 -0
- package/scripts/postinstall.js +51 -0
- package/src/chrome-controller.js +107 -106
- package/src/config-loader.js +18 -17
- package/src/database.js +81 -80
- package/src/index.js +35 -24
- package/src/middleware/auth.js +30 -29
- package/src/port-discovery.js +7 -6
- package/src/services/failover-manager.js +19 -18
- package/src/services/project-manager.js +16 -15
- package/src/utils/logger.js +63 -0
package/src/chrome-controller.js
CHANGED
|
@@ -7,6 +7,7 @@ import { SessionManager } from './services/session-manager.js';
|
|
|
7
7
|
import { BrowserDaemon } from './services/browser-daemon.js';
|
|
8
8
|
import { FailoverManager } from './services/failover-manager.js';
|
|
9
9
|
import { getExtensionPath } from './utils/extension-path.js';
|
|
10
|
+
import logger from './utils/logger.js';
|
|
10
11
|
|
|
11
12
|
export class ChromeController {
|
|
12
13
|
constructor() {
|
|
@@ -104,10 +105,10 @@ export class ChromeController {
|
|
|
104
105
|
this.setActiveTab(targetId);
|
|
105
106
|
}
|
|
106
107
|
|
|
107
|
-
|
|
108
|
+
logger.debug(`[TabRegistry] Registered tab ${targetId} for session ${this.sessionId}`);
|
|
108
109
|
return tabData;
|
|
109
110
|
} catch (error) {
|
|
110
|
-
|
|
111
|
+
logger.error(`[TabRegistry] Failed to register tab:`, error);
|
|
111
112
|
return null;
|
|
112
113
|
}
|
|
113
114
|
}
|
|
@@ -120,7 +121,7 @@ export class ChromeController {
|
|
|
120
121
|
if (tabData) {
|
|
121
122
|
this.activeTargetId = targetId;
|
|
122
123
|
this.page = tabData.page; // Maintain backward compatibility
|
|
123
|
-
|
|
124
|
+
logger.debug(`[TabRegistry] Active tab set to ${targetId}`);
|
|
124
125
|
return true;
|
|
125
126
|
}
|
|
126
127
|
return false;
|
|
@@ -158,7 +159,7 @@ export class ChromeController {
|
|
|
158
159
|
const removed = this.tabRegistry.delete(targetId);
|
|
159
160
|
|
|
160
161
|
if (removed) {
|
|
161
|
-
|
|
162
|
+
logger.debug(`[TabRegistry] Unregistered tab ${targetId}`);
|
|
162
163
|
|
|
163
164
|
// If this was the active tab, select a new active tab
|
|
164
165
|
if (wasActive) {
|
|
@@ -182,7 +183,7 @@ export class ChromeController {
|
|
|
182
183
|
this.tabRegistry.clear();
|
|
183
184
|
this.activeTargetId = null;
|
|
184
185
|
this.page = null;
|
|
185
|
-
|
|
186
|
+
logger.debug(`[TabRegistry] Cleared all tabs for session ${this.sessionId}`);
|
|
186
187
|
}
|
|
187
188
|
|
|
188
189
|
/**
|
|
@@ -208,7 +209,7 @@ export class ChromeController {
|
|
|
208
209
|
// Only handle page targets (tabs)
|
|
209
210
|
if (targetInfo.type === 'page') {
|
|
210
211
|
try {
|
|
211
|
-
|
|
212
|
+
logger.debug(`[TargetEvents] New tab created: ${targetInfo.targetId}`);
|
|
212
213
|
|
|
213
214
|
// Wait a bit for the page to be ready
|
|
214
215
|
setTimeout(async () => {
|
|
@@ -224,11 +225,11 @@ export class ChromeController {
|
|
|
224
225
|
await this.registerTab(newPage, targetInfo.targetId);
|
|
225
226
|
}
|
|
226
227
|
} catch (error) {
|
|
227
|
-
|
|
228
|
+
logger.error(`[TargetEvents] Error registering new tab:`, error);
|
|
228
229
|
}
|
|
229
230
|
}, 100);
|
|
230
231
|
} catch (error) {
|
|
231
|
-
|
|
232
|
+
logger.error(`[TargetEvents] Error handling target creation:`, error);
|
|
232
233
|
}
|
|
233
234
|
}
|
|
234
235
|
});
|
|
@@ -236,7 +237,7 @@ export class ChromeController {
|
|
|
236
237
|
// Listen for tab destruction
|
|
237
238
|
browserClient.on('Target.targetDestroyed', (params) => {
|
|
238
239
|
const { targetId } = params;
|
|
239
|
-
|
|
240
|
+
logger.debug(`[TargetEvents] Tab destroyed: ${targetId}`);
|
|
240
241
|
this.unregisterTab(targetId);
|
|
241
242
|
});
|
|
242
243
|
|
|
@@ -249,15 +250,15 @@ export class ChromeController {
|
|
|
249
250
|
if (tabData) {
|
|
250
251
|
tabData.url = targetInfo.url;
|
|
251
252
|
tabData.title = targetInfo.title || 'Untitled';
|
|
252
|
-
|
|
253
|
+
logger.debug(`[TargetEvents] Tab info updated: ${targetInfo.targetId} - ${targetInfo.title}`);
|
|
253
254
|
}
|
|
254
255
|
}
|
|
255
256
|
});
|
|
256
257
|
|
|
257
258
|
this.targetEventListenersSetup = true;
|
|
258
|
-
|
|
259
|
+
logger.debug(`[TabRegistry] Target event listeners setup complete for session ${this.sessionId}`);
|
|
259
260
|
} catch (error) {
|
|
260
|
-
|
|
261
|
+
logger.error(`[TabRegistry] Failed to setup target event listeners:`, error);
|
|
261
262
|
}
|
|
262
263
|
}
|
|
263
264
|
|
|
@@ -266,13 +267,13 @@ export class ChromeController {
|
|
|
266
267
|
*/
|
|
267
268
|
async initializeTabRegistry() {
|
|
268
269
|
if (!this.browser) {
|
|
269
|
-
|
|
270
|
+
logger.warn(`[TabRegistry] Cannot initialize - no browser connection`);
|
|
270
271
|
return;
|
|
271
272
|
}
|
|
272
273
|
|
|
273
274
|
try {
|
|
274
275
|
const pages = await this.browser.pages();
|
|
275
|
-
|
|
276
|
+
logger.debug(`[TabRegistry] Initializing with ${pages.length} existing tabs`);
|
|
276
277
|
|
|
277
278
|
for (const page of pages) {
|
|
278
279
|
await this.registerTab(page);
|
|
@@ -281,9 +282,9 @@ export class ChromeController {
|
|
|
281
282
|
// Set up event listeners after registry initialization
|
|
282
283
|
await this.setupTargetEventListeners();
|
|
283
284
|
|
|
284
|
-
|
|
285
|
+
logger.debug(`[TabRegistry] Initialization complete - ${this.tabRegistry.size} tabs registered`);
|
|
285
286
|
} catch (error) {
|
|
286
|
-
|
|
287
|
+
logger.error(`[TabRegistry] Failed to initialize tab registry:`, error);
|
|
287
288
|
}
|
|
288
289
|
}
|
|
289
290
|
|
|
@@ -323,7 +324,7 @@ export class ChromeController {
|
|
|
323
324
|
}
|
|
324
325
|
}
|
|
325
326
|
} catch (error) {
|
|
326
|
-
|
|
327
|
+
logger.error(`[TabRegistry] Error finding valid page:`, error);
|
|
327
328
|
}
|
|
328
329
|
}
|
|
329
330
|
|
|
@@ -373,7 +374,7 @@ export class ChromeController {
|
|
|
373
374
|
if (!options.allowCrossSession) {
|
|
374
375
|
throw new Error(`Cross-session tab access denied. Tab belongs to session ${tabData.sessionId}, current session is ${this.sessionId}`);
|
|
375
376
|
}
|
|
376
|
-
|
|
377
|
+
logger.warn(`[TabRegistry] Cross-session tab switch: ${tabData.sessionId} → ${this.sessionId}`);
|
|
377
378
|
}
|
|
378
379
|
|
|
379
380
|
// Verify the page is still valid
|
|
@@ -385,7 +386,7 @@ export class ChromeController {
|
|
|
385
386
|
// Switch to the tab
|
|
386
387
|
const success = this.setActiveTab(targetId);
|
|
387
388
|
if (success) {
|
|
388
|
-
|
|
389
|
+
logger.debug(`[TabRegistry] Switched to tab ${targetId} (Session: ${tabData.sessionId})`);
|
|
389
390
|
return {
|
|
390
391
|
success: true,
|
|
391
392
|
targetId,
|
|
@@ -416,7 +417,7 @@ export class ChromeController {
|
|
|
416
417
|
const tabData = await this.registerTab(newPage);
|
|
417
418
|
|
|
418
419
|
if (tabData) {
|
|
419
|
-
|
|
420
|
+
logger.debug(`[TabRegistry] Created new tab ${tabData.targetId} for session ${this.sessionId}`);
|
|
420
421
|
return {
|
|
421
422
|
success: true,
|
|
422
423
|
targetId: tabData.targetId,
|
|
@@ -427,7 +428,7 @@ export class ChromeController {
|
|
|
427
428
|
|
|
428
429
|
throw new Error('Failed to register new tab');
|
|
429
430
|
} catch (error) {
|
|
430
|
-
|
|
431
|
+
logger.error(`[TabRegistry] Error creating new tab:`, error);
|
|
431
432
|
throw error;
|
|
432
433
|
}
|
|
433
434
|
}
|
|
@@ -442,14 +443,14 @@ export class ChromeController {
|
|
|
442
443
|
// Security: Validate that processId is a valid number and contains no suspicious characters
|
|
443
444
|
const pidStr = String(this.processId).trim();
|
|
444
445
|
if (!/^\d+$/.test(pidStr)) {
|
|
445
|
-
|
|
446
|
+
logger.error('Invalid or suspicious process ID for Chrome process:', this.processId);
|
|
446
447
|
this.processId = null;
|
|
447
448
|
return;
|
|
448
449
|
}
|
|
449
450
|
|
|
450
451
|
const pid = parseInt(pidStr, 10);
|
|
451
452
|
if (isNaN(pid) || pid <= 0 || pid > 4194304) {
|
|
452
|
-
|
|
453
|
+
logger.error('Invalid process ID for Chrome process:', this.processId);
|
|
453
454
|
this.processId = null;
|
|
454
455
|
return;
|
|
455
456
|
}
|
|
@@ -478,7 +479,7 @@ export class ChromeController {
|
|
|
478
479
|
if (error.code === 'ESRCH') {
|
|
479
480
|
// Process already dead, ignore
|
|
480
481
|
} else if (error.code === 'EPERM') {
|
|
481
|
-
|
|
482
|
+
logger.error('Permission denied killing Chrome process:', pid);
|
|
482
483
|
} else {
|
|
483
484
|
throw error;
|
|
484
485
|
}
|
|
@@ -487,7 +488,7 @@ export class ChromeController {
|
|
|
487
488
|
this.processId = null;
|
|
488
489
|
}
|
|
489
490
|
} catch (error) {
|
|
490
|
-
|
|
491
|
+
logger.error('Error killing Chrome process:', error.message);
|
|
491
492
|
}
|
|
492
493
|
}
|
|
493
494
|
|
|
@@ -513,7 +514,7 @@ export class ChromeController {
|
|
|
513
514
|
try {
|
|
514
515
|
// Use Node.js fs.rm() instead of shell commands to prevent injection
|
|
515
516
|
await fs.rm(resolvedPath, { recursive: true, force: true });
|
|
516
|
-
|
|
517
|
+
logger.error(`Cleaned up directory: ${resolvedPath}`);
|
|
517
518
|
} catch (error) {
|
|
518
519
|
if (error.code === 'ENOENT') {
|
|
519
520
|
// Directory doesn't exist, ignore
|
|
@@ -542,23 +543,23 @@ export class ChromeController {
|
|
|
542
543
|
try {
|
|
543
544
|
process.kill(pid, 0);
|
|
544
545
|
// Process exists, skip this directory
|
|
545
|
-
|
|
546
|
+
logger.error(`Skipping active ChromeDebug MCP directory: ${dir} (PID ${pid} is still running)`);
|
|
546
547
|
} catch (e) {
|
|
547
548
|
if (e.code === 'ESRCH') {
|
|
548
549
|
// Process doesn't exist, safe to clean up
|
|
549
550
|
const dirPath = path.join(tmpDir, dir);
|
|
550
|
-
|
|
551
|
+
logger.error(`Cleaning up stale ChromeDebug MCP directory: ${dir} (PID ${pid} no longer exists)`);
|
|
551
552
|
await fs.rm(dirPath, { recursive: true, force: true });
|
|
552
553
|
}
|
|
553
554
|
}
|
|
554
555
|
}
|
|
555
556
|
}
|
|
556
557
|
} catch (error) {
|
|
557
|
-
|
|
558
|
+
logger.error(`Error processing directory ${dir}:`, error.message);
|
|
558
559
|
}
|
|
559
560
|
}
|
|
560
561
|
} catch (error) {
|
|
561
|
-
|
|
562
|
+
logger.error('Error during stale profile cleanup:', error.message);
|
|
562
563
|
}
|
|
563
564
|
}
|
|
564
565
|
|
|
@@ -584,7 +585,7 @@ export class ChromeController {
|
|
|
584
585
|
return sharedResult;
|
|
585
586
|
}
|
|
586
587
|
} catch (error) {
|
|
587
|
-
|
|
588
|
+
logger.warn('[ChromeController] Shared browser unavailable, falling back to individual browser:', error.message);
|
|
588
589
|
}
|
|
589
590
|
|
|
590
591
|
// Fallback to individual browser launch
|
|
@@ -594,9 +595,9 @@ export class ChromeController {
|
|
|
594
595
|
if (sessionData) {
|
|
595
596
|
try {
|
|
596
597
|
await this.sessionManager.forceCleanupSession(sessionData.sessionId);
|
|
597
|
-
|
|
598
|
+
logger.error(`Cleaned up session after launch failure: ${sessionData.sessionId}`);
|
|
598
599
|
} catch (cleanupError) {
|
|
599
|
-
|
|
600
|
+
logger.error(`Failed to clean up session: ${sessionData.sessionId}`, cleanupError.message);
|
|
600
601
|
}
|
|
601
602
|
}
|
|
602
603
|
|
|
@@ -616,13 +617,13 @@ export class ChromeController {
|
|
|
616
617
|
* @returns {Promise<Object>} Connection result
|
|
617
618
|
*/
|
|
618
619
|
async connectToSharedBrowser() {
|
|
619
|
-
|
|
620
|
+
logger.debug('[ChromeController] Attempting to connect to shared browser daemon...');
|
|
620
621
|
|
|
621
622
|
// Check if we should use fallback based on failover manager
|
|
622
623
|
const shouldUseFallback = await this.failoverManager.shouldUseFallback('connect');
|
|
623
624
|
if (shouldUseFallback) {
|
|
624
625
|
// Instead of immediately failing, try to auto-start a daemon if none exists
|
|
625
|
-
|
|
626
|
+
logger.debug('[ChromeController] Failover manager recommends fallback, but checking for auto-start opportunity...');
|
|
626
627
|
}
|
|
627
628
|
|
|
628
629
|
// Try to find a running daemon by checking common ports
|
|
@@ -657,7 +658,7 @@ export class ChromeController {
|
|
|
657
658
|
const isListening = await checkPort();
|
|
658
659
|
if (isListening) {
|
|
659
660
|
daemonPort = port;
|
|
660
|
-
|
|
661
|
+
logger.debug(`[ChromeController] Found potential daemon on port ${port}`);
|
|
661
662
|
break;
|
|
662
663
|
}
|
|
663
664
|
} catch (error) {
|
|
@@ -667,10 +668,10 @@ export class ChromeController {
|
|
|
667
668
|
|
|
668
669
|
if (!daemonPort) {
|
|
669
670
|
try {
|
|
670
|
-
|
|
671
|
+
logger.debug('[ChromeController] No existing daemon found, starting new daemon...');
|
|
671
672
|
daemonPort = await this.autoStartDaemon();
|
|
672
673
|
} catch (error) {
|
|
673
|
-
|
|
674
|
+
logger.warn('[ChromeController] Daemon auto-start failed, falling back to individual browser:', error.message);
|
|
674
675
|
// Now honor the failover manager's recommendation if auto-start also failed
|
|
675
676
|
if (shouldUseFallback) {
|
|
676
677
|
throw new Error('Failover manager recommends using individual browser after auto-start failed');
|
|
@@ -712,7 +713,7 @@ export class ChromeController {
|
|
|
712
713
|
// Notify failover manager of successful connection
|
|
713
714
|
await this.failoverManager.recordSuccessfulCheck(Date.now());
|
|
714
715
|
|
|
715
|
-
|
|
716
|
+
logger.debug(`[ChromeController] Successfully connected to shared browser daemon on port ${daemonPort}`);
|
|
716
717
|
|
|
717
718
|
return {
|
|
718
719
|
success: true,
|
|
@@ -730,7 +731,7 @@ export class ChromeController {
|
|
|
730
731
|
* @returns {Promise<Object>} Launch result
|
|
731
732
|
*/
|
|
732
733
|
async launchIndividualBrowser() {
|
|
733
|
-
|
|
734
|
+
logger.debug('[ChromeController] Launching individual browser instance...');
|
|
734
735
|
|
|
735
736
|
let sessionData = null;
|
|
736
737
|
|
|
@@ -743,8 +744,8 @@ export class ChromeController {
|
|
|
743
744
|
this.userDataDir = sessionData.profilePath;
|
|
744
745
|
this.sessionId = sessionData.sessionId;
|
|
745
746
|
|
|
746
|
-
|
|
747
|
-
|
|
747
|
+
logger.error(`Launching Chrome with debugging port ${this.debugPort}`);
|
|
748
|
+
logger.error(`User data directory: ${this.userDataDir}`);
|
|
748
749
|
|
|
749
750
|
// Detect Chrome executable path
|
|
750
751
|
let executablePath;
|
|
@@ -762,7 +763,7 @@ export class ChromeController {
|
|
|
762
763
|
executablePath = '/usr/bin/google-chrome';
|
|
763
764
|
}
|
|
764
765
|
|
|
765
|
-
|
|
766
|
+
logger.error(`Using Chrome executable: ${executablePath}`);
|
|
766
767
|
|
|
767
768
|
// Get Chrome extension path with fallback logic
|
|
768
769
|
const extensionPath = getExtensionPath();
|
|
@@ -784,7 +785,7 @@ export class ChromeController {
|
|
|
784
785
|
`--load-extension=${extensionPath}`
|
|
785
786
|
);
|
|
786
787
|
} else {
|
|
787
|
-
|
|
788
|
+
logger.warn('[ChromeController] ChromeDebug extension not found. Visual selection features will be unavailable.');
|
|
788
789
|
}
|
|
789
790
|
|
|
790
791
|
// First, try to launch Chrome with a simpler configuration
|
|
@@ -798,7 +799,7 @@ export class ChromeController {
|
|
|
798
799
|
timeout: 60000
|
|
799
800
|
});
|
|
800
801
|
} catch (launchError) {
|
|
801
|
-
|
|
802
|
+
logger.error('Simple launch failed, trying with additional flags...');
|
|
802
803
|
|
|
803
804
|
// Build fallback args with more flags
|
|
804
805
|
const fallbackArgs = [
|
|
@@ -845,7 +846,7 @@ export class ChromeController {
|
|
|
845
846
|
|
|
846
847
|
// Set up disconnect handler
|
|
847
848
|
this.browser.on('disconnected', () => {
|
|
848
|
-
|
|
849
|
+
logger.error('Browser disconnected');
|
|
849
850
|
this.browser = null;
|
|
850
851
|
this.page = null;
|
|
851
852
|
this.client = null;
|
|
@@ -899,7 +900,7 @@ export class ChromeController {
|
|
|
899
900
|
|
|
900
901
|
// Get the WebSocket endpoint for future connections
|
|
901
902
|
const wsEndpoint = this.browser.wsEndpoint();
|
|
902
|
-
|
|
903
|
+
logger.error(`Chrome WebSocket endpoint: ${wsEndpoint}`);
|
|
903
904
|
|
|
904
905
|
return {
|
|
905
906
|
success: true,
|
|
@@ -917,9 +918,9 @@ export class ChromeController {
|
|
|
917
918
|
if (sessionData) {
|
|
918
919
|
try {
|
|
919
920
|
await this.sessionManager.forceCleanupSession(sessionData.sessionId);
|
|
920
|
-
|
|
921
|
+
logger.error(`Cleaned up session after launch failure: ${sessionData.sessionId}`);
|
|
921
922
|
} catch (cleanupError) {
|
|
922
|
-
|
|
923
|
+
logger.error(`Failed to clean up session: ${sessionData.sessionId}`, cleanupError.message);
|
|
923
924
|
}
|
|
924
925
|
}
|
|
925
926
|
|
|
@@ -967,7 +968,7 @@ export class ChromeController {
|
|
|
967
968
|
async autoStartDaemon() {
|
|
968
969
|
// Prevent concurrent daemon starts using class-level lock
|
|
969
970
|
if (ChromeController.daemonStartPromise) {
|
|
970
|
-
|
|
971
|
+
logger.debug('[ChromeController] Daemon start already in progress, waiting...');
|
|
971
972
|
return await ChromeController.daemonStartPromise;
|
|
972
973
|
}
|
|
973
974
|
|
|
@@ -994,14 +995,14 @@ export class ChromeController {
|
|
|
994
995
|
this.isDaemonOwner = true;
|
|
995
996
|
|
|
996
997
|
const daemonPort = this.sharedDaemon.config.daemonPort;
|
|
997
|
-
|
|
998
|
+
logger.debug(`[ChromeController] Started shared browser daemon on port ${daemonPort}`);
|
|
998
999
|
|
|
999
1000
|
// Set up cleanup handlers for this specific daemon owner
|
|
1000
1001
|
this.setupDaemonCleanup();
|
|
1001
1002
|
|
|
1002
1003
|
return daemonPort;
|
|
1003
1004
|
} catch (error) {
|
|
1004
|
-
|
|
1005
|
+
logger.error('[ChromeController] Failed to start daemon:', error.message);
|
|
1005
1006
|
this.sharedDaemon = null;
|
|
1006
1007
|
this.isDaemonOwner = false;
|
|
1007
1008
|
throw new Error(`Could not start shared browser daemon: ${error.message}`);
|
|
@@ -1016,11 +1017,11 @@ export class ChromeController {
|
|
|
1016
1017
|
|
|
1017
1018
|
const cleanupHandler = async () => {
|
|
1018
1019
|
if (this.isDaemonOwner && this.sharedDaemon) {
|
|
1019
|
-
|
|
1020
|
+
logger.debug('[ChromeController] Cleaning up auto-started daemon...');
|
|
1020
1021
|
try {
|
|
1021
1022
|
await this.sharedDaemon.gracefulShutdown();
|
|
1022
1023
|
} catch (error) {
|
|
1023
|
-
|
|
1024
|
+
logger.error('[ChromeController] Error stopping daemon:', error.message);
|
|
1024
1025
|
}
|
|
1025
1026
|
this.sharedDaemon = null;
|
|
1026
1027
|
this.isDaemonOwner = false;
|
|
@@ -1040,7 +1041,7 @@ export class ChromeController {
|
|
|
1040
1041
|
await this.close();
|
|
1041
1042
|
}
|
|
1042
1043
|
|
|
1043
|
-
|
|
1044
|
+
logger.error(`Attempting to connect to Chrome on port ${port}...`);
|
|
1044
1045
|
|
|
1045
1046
|
// Try connecting with a timeout
|
|
1046
1047
|
this.browser = await this.withTimeout(
|
|
@@ -1053,10 +1054,10 @@ export class ChromeController {
|
|
|
1053
1054
|
);
|
|
1054
1055
|
|
|
1055
1056
|
this.debugPort = port;
|
|
1056
|
-
|
|
1057
|
+
logger.error(`Successfully connected to existing Chrome on port ${port}`);
|
|
1057
1058
|
|
|
1058
1059
|
this.browser.on('disconnected', () => {
|
|
1059
|
-
|
|
1060
|
+
logger.error('Browser disconnected');
|
|
1060
1061
|
this.browser = null;
|
|
1061
1062
|
this.page = null;
|
|
1062
1063
|
this.client = null;
|
|
@@ -1647,7 +1648,7 @@ export class ChromeController {
|
|
|
1647
1648
|
}
|
|
1648
1649
|
|
|
1649
1650
|
async forceReset() {
|
|
1650
|
-
|
|
1651
|
+
logger.error('Force resetting Chrome...');
|
|
1651
1652
|
|
|
1652
1653
|
await this.killOwnChromeProcess();
|
|
1653
1654
|
|
|
@@ -1658,9 +1659,9 @@ export class ChromeController {
|
|
|
1658
1659
|
if (this.sessionManager && this.sessionId) {
|
|
1659
1660
|
try {
|
|
1660
1661
|
await this.sessionManager.forceCleanupSession(this.sessionId);
|
|
1661
|
-
|
|
1662
|
+
logger.debug(`[ChromeController] Force cleanup completed for session ${this.sessionId}`);
|
|
1662
1663
|
} catch (error) {
|
|
1663
|
-
|
|
1664
|
+
logger.error('[ChromeController] Error during force cleanup:', error.message);
|
|
1664
1665
|
}
|
|
1665
1666
|
}
|
|
1666
1667
|
|
|
@@ -1686,7 +1687,7 @@ export class ChromeController {
|
|
|
1686
1687
|
try {
|
|
1687
1688
|
await this.browser.close();
|
|
1688
1689
|
} catch (e) {
|
|
1689
|
-
|
|
1690
|
+
logger.error('Error closing browser:', e.message);
|
|
1690
1691
|
} finally {
|
|
1691
1692
|
this.browser = null;
|
|
1692
1693
|
this.page = null;
|
|
@@ -1699,9 +1700,9 @@ export class ChromeController {
|
|
|
1699
1700
|
if (this.sessionManager) {
|
|
1700
1701
|
try {
|
|
1701
1702
|
await this.sessionManager.cleanup();
|
|
1702
|
-
|
|
1703
|
+
logger.debug(`[ChromeController] Session cleanup completed for ${this.sessionId}`);
|
|
1703
1704
|
} catch (error) {
|
|
1704
|
-
|
|
1705
|
+
logger.error('[ChromeController] Error during session cleanup:', error.message);
|
|
1705
1706
|
}
|
|
1706
1707
|
}
|
|
1707
1708
|
|
|
@@ -1717,11 +1718,11 @@ export class ChromeController {
|
|
|
1717
1718
|
async cleanup() {
|
|
1718
1719
|
// Clean up auto-started daemon first
|
|
1719
1720
|
if (this.isDaemonOwner && this.sharedDaemon) {
|
|
1720
|
-
|
|
1721
|
+
logger.debug('[ChromeController] Cleaning up auto-started daemon during cleanup...');
|
|
1721
1722
|
try {
|
|
1722
1723
|
await this.sharedDaemon.gracefulShutdown();
|
|
1723
1724
|
} catch (error) {
|
|
1724
|
-
|
|
1725
|
+
logger.error('[ChromeController] Error stopping daemon during cleanup:', error.message);
|
|
1725
1726
|
}
|
|
1726
1727
|
this.sharedDaemon = null;
|
|
1727
1728
|
this.isDaemonOwner = false;
|
|
@@ -1987,7 +1988,7 @@ export class ChromeController {
|
|
|
1987
1988
|
// Merge function traces into actions if provided
|
|
1988
1989
|
let allActions = [...actions];
|
|
1989
1990
|
if (functionTraces && functionTraces.length > 0) {
|
|
1990
|
-
|
|
1991
|
+
logger.debug(`[ChromeController] Merging ${functionTraces.length} function traces into actions`);
|
|
1991
1992
|
// Function traces are already in the correct format from the Chrome extension
|
|
1992
1993
|
allActions = [...actions, ...functionTraces];
|
|
1993
1994
|
// Sort all actions by timestamp to maintain chronological order
|
|
@@ -2005,10 +2006,10 @@ export class ChromeController {
|
|
|
2005
2006
|
// Store logs if provided
|
|
2006
2007
|
if (includeLogs && logs && logs.length > 0) {
|
|
2007
2008
|
const logsResult = database.storeWorkflowLogs(workflowId, logs);
|
|
2008
|
-
|
|
2009
|
+
logger.debug(`Stored ${logsResult.storedCount} logs for workflow ${sessionId}`);
|
|
2009
2010
|
}
|
|
2010
2011
|
|
|
2011
|
-
|
|
2012
|
+
logger.debug(`Stored workflow recording ${sessionId} with ${actionsResult.storedCount} actions (including ${functionTraces ? functionTraces.length : 0} function traces)`);
|
|
2012
2013
|
|
|
2013
2014
|
return {
|
|
2014
2015
|
success: true,
|
|
@@ -2019,7 +2020,7 @@ export class ChromeController {
|
|
|
2019
2020
|
functionTracesStored: functionTraces ? functionTraces.length : 0
|
|
2020
2021
|
};
|
|
2021
2022
|
} catch (error) {
|
|
2022
|
-
|
|
2023
|
+
logger.error('Error storing workflow recording:', error);
|
|
2023
2024
|
throw error;
|
|
2024
2025
|
}
|
|
2025
2026
|
}
|
|
@@ -2029,7 +2030,7 @@ export class ChromeController {
|
|
|
2029
2030
|
const { database } = await import('./database.js');
|
|
2030
2031
|
return database.getWorkflowRecording(sessionId);
|
|
2031
2032
|
} catch (error) {
|
|
2032
|
-
|
|
2033
|
+
logger.error('Error retrieving workflow recording:', error);
|
|
2033
2034
|
throw error;
|
|
2034
2035
|
}
|
|
2035
2036
|
}
|
|
@@ -2040,7 +2041,7 @@ export class ChromeController {
|
|
|
2040
2041
|
const recordings = database.listWorkflowRecordings();
|
|
2041
2042
|
return { recordings };
|
|
2042
2043
|
} catch (error) {
|
|
2043
|
-
|
|
2044
|
+
logger.error('Error listing workflow recordings:', error);
|
|
2044
2045
|
throw error;
|
|
2045
2046
|
}
|
|
2046
2047
|
}
|
|
@@ -2062,7 +2063,7 @@ export class ChromeController {
|
|
|
2062
2063
|
recording: recording
|
|
2063
2064
|
};
|
|
2064
2065
|
} catch (error) {
|
|
2065
|
-
|
|
2066
|
+
logger.error('Error playing workflow recording:', error);
|
|
2066
2067
|
throw error;
|
|
2067
2068
|
}
|
|
2068
2069
|
}
|
|
@@ -2094,7 +2095,7 @@ export class ChromeController {
|
|
|
2094
2095
|
recording: fullRecording
|
|
2095
2096
|
};
|
|
2096
2097
|
} catch (error) {
|
|
2097
|
-
|
|
2098
|
+
logger.error('Error playing workflow by name:', error);
|
|
2098
2099
|
throw error;
|
|
2099
2100
|
}
|
|
2100
2101
|
}
|
|
@@ -2110,7 +2111,7 @@ export class ChromeController {
|
|
|
2110
2111
|
);
|
|
2111
2112
|
return result;
|
|
2112
2113
|
} catch (error) {
|
|
2113
|
-
|
|
2114
|
+
logger.error('Error saving restore point:', error);
|
|
2114
2115
|
throw error;
|
|
2115
2116
|
}
|
|
2116
2117
|
}
|
|
@@ -2121,7 +2122,7 @@ export class ChromeController {
|
|
|
2121
2122
|
const { database } = await import('./database.js');
|
|
2122
2123
|
return database.getRestorePoint(restorePointId);
|
|
2123
2124
|
} catch (error) {
|
|
2124
|
-
|
|
2125
|
+
logger.error('Error getting restore point:', error);
|
|
2125
2126
|
throw error;
|
|
2126
2127
|
}
|
|
2127
2128
|
}
|
|
@@ -2132,7 +2133,7 @@ export class ChromeController {
|
|
|
2132
2133
|
const { database } = await import('./database.js');
|
|
2133
2134
|
return database.listRestorePoints(workflowId);
|
|
2134
2135
|
} catch (error) {
|
|
2135
|
-
|
|
2136
|
+
logger.error('Error listing restore points:', error);
|
|
2136
2137
|
throw error;
|
|
2137
2138
|
}
|
|
2138
2139
|
}
|
|
@@ -2146,7 +2147,7 @@ export class ChromeController {
|
|
|
2146
2147
|
const result = deleteStmt.run(restorePointId);
|
|
2147
2148
|
return { success: result.changes > 0 };
|
|
2148
2149
|
} catch (error) {
|
|
2149
|
-
|
|
2150
|
+
logger.error('Error deleting restore point:', error);
|
|
2150
2151
|
throw error;
|
|
2151
2152
|
}
|
|
2152
2153
|
}
|
|
@@ -2157,7 +2158,7 @@ export class ChromeController {
|
|
|
2157
2158
|
const { database } = await import('./database.js');
|
|
2158
2159
|
return database.getFrameSessionInfo(sessionId);
|
|
2159
2160
|
} catch (error) {
|
|
2160
|
-
|
|
2161
|
+
logger.error('Error getting frame session info:', error);
|
|
2161
2162
|
throw error;
|
|
2162
2163
|
}
|
|
2163
2164
|
}
|
|
@@ -2167,7 +2168,7 @@ export class ChromeController {
|
|
|
2167
2168
|
const { database } = await import('./database.js');
|
|
2168
2169
|
return database.getFrame(sessionId, frameIndex);
|
|
2169
2170
|
} catch (error) {
|
|
2170
|
-
|
|
2171
|
+
logger.error('Error getting frame:', error);
|
|
2171
2172
|
throw error;
|
|
2172
2173
|
}
|
|
2173
2174
|
}
|
|
@@ -2177,7 +2178,7 @@ export class ChromeController {
|
|
|
2177
2178
|
const { database } = await import('./database.js');
|
|
2178
2179
|
return database.getFrameScreenshot(sessionId, frameIndex, includeMetadata);
|
|
2179
2180
|
} catch (error) {
|
|
2180
|
-
|
|
2181
|
+
logger.error('Error getting frame screenshot:', error);
|
|
2181
2182
|
throw error;
|
|
2182
2183
|
}
|
|
2183
2184
|
}
|
|
@@ -2187,7 +2188,7 @@ export class ChromeController {
|
|
|
2187
2188
|
const { database } = await import('./database.js');
|
|
2188
2189
|
return database.getScreenInteractions(sessionId);
|
|
2189
2190
|
} catch (error) {
|
|
2190
|
-
|
|
2191
|
+
logger.error('Error retrieving screen interactions:', error);
|
|
2191
2192
|
throw error;
|
|
2192
2193
|
}
|
|
2193
2194
|
}
|
|
@@ -2197,7 +2198,7 @@ export class ChromeController {
|
|
|
2197
2198
|
const { database } = await import('./database.js');
|
|
2198
2199
|
return database.getFrameInteractions(sessionId, frameIndex);
|
|
2199
2200
|
} catch (error) {
|
|
2200
|
-
|
|
2201
|
+
logger.error('Error retrieving frame interactions:', error);
|
|
2201
2202
|
throw error;
|
|
2202
2203
|
}
|
|
2203
2204
|
}
|
|
@@ -2207,7 +2208,7 @@ export class ChromeController {
|
|
|
2207
2208
|
const { database } = await import('./database.js');
|
|
2208
2209
|
return database.getFrameSession(sessionId);
|
|
2209
2210
|
} catch (error) {
|
|
2210
|
-
|
|
2211
|
+
logger.error('Error getting frame session:', error);
|
|
2211
2212
|
throw error;
|
|
2212
2213
|
}
|
|
2213
2214
|
}
|
|
@@ -2219,21 +2220,21 @@ export class ChromeController {
|
|
|
2219
2220
|
|
|
2220
2221
|
// After storing frames, attempt to associate any deferred logs
|
|
2221
2222
|
if (result && result.id) {
|
|
2222
|
-
|
|
2223
|
+
logger.debug(`[storeFrameBatch] Frames stored successfully, checking for deferred logs...`);
|
|
2223
2224
|
try {
|
|
2224
2225
|
const deferredResult = await database.associateDeferredLogs(sessionId);
|
|
2225
2226
|
if (deferredResult.success && deferredResult.logsAssociated > 0) {
|
|
2226
|
-
|
|
2227
|
+
logger.debug(`[storeFrameBatch] Associated ${deferredResult.logsAssociated} deferred logs with new frames`);
|
|
2227
2228
|
}
|
|
2228
2229
|
} catch (deferredError) {
|
|
2229
|
-
|
|
2230
|
+
logger.error('[storeFrameBatch] Error associating deferred logs:', deferredError);
|
|
2230
2231
|
// Don't fail the frame storage if deferred log association fails
|
|
2231
2232
|
}
|
|
2232
2233
|
}
|
|
2233
2234
|
|
|
2234
2235
|
return result;
|
|
2235
2236
|
} catch (error) {
|
|
2236
|
-
|
|
2237
|
+
logger.error('Error storing frame batch:', error);
|
|
2237
2238
|
throw error;
|
|
2238
2239
|
}
|
|
2239
2240
|
}
|
|
@@ -2243,7 +2244,7 @@ export class ChromeController {
|
|
|
2243
2244
|
const { database } = await import('./database.js');
|
|
2244
2245
|
return database.associateLogsWithFrames(sessionId, logs);
|
|
2245
2246
|
} catch (error) {
|
|
2246
|
-
|
|
2247
|
+
logger.error('Error associating logs with frames:', error);
|
|
2247
2248
|
throw error;
|
|
2248
2249
|
}
|
|
2249
2250
|
}
|
|
@@ -2253,7 +2254,7 @@ export class ChromeController {
|
|
|
2253
2254
|
const { database } = await import('./database.js');
|
|
2254
2255
|
return database.streamLogsToFrames(sessionId, logs);
|
|
2255
2256
|
} catch (error) {
|
|
2256
|
-
|
|
2257
|
+
logger.error('Error streaming logs to frames:', error);
|
|
2257
2258
|
throw error;
|
|
2258
2259
|
}
|
|
2259
2260
|
}
|
|
@@ -2281,7 +2282,7 @@ export class ChromeController {
|
|
|
2281
2282
|
const { database } = await import('./database.js');
|
|
2282
2283
|
return database.storeSnapshot(sessionId, frameData, userNote);
|
|
2283
2284
|
} catch (error) {
|
|
2284
|
-
|
|
2285
|
+
logger.error('Error storing snapshot:', error);
|
|
2285
2286
|
throw error;
|
|
2286
2287
|
}
|
|
2287
2288
|
}
|
|
@@ -2291,7 +2292,7 @@ export class ChromeController {
|
|
|
2291
2292
|
const { database } = await import('./database.js');
|
|
2292
2293
|
return database.getSnapshots(limit);
|
|
2293
2294
|
} catch (error) {
|
|
2294
|
-
|
|
2295
|
+
logger.error('Error getting snapshots:', error);
|
|
2295
2296
|
throw error;
|
|
2296
2297
|
}
|
|
2297
2298
|
}
|
|
@@ -2334,7 +2335,7 @@ export class ChromeController {
|
|
|
2334
2335
|
message: `Snapshot created successfully with ID: ${result}`
|
|
2335
2336
|
};
|
|
2336
2337
|
} catch (error) {
|
|
2337
|
-
|
|
2338
|
+
logger.error('Error taking snapshot:', error);
|
|
2338
2339
|
throw error;
|
|
2339
2340
|
}
|
|
2340
2341
|
}
|
|
@@ -2345,7 +2346,7 @@ export class ChromeController {
|
|
|
2345
2346
|
const { database } = await import('./database.js');
|
|
2346
2347
|
return database.listRecordings();
|
|
2347
2348
|
} catch (error) {
|
|
2348
|
-
|
|
2349
|
+
logger.error('Error listing frame sessions:', error);
|
|
2349
2350
|
throw error;
|
|
2350
2351
|
}
|
|
2351
2352
|
}
|
|
@@ -2393,7 +2394,7 @@ export class ChromeController {
|
|
|
2393
2394
|
timestamp: log.timestamp
|
|
2394
2395
|
}));
|
|
2395
2396
|
} catch (error) {
|
|
2396
|
-
|
|
2397
|
+
logger.error('Error searching frame logs:', error);
|
|
2397
2398
|
throw error;
|
|
2398
2399
|
}
|
|
2399
2400
|
}
|
|
@@ -2463,7 +2464,7 @@ export class ChromeController {
|
|
|
2463
2464
|
logs
|
|
2464
2465
|
};
|
|
2465
2466
|
} catch (error) {
|
|
2466
|
-
|
|
2467
|
+
logger.error('Error getting paginated frame logs:', error);
|
|
2467
2468
|
throw error;
|
|
2468
2469
|
}
|
|
2469
2470
|
}
|
|
@@ -2478,7 +2479,7 @@ export class ChromeController {
|
|
|
2478
2479
|
return { success: false, error: 'Recording not found' };
|
|
2479
2480
|
}
|
|
2480
2481
|
} catch (error) {
|
|
2481
|
-
|
|
2482
|
+
logger.error('Error deleting recording:', error);
|
|
2482
2483
|
throw error;
|
|
2483
2484
|
}
|
|
2484
2485
|
}
|
|
@@ -2493,7 +2494,7 @@ export class ChromeController {
|
|
|
2493
2494
|
return { success: false, error: 'Workflow recording not found' };
|
|
2494
2495
|
}
|
|
2495
2496
|
} catch (error) {
|
|
2496
|
-
|
|
2497
|
+
logger.error('Error deleting workflow recording:', error);
|
|
2497
2498
|
throw error;
|
|
2498
2499
|
}
|
|
2499
2500
|
}
|
|
@@ -2541,7 +2542,7 @@ export class ChromeController {
|
|
|
2541
2542
|
if (workflow.url) {
|
|
2542
2543
|
const currentUrl = currentPage.url();
|
|
2543
2544
|
if (currentUrl !== workflow.url) {
|
|
2544
|
-
|
|
2545
|
+
logger.debug(`Navigating to workflow URL: ${workflow.url}`);
|
|
2545
2546
|
await currentPage.goto(workflow.url, { waitUntil: 'networkidle2' });
|
|
2546
2547
|
await new Promise(resolve => setTimeout(resolve, 1000)); // Wait for page to settle
|
|
2547
2548
|
}
|
|
@@ -2550,7 +2551,7 @@ export class ChromeController {
|
|
|
2550
2551
|
// Execute each action
|
|
2551
2552
|
for (const action of actions) {
|
|
2552
2553
|
try {
|
|
2553
|
-
|
|
2554
|
+
logger.debug(`Executing action ${executedActions + 1}/${actions.length}: ${action.type}`);
|
|
2554
2555
|
|
|
2555
2556
|
// Calculate delay based on speed
|
|
2556
2557
|
const delay = action.timestamp && actions.indexOf(action) > 0
|
|
@@ -2566,13 +2567,13 @@ export class ChromeController {
|
|
|
2566
2567
|
case 'click':
|
|
2567
2568
|
if (action.selector) {
|
|
2568
2569
|
const fixedSelector = this.fixSelector(action.selector);
|
|
2569
|
-
|
|
2570
|
+
logger.debug(`Fixed selector: ${fixedSelector} (original: ${action.selector})`);
|
|
2570
2571
|
|
|
2571
2572
|
try {
|
|
2572
2573
|
await currentPage.waitForSelector(fixedSelector, { timeout: 5000 });
|
|
2573
2574
|
await currentPage.click(fixedSelector);
|
|
2574
2575
|
} catch (selectorError) {
|
|
2575
|
-
|
|
2576
|
+
logger.error(`Failed with CSS selector, trying coordinates fallback`);
|
|
2576
2577
|
if (action.x && action.y) {
|
|
2577
2578
|
await currentPage.mouse.click(action.x, action.y);
|
|
2578
2579
|
} else {
|
|
@@ -2587,7 +2588,7 @@ export class ChromeController {
|
|
|
2587
2588
|
case 'input':
|
|
2588
2589
|
if (action.selector) {
|
|
2589
2590
|
const fixedSelector = this.fixSelector(action.selector);
|
|
2590
|
-
|
|
2591
|
+
logger.debug(`Fixed input selector: ${fixedSelector} (original: ${action.selector})`);
|
|
2591
2592
|
|
|
2592
2593
|
await currentPage.waitForSelector(fixedSelector, { timeout: 5000 });
|
|
2593
2594
|
await currentPage.click(fixedSelector); // Focus the element
|
|
@@ -2625,17 +2626,17 @@ export class ChromeController {
|
|
|
2625
2626
|
// Wait for navigation to complete
|
|
2626
2627
|
await currentPage.waitForNavigation({ waitUntil: 'networkidle2', timeout: 10000 }).catch(() => {
|
|
2627
2628
|
// Navigation might not happen, continue
|
|
2628
|
-
|
|
2629
|
+
logger.debug('Navigation timeout, continuing...');
|
|
2629
2630
|
});
|
|
2630
2631
|
break;
|
|
2631
2632
|
|
|
2632
2633
|
default:
|
|
2633
|
-
|
|
2634
|
+
logger.warn(`Unknown action type: ${action.type}`);
|
|
2634
2635
|
}
|
|
2635
2636
|
|
|
2636
2637
|
executedActions++;
|
|
2637
2638
|
} catch (error) {
|
|
2638
|
-
|
|
2639
|
+
logger.error(`Error executing action ${executedActions + 1}:`, error);
|
|
2639
2640
|
lastError = error.message;
|
|
2640
2641
|
// Continue with next action on error
|
|
2641
2642
|
}
|
|
@@ -2651,7 +2652,7 @@ export class ChromeController {
|
|
|
2651
2652
|
error: lastError
|
|
2652
2653
|
};
|
|
2653
2654
|
} catch (error) {
|
|
2654
|
-
|
|
2655
|
+
logger.error('Error playing workflow:', error);
|
|
2655
2656
|
throw error;
|
|
2656
2657
|
}
|
|
2657
2658
|
}
|