@dynamicu/chromedebug-mcp 2.6.4 → 2.6.7
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 +25 -0
- package/chrome-extension/activation-manager.js +18 -4
- package/chrome-extension/background.js +439 -53
- package/chrome-extension/browser-recording-manager.js +256 -0
- package/chrome-extension/content.js +167 -97
- package/chrome-extension/data-buffer.js +206 -17
- package/chrome-extension/frame-capture.js +52 -15
- package/chrome-extension/manifest.free.json +3 -4
- package/chrome-extension/popup.html +109 -5
- package/chrome-extension/popup.js +597 -159
- package/config/chromedebug-config.json +101 -0
- package/dist/chromedebug-extension-free.zip +0 -0
- package/package.json +1 -1
- package/scripts/package-pro-extension.js +1 -1
- package/scripts/webpack.config.free.cjs +4 -1
- package/scripts/webpack.config.pro.cjs +4 -1
- package/src/chrome-controller.js +7 -7
- package/src/cli.js +6 -0
- package/src/database.js +6 -2
- package/src/http-server.js +3 -2
- package/src/index.js +6 -1
- package/src/services/unified-session-manager.js +22 -2
- package/src/validation/schemas.js +36 -6
|
@@ -10,6 +10,7 @@ importScripts('upload-manager.js');
|
|
|
10
10
|
importScripts('chrome-session-manager.js');
|
|
11
11
|
importScripts('firebase-config.js');
|
|
12
12
|
importScripts('license-helper.js');
|
|
13
|
+
importScripts('browser-recording-manager.js');
|
|
13
14
|
|
|
14
15
|
const CONFIG_PORTS = CHROMEDEBUG_CONFIG.ports;
|
|
15
16
|
const DISCOVERY_TIMEOUT = CHROMEDEBUG_CONFIG.discoveryTimeout;
|
|
@@ -35,12 +36,18 @@ class LogStreamer {
|
|
|
35
36
|
}
|
|
36
37
|
|
|
37
38
|
async init() {
|
|
39
|
+
// Skip initialization in browser-only mode
|
|
40
|
+
if (serverMode === 'browser-only') {
|
|
41
|
+
console.log('[LogStreamer] Skipping initialization - browser-only mode');
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
38
45
|
// Test server availability
|
|
39
46
|
await this.checkServerAvailability();
|
|
40
|
-
|
|
47
|
+
|
|
41
48
|
// Start periodic streaming
|
|
42
49
|
this.startPeriodicStreaming();
|
|
43
|
-
|
|
50
|
+
|
|
44
51
|
console.log('[LogStreamer] Initialized with server availability:', this.serverAvailable);
|
|
45
52
|
}
|
|
46
53
|
|
|
@@ -78,10 +85,16 @@ class LogStreamer {
|
|
|
78
85
|
async addLog(log, sessionId) {
|
|
79
86
|
log.sequence = ++this.sequenceNumber;
|
|
80
87
|
log.timestamp = Date.now();
|
|
81
|
-
|
|
88
|
+
|
|
82
89
|
this.currentSessionId = sessionId;
|
|
90
|
+
|
|
91
|
+
// In browser-only mode, skip server streaming (logs stored in LogBuffer)
|
|
92
|
+
if (serverMode === 'browser-only') {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
83
96
|
this.buffer.push(log);
|
|
84
|
-
|
|
97
|
+
|
|
85
98
|
// If buffer is getting full, force immediate stream
|
|
86
99
|
if (this.buffer.length >= this.maxBufferSize) {
|
|
87
100
|
await this.streamLogs();
|
|
@@ -89,6 +102,9 @@ class LogStreamer {
|
|
|
89
102
|
}
|
|
90
103
|
|
|
91
104
|
async streamLogs() {
|
|
105
|
+
// Skip streaming in browser-only mode
|
|
106
|
+
if (serverMode === 'browser-only') return;
|
|
107
|
+
|
|
92
108
|
if (this.buffer.length === 0 || !this.currentSessionId) return;
|
|
93
109
|
|
|
94
110
|
// Check if we have waited at least 100ms since last frame capture
|
|
@@ -470,6 +486,8 @@ let dataBuffer = null;
|
|
|
470
486
|
let uploadManager = null;
|
|
471
487
|
let logStreamer = null;
|
|
472
488
|
let logBuffer = null;
|
|
489
|
+
let browserRecordingManager = null;
|
|
490
|
+
let serverMode = 'unknown'; // 'server', 'browser-only', or 'unknown'
|
|
473
491
|
|
|
474
492
|
// Error handling state and configuration
|
|
475
493
|
// IMPORTANT: These must be declared before initializeServices()
|
|
@@ -566,6 +584,15 @@ async function initializeServices() {
|
|
|
566
584
|
logBuffer = null;
|
|
567
585
|
}
|
|
568
586
|
|
|
587
|
+
// Initialize browser recording manager for browser-only mode
|
|
588
|
+
try {
|
|
589
|
+
browserRecordingManager = new BrowserRecordingManager(dataBuffer);
|
|
590
|
+
console.log('[Background] Browser recording manager initialized');
|
|
591
|
+
} catch (recordingErr) {
|
|
592
|
+
console.error('[Background] Failed to initialize browser recording manager:', recordingErr);
|
|
593
|
+
browserRecordingManager = null;
|
|
594
|
+
}
|
|
595
|
+
|
|
569
596
|
// Initialize session manager with recovery
|
|
570
597
|
try {
|
|
571
598
|
if (!sessionManager) {
|
|
@@ -909,6 +936,112 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
|
|
909
936
|
}
|
|
910
937
|
break;
|
|
911
938
|
|
|
939
|
+
case 'getBrowserRecordings':
|
|
940
|
+
// Get all browser-only recordings from IndexedDB
|
|
941
|
+
console.log('[Background] getBrowserRecordings - Manager exists:', !!browserRecordingManager);
|
|
942
|
+
if (browserRecordingManager) {
|
|
943
|
+
browserRecordingManager.listRecordings().then(recordings => {
|
|
944
|
+
console.log('[Background] Recordings retrieved from manager:', recordings);
|
|
945
|
+
sendResponse(recordings);
|
|
946
|
+
}).catch(error => {
|
|
947
|
+
console.error('[Background] Failed to get browser recordings:', error);
|
|
948
|
+
sendResponse([]);
|
|
949
|
+
});
|
|
950
|
+
return true; // Async response
|
|
951
|
+
} else {
|
|
952
|
+
console.warn('[Background] browserRecordingManager not initialized');
|
|
953
|
+
sendResponse([]);
|
|
954
|
+
}
|
|
955
|
+
break;
|
|
956
|
+
|
|
957
|
+
case 'exportBrowserRecording':
|
|
958
|
+
// Export a browser-only recording
|
|
959
|
+
if (browserRecordingManager && message.sessionId) {
|
|
960
|
+
browserRecordingManager.exportRecording(message.sessionId).then(result => {
|
|
961
|
+
sendResponse({ success: true, data: result });
|
|
962
|
+
}).catch(error => {
|
|
963
|
+
console.error('[Background] Failed to export recording:', error);
|
|
964
|
+
sendResponse({ success: false, error: error.message });
|
|
965
|
+
});
|
|
966
|
+
return true; // Async response
|
|
967
|
+
} else {
|
|
968
|
+
sendResponse({ success: false, error: 'Manager not available or missing sessionId' });
|
|
969
|
+
}
|
|
970
|
+
break;
|
|
971
|
+
|
|
972
|
+
case 'deleteBrowserRecording':
|
|
973
|
+
// Delete a browser-only recording
|
|
974
|
+
if (browserRecordingManager && message.sessionId) {
|
|
975
|
+
browserRecordingManager.deleteRecording(message.sessionId).then(() => {
|
|
976
|
+
sendResponse({ success: true });
|
|
977
|
+
}).catch(error => {
|
|
978
|
+
console.error('[Background] Failed to delete recording:', error);
|
|
979
|
+
sendResponse({ success: false, error: error.message });
|
|
980
|
+
});
|
|
981
|
+
return true; // Async response
|
|
982
|
+
} else {
|
|
983
|
+
sendResponse({ success: false, error: 'Manager not available or missing sessionId' });
|
|
984
|
+
}
|
|
985
|
+
break;
|
|
986
|
+
|
|
987
|
+
case 'getBrowserRecordingFrames':
|
|
988
|
+
// Get frames for a browser-only recording
|
|
989
|
+
if (dataBuffer && message.sessionId) {
|
|
990
|
+
dataBuffer.getBrowserFrames(message.sessionId).then(frames => {
|
|
991
|
+
console.log(`[DEBUG] getBrowserRecordingFrames: Retrieved ${frames.length} frames from IndexedDB`);
|
|
992
|
+
|
|
993
|
+
// Debug first frame to see structure
|
|
994
|
+
if (frames.length > 0) {
|
|
995
|
+
const firstFrame = frames[0];
|
|
996
|
+
const debugInfo = {
|
|
997
|
+
hasScreenshot: !!firstFrame.screenshot,
|
|
998
|
+
screenshotPrefix: firstFrame.screenshot ? firstFrame.screenshot.substring(0, 30) : 'N/A',
|
|
999
|
+
hasLogs: !!firstFrame.logs,
|
|
1000
|
+
logsCount: firstFrame.logs ? firstFrame.logs.length : 0,
|
|
1001
|
+
timestamp: firstFrame.timestamp,
|
|
1002
|
+
frameIndex: firstFrame.frameIndex,
|
|
1003
|
+
allKeys: Object.keys(firstFrame)
|
|
1004
|
+
};
|
|
1005
|
+
console.log('[DEBUG] First frame structure:', JSON.stringify(debugInfo, null, 2));
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
// Transform frames to match frame editor's expected structure
|
|
1009
|
+
// Frame editor expects 'imageData' property, but we store as 'screenshot'
|
|
1010
|
+
const transformedFrames = frames.map(frame => ({
|
|
1011
|
+
...frame,
|
|
1012
|
+
imageData: frame.screenshot // Rename screenshot to imageData
|
|
1013
|
+
}));
|
|
1014
|
+
|
|
1015
|
+
const transformDebug = transformedFrames.length > 0 ? {
|
|
1016
|
+
hasImageData: !!transformedFrames[0].imageData,
|
|
1017
|
+
imageDataPrefix: transformedFrames[0].imageData ? transformedFrames[0].imageData.substring(0, 30) : 'N/A',
|
|
1018
|
+
hasLogs: !!transformedFrames[0].logs,
|
|
1019
|
+
logsCount: transformedFrames[0].logs ? transformedFrames[0].logs.length : 0,
|
|
1020
|
+
allKeys: Object.keys(transformedFrames[0])
|
|
1021
|
+
} : 'No frames';
|
|
1022
|
+
console.log('[DEBUG] Transformed first frame:', JSON.stringify(transformDebug, null, 2));
|
|
1023
|
+
|
|
1024
|
+
sendResponse({ success: true, frames: transformedFrames });
|
|
1025
|
+
}).catch(error => {
|
|
1026
|
+
console.error('[Background] Failed to get browser recording frames:', error);
|
|
1027
|
+
sendResponse({ success: false, error: error.message });
|
|
1028
|
+
});
|
|
1029
|
+
return true; // Async response
|
|
1030
|
+
} else {
|
|
1031
|
+
sendResponse({ success: false, error: 'DataBuffer not available or missing sessionId' });
|
|
1032
|
+
}
|
|
1033
|
+
break;
|
|
1034
|
+
|
|
1035
|
+
case 'getQuotaInfo':
|
|
1036
|
+
// Get storage quota information
|
|
1037
|
+
getQuotaInfo().then(info => {
|
|
1038
|
+
sendResponse(info);
|
|
1039
|
+
}).catch(error => {
|
|
1040
|
+
console.error('[Background] Failed to get quota info:', error);
|
|
1041
|
+
sendResponse({ supported: false });
|
|
1042
|
+
});
|
|
1043
|
+
return true; // Async response
|
|
1044
|
+
|
|
912
1045
|
default:
|
|
913
1046
|
// Don't warn or respond for messages that should be handled by other listener (v2.0.6)
|
|
914
1047
|
// This prevents intercepting workflow recording messages
|
|
@@ -1891,6 +2024,17 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
|
|
|
1891
2024
|
return true;
|
|
1892
2025
|
}
|
|
1893
2026
|
|
|
2027
|
+
// Get quota information for browser-only mode
|
|
2028
|
+
if (request.action === 'getQuotaInfo') {
|
|
2029
|
+
getQuotaInfo().then((quotaInfo) => {
|
|
2030
|
+
sendResponse({ quotaInfo });
|
|
2031
|
+
}).catch((error) => {
|
|
2032
|
+
console.error('Error getting quota info:', error);
|
|
2033
|
+
sendResponse({ quotaInfo: null, error: error.message });
|
|
2034
|
+
});
|
|
2035
|
+
return true;
|
|
2036
|
+
}
|
|
2037
|
+
|
|
1894
2038
|
// Session manager lease renewal handler
|
|
1895
2039
|
if (request.action === 'renewLease') {
|
|
1896
2040
|
console.log('[Background] RENEW LEASE - Session:', request.sessionId, 'Manager exists:', !!sessionManager);
|
|
@@ -2322,13 +2466,24 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
|
|
|
2322
2466
|
|
|
2323
2467
|
// Handle workflow screenshot capture
|
|
2324
2468
|
if (request.action === 'captureWorkflowScreenshot' && sender.tab) {
|
|
2469
|
+
console.log('[SCREENSHOT-DEBUG] background.js - Received captureWorkflowScreenshot message');
|
|
2325
2470
|
const tabId = sender.tab.id;
|
|
2326
2471
|
const settings = request.settings || {};
|
|
2327
|
-
|
|
2472
|
+
console.log('[SCREENSHOT-DEBUG] background.js - tabId:', tabId);
|
|
2473
|
+
console.log('[SCREENSHOT-DEBUG] background.js - settings:', JSON.stringify(settings));
|
|
2474
|
+
|
|
2328
2475
|
captureTabScreenshot(tabId, settings).then((screenshotData) => {
|
|
2476
|
+
console.log('[SCREENSHOT-DEBUG] background.js - captureTabScreenshot succeeded');
|
|
2477
|
+
console.log('[SCREENSHOT-DEBUG] background.js - screenshotData type:', typeof screenshotData);
|
|
2478
|
+
console.log('[SCREENSHOT-DEBUG] background.js - screenshotData length:', screenshotData?.length);
|
|
2479
|
+
console.log('[SCREENSHOT-DEBUG] background.js - screenshotData preview:', screenshotData?.substring(0, 100));
|
|
2480
|
+
console.log('[SCREENSHOT-DEBUG] background.js - Sending response with success: true');
|
|
2329
2481
|
sendResponse({ success: true, screenshotData });
|
|
2330
2482
|
}).catch((error) => {
|
|
2331
|
-
console.error('
|
|
2483
|
+
console.error('[SCREENSHOT-DEBUG] background.js - captureTabScreenshot FAILED');
|
|
2484
|
+
console.error('[SCREENSHOT-DEBUG] background.js - Error:', error);
|
|
2485
|
+
console.error('[SCREENSHOT-DEBUG] background.js - Error message:', error.message);
|
|
2486
|
+
console.error('[SCREENSHOT-DEBUG] background.js - Error stack:', error.stack);
|
|
2332
2487
|
sendResponse({ success: false, error: error.message });
|
|
2333
2488
|
});
|
|
2334
2489
|
return true;
|
|
@@ -2456,14 +2611,34 @@ async function takeStandaloneSnapshot(tabId, note = '') {
|
|
|
2456
2611
|
|
|
2457
2612
|
// Capture screenshot for workflow recording
|
|
2458
2613
|
async function captureTabScreenshot(tabId, settings) {
|
|
2614
|
+
console.log('[SCREENSHOT-DEBUG] captureTabScreenshot - ENTRY');
|
|
2615
|
+
console.log('[SCREENSHOT-DEBUG] captureTabScreenshot - tabId:', tabId);
|
|
2616
|
+
console.log('[SCREENSHOT-DEBUG] captureTabScreenshot - settings:', JSON.stringify(settings));
|
|
2617
|
+
|
|
2459
2618
|
try {
|
|
2460
|
-
|
|
2619
|
+
// Get the tab to find its window
|
|
2620
|
+
console.log('[SCREENSHOT-DEBUG] captureTabScreenshot - Getting tab info...');
|
|
2621
|
+
const tab = await chrome.tabs.get(tabId);
|
|
2622
|
+
const windowId = tab.windowId;
|
|
2623
|
+
console.log('[SCREENSHOT-DEBUG] captureTabScreenshot - windowId:', windowId);
|
|
2624
|
+
console.log('[SCREENSHOT-DEBUG] captureTabScreenshot - tab.url:', tab.url);
|
|
2625
|
+
|
|
2626
|
+
const captureOptions = {
|
|
2461
2627
|
format: settings.format || 'jpeg',
|
|
2462
2628
|
quality: settings.quality || 30
|
|
2463
|
-
}
|
|
2464
|
-
|
|
2629
|
+
};
|
|
2630
|
+
console.log('[SCREENSHOT-DEBUG] captureTabScreenshot - captureOptions:', JSON.stringify(captureOptions));
|
|
2631
|
+
console.log('[SCREENSHOT-DEBUG] captureTabScreenshot - Calling chrome.tabs.captureVisibleTab...');
|
|
2632
|
+
|
|
2633
|
+
const dataUrl = await chrome.tabs.captureVisibleTab(windowId, captureOptions);
|
|
2634
|
+
|
|
2635
|
+
console.log('[SCREENSHOT-DEBUG] captureTabScreenshot - captureVisibleTab SUCCESS');
|
|
2636
|
+
console.log('[SCREENSHOT-DEBUG] captureTabScreenshot - dataUrl length:', dataUrl?.length);
|
|
2637
|
+
console.log('[SCREENSHOT-DEBUG] captureTabScreenshot - dataUrl prefix:', dataUrl?.substring(0, 100));
|
|
2638
|
+
|
|
2465
2639
|
// If resolution is specified, resize the image
|
|
2466
2640
|
if (settings.maxWidth || settings.maxHeight) {
|
|
2641
|
+
console.log('[SCREENSHOT-DEBUG] captureTabScreenshot - Resizing required, maxWidth:', settings.maxWidth, 'maxHeight:', settings.maxHeight);
|
|
2467
2642
|
// Create an image element to get dimensions
|
|
2468
2643
|
const img = new Image();
|
|
2469
2644
|
const canvas = new OffscreenCanvas(1, 1);
|
|
@@ -2503,17 +2678,75 @@ async function captureTabScreenshot(tabId, settings) {
|
|
|
2503
2678
|
});
|
|
2504
2679
|
}
|
|
2505
2680
|
|
|
2681
|
+
console.log('[SCREENSHOT-DEBUG] captureTabScreenshot - No resize needed, returning dataUrl');
|
|
2682
|
+
console.log('[SCREENSHOT-DEBUG] captureTabScreenshot - Final dataUrl length:', dataUrl?.length);
|
|
2506
2683
|
return dataUrl;
|
|
2507
2684
|
} catch (error) {
|
|
2508
|
-
console.error('
|
|
2685
|
+
console.error('[SCREENSHOT-DEBUG] captureTabScreenshot - EXCEPTION CAUGHT');
|
|
2686
|
+
console.error('[SCREENSHOT-DEBUG] captureTabScreenshot - Error:', error);
|
|
2687
|
+
console.error('[SCREENSHOT-DEBUG] captureTabScreenshot - Error message:', error.message);
|
|
2688
|
+
console.error('[SCREENSHOT-DEBUG] captureTabScreenshot - Error stack:', error.stack);
|
|
2509
2689
|
throw error;
|
|
2510
2690
|
}
|
|
2511
2691
|
}
|
|
2512
2692
|
|
|
2513
2693
|
// Start recording
|
|
2694
|
+
// Detect if server is available (server mode vs browser-only mode)
|
|
2695
|
+
async function detectServerMode() {
|
|
2696
|
+
console.log('[ServerDetection] Checking server availability...');
|
|
2697
|
+
|
|
2698
|
+
for (const port of CONFIG_PORTS) {
|
|
2699
|
+
try {
|
|
2700
|
+
const response = await Promise.race([
|
|
2701
|
+
fetch(`http://localhost:${port}/health`, {
|
|
2702
|
+
method: 'GET'
|
|
2703
|
+
}),
|
|
2704
|
+
new Promise((_, reject) =>
|
|
2705
|
+
setTimeout(() => reject(new Error('Timeout')), 1000)
|
|
2706
|
+
)
|
|
2707
|
+
]);
|
|
2708
|
+
|
|
2709
|
+
if (response.ok) {
|
|
2710
|
+
console.log(`[ServerDetection] Server detected on port ${port}`);
|
|
2711
|
+
serverMode = 'server';
|
|
2712
|
+
return 'server';
|
|
2713
|
+
}
|
|
2714
|
+
} catch (error) {
|
|
2715
|
+
// Continue checking other ports
|
|
2716
|
+
}
|
|
2717
|
+
}
|
|
2718
|
+
|
|
2719
|
+
console.log('[ServerDetection] No server detected - using browser-only mode');
|
|
2720
|
+
serverMode = 'browser-only';
|
|
2721
|
+
return 'browser-only';
|
|
2722
|
+
}
|
|
2723
|
+
|
|
2724
|
+
// Simple quota info utility (replaces over-engineered QuotaMonitor class)
|
|
2725
|
+
async function getQuotaInfo() {
|
|
2726
|
+
if (!navigator.storage?.estimate) {
|
|
2727
|
+
return { supported: false };
|
|
2728
|
+
}
|
|
2729
|
+
|
|
2730
|
+
const { usage = 0, quota = 0 } = await navigator.storage.estimate();
|
|
2731
|
+
const percentage = quota > 0 ? usage / quota : 0;
|
|
2732
|
+
const availableMB = Math.round((quota - usage) / (1024 * 1024));
|
|
2733
|
+
|
|
2734
|
+
return {
|
|
2735
|
+
supported: true,
|
|
2736
|
+
percentage,
|
|
2737
|
+
availableMB,
|
|
2738
|
+
usageMB: Math.round(usage / (1024 * 1024)),
|
|
2739
|
+
quotaMB: Math.round(quota / (1024 * 1024))
|
|
2740
|
+
};
|
|
2741
|
+
}
|
|
2742
|
+
|
|
2514
2743
|
async function startRecording(tabId, settings = {}) {
|
|
2515
2744
|
try {
|
|
2516
|
-
// STEP 1:
|
|
2745
|
+
// STEP 1: Detect server mode before starting
|
|
2746
|
+
await detectServerMode();
|
|
2747
|
+
console.log(`[Recording] Mode: ${serverMode}`);
|
|
2748
|
+
|
|
2749
|
+
// STEP 2: Check license and usage limits BEFORE recording
|
|
2517
2750
|
console.log('[License] Checking license before recording...');
|
|
2518
2751
|
const licenseCheck = await LicenseHelper.checkLicenseBeforeRecording();
|
|
2519
2752
|
console.log('[License] License check result:', licenseCheck);
|
|
@@ -2602,6 +2835,18 @@ async function startRecording(tabId, settings = {}) {
|
|
|
2602
2835
|
const sessionId = currentSession.sessionId;
|
|
2603
2836
|
const scheduledStartTime = Date.now() + 3000; // Schedule start 3 seconds in future for countdown synchronization
|
|
2604
2837
|
|
|
2838
|
+
// STEP 4: Initialize browser-only recording if in browser-only mode
|
|
2839
|
+
if (serverMode === 'browser-only' && browserRecordingManager) {
|
|
2840
|
+
console.log('[Recording] Initializing browser-only recording in IndexedDB');
|
|
2841
|
+
const tab = await chrome.tabs.get(tabId);
|
|
2842
|
+
await browserRecordingManager.startRecording(sessionId, {
|
|
2843
|
+
tabId: tabId,
|
|
2844
|
+
url: tab.url,
|
|
2845
|
+
title: tab.title || settings.sessionName || 'Browser Recording',
|
|
2846
|
+
startTime: scheduledStartTime
|
|
2847
|
+
});
|
|
2848
|
+
}
|
|
2849
|
+
|
|
2605
2850
|
// Update legacy state for backward compatibility (will be removed)
|
|
2606
2851
|
recordingTabId = tabId;
|
|
2607
2852
|
isCurrentlyRecording = true;
|
|
@@ -2679,6 +2924,17 @@ async function startRecording(tabId, settings = {}) {
|
|
|
2679
2924
|
}
|
|
2680
2925
|
});
|
|
2681
2926
|
|
|
2927
|
+
// STEP 4: Initialize browser recording if in browser-only mode
|
|
2928
|
+
if (serverMode === 'browser-only' && browserRecordingManager) {
|
|
2929
|
+
const tab = await chrome.tabs.get(tabId);
|
|
2930
|
+
await browserRecordingManager.startRecording(sessionId, {
|
|
2931
|
+
tabId,
|
|
2932
|
+
url: tab.url,
|
|
2933
|
+
title: tab.title
|
|
2934
|
+
});
|
|
2935
|
+
console.log('[Recording] Browser recording initialized for session:', sessionId);
|
|
2936
|
+
}
|
|
2937
|
+
|
|
2682
2938
|
// Send stream ID, session info, and settings to offscreen document for frame capture
|
|
2683
2939
|
await chrome.runtime.sendMessage({
|
|
2684
2940
|
type: 'start-frame-capture',
|
|
@@ -2688,14 +2944,15 @@ async function startRecording(tabId, settings = {}) {
|
|
|
2688
2944
|
sessionId: sessionId,
|
|
2689
2945
|
ownerId: currentOwnerId,
|
|
2690
2946
|
scheduledStartTime: scheduledStartTime,
|
|
2947
|
+
mode: serverMode, // Pass mode to offscreen document
|
|
2691
2948
|
settings: {
|
|
2692
2949
|
frameRate: settings.frameRate || 1,
|
|
2693
|
-
imageQuality: settings.imageQuality || 30,
|
|
2950
|
+
imageQuality: settings.imageQuality || (serverMode === 'browser-only' ? 15 : 30),
|
|
2694
2951
|
frameFlash: settings.frameFlash !== false
|
|
2695
2952
|
}
|
|
2696
2953
|
});
|
|
2697
|
-
|
|
2698
|
-
console.log('Recording started');
|
|
2954
|
+
|
|
2955
|
+
console.log('Recording started in', serverMode, 'mode');
|
|
2699
2956
|
} catch (error) {
|
|
2700
2957
|
console.error('Error in startRecording:', error);
|
|
2701
2958
|
throw error;
|
|
@@ -2727,6 +2984,12 @@ async function stopRecording() {
|
|
|
2727
2984
|
} else {
|
|
2728
2985
|
console.log(`Recording stopped. Duration: ${stopResult.sessionDuration}ms, Frames: ${stopResult.frameCount}`);
|
|
2729
2986
|
|
|
2987
|
+
// Finalize browser-only recording if in browser-only mode
|
|
2988
|
+
if (serverMode === 'browser-only' && browserRecordingManager) {
|
|
2989
|
+
console.log('[Browser-Only] Finalizing browser recording in IndexedDB');
|
|
2990
|
+
await browserRecordingManager.stopRecording(currentSession.sessionId);
|
|
2991
|
+
}
|
|
2992
|
+
|
|
2730
2993
|
// STEP 2: Track usage AFTER successful recording completion
|
|
2731
2994
|
console.log('[License] Tracking usage after recording completion...');
|
|
2732
2995
|
const usageResult = await LicenseHelper.trackUsageAfterRecording();
|
|
@@ -3215,8 +3478,21 @@ async function stopWorkflowRecording(tabId) {
|
|
|
3215
3478
|
let workflowData = response.workflow || [];
|
|
3216
3479
|
let workflow = Array.isArray(workflowData) ? workflowData : (workflowData.actions || []);
|
|
3217
3480
|
let functionTraces = Array.isArray(workflowData) ? [] : (workflowData.functionTraces || []);
|
|
3218
|
-
|
|
3481
|
+
|
|
3219
3482
|
console.log(`[Background] Received ${workflow.length} actions and ${functionTraces.length} function traces`);
|
|
3483
|
+
console.log('[SCREENSHOT-DEBUG] background.stopWorkflowRecording - Checking actions for screenshot_data');
|
|
3484
|
+
|
|
3485
|
+
const actionsWithScreenshots = workflow.filter(a => a.screenshot_data);
|
|
3486
|
+
console.log('[SCREENSHOT-DEBUG] background.stopWorkflowRecording - Actions WITH screenshot_data:', actionsWithScreenshots.length);
|
|
3487
|
+
|
|
3488
|
+
workflow.forEach((action, index) => {
|
|
3489
|
+
console.log(`[SCREENSHOT-DEBUG] background.stopWorkflowRecording - Action ${index}:`, {
|
|
3490
|
+
type: action.type,
|
|
3491
|
+
hasScreenshotData: !!action.screenshot_data,
|
|
3492
|
+
screenshotDataLength: action.screenshot_data?.length,
|
|
3493
|
+
screenshotPreview: action.screenshot_data?.substring(0, 50)
|
|
3494
|
+
});
|
|
3495
|
+
});
|
|
3220
3496
|
|
|
3221
3497
|
// If logs should be included, get them from session storage
|
|
3222
3498
|
if (workflowIncludeLogs.get(tabId)) {
|
|
@@ -3269,22 +3545,40 @@ async function stopWorkflowRecording(tabId) {
|
|
|
3269
3545
|
for (const port of serverPorts) {
|
|
3270
3546
|
try {
|
|
3271
3547
|
console.log(`[Workflow] Attempting to save to server on port ${port}...`);
|
|
3548
|
+
|
|
3549
|
+
const payloadData = {
|
|
3550
|
+
sessionId: sessionId,
|
|
3551
|
+
name: sessionName,
|
|
3552
|
+
url: tab.url,
|
|
3553
|
+
title: tab.title,
|
|
3554
|
+
includeLogs: includeLogs,
|
|
3555
|
+
screenshotSettings: screenshotSettings || {}, // v2.0.8 fix: ensure it's an object, not null
|
|
3556
|
+
actions: workflow,
|
|
3557
|
+
logs: includeLogs ? workflow.flatMap(action => action.logs || []) : [],
|
|
3558
|
+
functionTraces: functionTraces // Include function execution traces
|
|
3559
|
+
};
|
|
3560
|
+
|
|
3561
|
+
console.log('[SCREENSHOT-DEBUG] background.stopWorkflowRecording - HTTP POST payload being prepared');
|
|
3562
|
+
console.log('[SCREENSHOT-DEBUG] background.stopWorkflowRecording - Payload actions count:', payloadData.actions.length);
|
|
3563
|
+
|
|
3564
|
+
const payloadActionsWithScreenshots = payloadData.actions.filter(a => a.screenshot_data);
|
|
3565
|
+
console.log('[SCREENSHOT-DEBUG] background.stopWorkflowRecording - Payload actions WITH screenshot_data:', payloadActionsWithScreenshots.length);
|
|
3566
|
+
|
|
3567
|
+
payloadData.actions.forEach((action, index) => {
|
|
3568
|
+
console.log(`[SCREENSHOT-DEBUG] background.stopWorkflowRecording - Payload Action ${index}:`, {
|
|
3569
|
+
type: action.type,
|
|
3570
|
+
hasScreenshotData: !!action.screenshot_data,
|
|
3571
|
+
screenshotDataLength: action.screenshot_data?.length,
|
|
3572
|
+
screenshotPreview: action.screenshot_data?.substring(0, 50)
|
|
3573
|
+
});
|
|
3574
|
+
});
|
|
3575
|
+
|
|
3272
3576
|
const response = await fetch(`http://localhost:${port}/chromedebug/workflow-recording`, {
|
|
3273
3577
|
method: 'POST',
|
|
3274
3578
|
headers: {
|
|
3275
3579
|
'Content-Type': 'application/json',
|
|
3276
3580
|
},
|
|
3277
|
-
body: JSON.stringify(
|
|
3278
|
-
sessionId: sessionId,
|
|
3279
|
-
name: sessionName,
|
|
3280
|
-
url: tab.url,
|
|
3281
|
-
title: tab.title,
|
|
3282
|
-
includeLogs: includeLogs,
|
|
3283
|
-
screenshotSettings: screenshotSettings || {}, // v2.0.8 fix: ensure it's an object, not null
|
|
3284
|
-
actions: workflow,
|
|
3285
|
-
logs: includeLogs ? workflow.flatMap(action => action.logs || []) : [],
|
|
3286
|
-
functionTraces: functionTraces // Include function execution traces
|
|
3287
|
-
})
|
|
3581
|
+
body: JSON.stringify(payloadData)
|
|
3288
3582
|
});
|
|
3289
3583
|
|
|
3290
3584
|
if (response.ok) {
|
|
@@ -3432,8 +3726,9 @@ async function handleFrameBatch(batchData, skipValidation = false) {
|
|
|
3432
3726
|
try {
|
|
3433
3727
|
const sessionId = batchData.sessionId;
|
|
3434
3728
|
|
|
3435
|
-
//
|
|
3436
|
-
if (
|
|
3729
|
+
// Skip session validation entirely in browser-only mode
|
|
3730
|
+
// Validate session if session manager is available (unless skipping for retry or in browser-only mode)
|
|
3731
|
+
if (sessionManager && !skipValidation && serverMode !== 'browser-only') {
|
|
3437
3732
|
const validationResult = await sessionManager.isSessionValid(sessionId);
|
|
3438
3733
|
if (!validationResult.valid) {
|
|
3439
3734
|
// DON'T LOSE FRAMES - Queue for retry instead of rejecting
|
|
@@ -3537,18 +3832,43 @@ async function handleFrameBatch(batchData, skipValidation = false) {
|
|
|
3537
3832
|
}
|
|
3538
3833
|
|
|
3539
3834
|
if (!uploadSuccess) {
|
|
3540
|
-
console.
|
|
3541
|
-
|
|
3542
|
-
|
|
3543
|
-
|
|
3544
|
-
|
|
3545
|
-
|
|
3546
|
-
|
|
3547
|
-
|
|
3548
|
-
|
|
3549
|
-
|
|
3835
|
+
console.log(`No server available - checking if browser-only mode`);
|
|
3836
|
+
|
|
3837
|
+
// If in browser-only mode, store frames in IndexedDB
|
|
3838
|
+
if (serverMode === 'browser-only' && browserRecordingManager) {
|
|
3839
|
+
console.log(`[Browser-Only] Storing ${indexedFrames.length} frames in IndexedDB`);
|
|
3840
|
+
// Use the browser recording session ID, not the frame capture session ID
|
|
3841
|
+
const recordingSessionId = currentRecordingSessionId || batchData.sessionId;
|
|
3842
|
+
for (const frame of indexedFrames) {
|
|
3843
|
+
// Validate that critical timestamp data exists
|
|
3844
|
+
if (!frame.absoluteTimestamp) {
|
|
3845
|
+
console.warn(`[Browser-Only] WARNING: Frame ${frame.index} missing absoluteTimestamp - log association will fail`);
|
|
3846
|
+
}
|
|
3847
|
+
|
|
3848
|
+
await browserRecordingManager.addFrame(recordingSessionId, {
|
|
3849
|
+
frameIndex: frame.index,
|
|
3850
|
+
screenshot: frame.imageData || frame.screenshot, // Use imageData from frame capture
|
|
3851
|
+
logs: frame.logs || [],
|
|
3852
|
+
timestamp: frame.timestamp || Date.now(),
|
|
3853
|
+
absoluteTimestamp: frame.absoluteTimestamp // Don't use Date.now() fallback - preserve actual capture time
|
|
3854
|
+
});
|
|
3550
3855
|
}
|
|
3551
|
-
|
|
3856
|
+
console.log(`[Browser-Only] Frames stored successfully in IndexedDB for session ${recordingSessionId}`);
|
|
3857
|
+
} else {
|
|
3858
|
+
// Server mode but no server available - show error
|
|
3859
|
+
console.error(`CRITICAL: Failed to upload frame batch to any Chrome Debug server port. Last error: ${lastError}`);
|
|
3860
|
+
console.error(`Session ${batchData.sessionId} frames are only stored in Chrome extension storage and not accessible to Chrome Debug.`);
|
|
3861
|
+
|
|
3862
|
+
// Store error in chrome storage for user visibility
|
|
3863
|
+
await chrome.storage.local.set({
|
|
3864
|
+
[`upload_error_${batchData.sessionId}`]: {
|
|
3865
|
+
error: lastError,
|
|
3866
|
+
timestamp: Date.now(),
|
|
3867
|
+
sessionId: batchData.sessionId,
|
|
3868
|
+
frameCount: indexedFrames.length
|
|
3869
|
+
}
|
|
3870
|
+
});
|
|
3871
|
+
}
|
|
3552
3872
|
}
|
|
3553
3873
|
|
|
3554
3874
|
// Notify LogStreamer that frames have been captured and processed
|
|
@@ -3671,7 +3991,9 @@ async function handleFrameCaptureComplete(sessionData) {
|
|
|
3671
3991
|
await chrome.storage.session.remove(String(tabIdToNotify));
|
|
3672
3992
|
|
|
3673
3993
|
// Send logs to server for association with frames with retry logic
|
|
3674
|
-
|
|
3994
|
+
// Skip server communication in browser-only mode (logs already in IndexedDB)
|
|
3995
|
+
if (bufferedLogs.length > 0 && serverMode !== 'browser-only') {
|
|
3996
|
+
console.log(`[Server Mode] Associating ${bufferedLogs.length} logs with server...`);
|
|
3675
3997
|
|
|
3676
3998
|
// Retry logic to handle race conditions
|
|
3677
3999
|
const maxRetries = 3;
|
|
@@ -3849,6 +4171,62 @@ async function handleFrameCaptureComplete(sessionData) {
|
|
|
3849
4171
|
priority: 1
|
|
3850
4172
|
});
|
|
3851
4173
|
}
|
|
4174
|
+
} else if (bufferedLogs.length > 0 && serverMode === 'browser-only') {
|
|
4175
|
+
// Browser-only mode: Associate logs with frames in IndexedDB
|
|
4176
|
+
console.log(`[Browser-Only] Associating ${bufferedLogs.length} logs with frames in IndexedDB...`);
|
|
4177
|
+
|
|
4178
|
+
try {
|
|
4179
|
+
// Get all frames for this session from IndexedDB
|
|
4180
|
+
const frames = await dataBuffer.getBrowserFrames(sessionIdToUse);
|
|
4181
|
+
console.log(`[Browser-Only] Retrieved ${frames.length} frames from IndexedDB for log association`);
|
|
4182
|
+
|
|
4183
|
+
if (frames.length === 0) {
|
|
4184
|
+
console.warn('[Browser-Only] No frames found - logs cannot be associated');
|
|
4185
|
+
} else {
|
|
4186
|
+
// Associate each log with the appropriate frame based on timestamp
|
|
4187
|
+
// Frames have absoluteTimestamp, logs have timestamp
|
|
4188
|
+
|
|
4189
|
+
for (const log of bufferedLogs) {
|
|
4190
|
+
// Find the frame that this log belongs to (closest frame before the log timestamp)
|
|
4191
|
+
let targetFrame = null;
|
|
4192
|
+
for (let i = 0; i < frames.length; i++) {
|
|
4193
|
+
const frame = frames[i];
|
|
4194
|
+
const frameTime = frame.absoluteTimestamp || frame.timestamp;
|
|
4195
|
+
if (frameTime <= log.timestamp) {
|
|
4196
|
+
targetFrame = frame;
|
|
4197
|
+
} else {
|
|
4198
|
+
break; // Frames are in chronological order
|
|
4199
|
+
}
|
|
4200
|
+
}
|
|
4201
|
+
|
|
4202
|
+
// If no frame found before this log, associate with first frame
|
|
4203
|
+
if (!targetFrame && frames.length > 0) {
|
|
4204
|
+
targetFrame = frames[0];
|
|
4205
|
+
}
|
|
4206
|
+
|
|
4207
|
+
if (targetFrame) {
|
|
4208
|
+
if (!targetFrame.logs) {
|
|
4209
|
+
targetFrame.logs = [];
|
|
4210
|
+
}
|
|
4211
|
+
targetFrame.logs.push(log);
|
|
4212
|
+
}
|
|
4213
|
+
}
|
|
4214
|
+
|
|
4215
|
+
// Update frames in IndexedDB with associated logs
|
|
4216
|
+
let updatedCount = 0;
|
|
4217
|
+
for (const frame of frames) {
|
|
4218
|
+
if (frame.logs && frame.logs.length > 0) {
|
|
4219
|
+
// Update this frame in IndexedDB
|
|
4220
|
+
await dataBuffer.updateBrowserFrame(frame.id, { logs: frame.logs });
|
|
4221
|
+
updatedCount++;
|
|
4222
|
+
}
|
|
4223
|
+
}
|
|
4224
|
+
|
|
4225
|
+
console.log(`[Browser-Only] ✓ Associated logs with ${updatedCount} frames in IndexedDB`);
|
|
4226
|
+
}
|
|
4227
|
+
} catch (error) {
|
|
4228
|
+
console.error('[Browser-Only] Failed to associate logs with frames:', error);
|
|
4229
|
+
}
|
|
3852
4230
|
} else {
|
|
3853
4231
|
// No logs were captured during the session
|
|
3854
4232
|
console.warn('[Recording] No console logs were captured during this session');
|
|
@@ -3866,10 +4244,11 @@ async function handleFrameCaptureComplete(sessionData) {
|
|
|
3866
4244
|
priority: 0
|
|
3867
4245
|
});
|
|
3868
4246
|
}
|
|
3869
|
-
|
|
4247
|
+
|
|
3870
4248
|
// Save screen interactions to database
|
|
3871
|
-
|
|
3872
|
-
|
|
4249
|
+
// Skip server communication in browser-only mode
|
|
4250
|
+
if (screenInteractions.length > 0 && serverMode !== 'browser-only') {
|
|
4251
|
+
console.log(`[Server Mode] Saving ${screenInteractions.length} screen interactions to database`);
|
|
3873
4252
|
|
|
3874
4253
|
// Use comprehensive error handling for screen interactions save
|
|
3875
4254
|
const saveScreenInteractions = async (data, context) => {
|
|
@@ -3935,8 +4314,11 @@ async function handleFrameCaptureComplete(sessionData) {
|
|
|
3935
4314
|
} else {
|
|
3936
4315
|
console.error('[ErrorHandling] Failed to save screen interactions after comprehensive error handling');
|
|
3937
4316
|
}
|
|
4317
|
+
} else if (screenInteractions.length > 0 && serverMode === 'browser-only') {
|
|
4318
|
+
// Browser-only mode: screen interactions not needed
|
|
4319
|
+
console.log(`[Browser-Only] ✓ Skipping screen interactions save (browser-only mode)`);
|
|
3938
4320
|
}
|
|
3939
|
-
|
|
4321
|
+
|
|
3940
4322
|
// Stop session via session manager if available
|
|
3941
4323
|
if (sessionManager && currentSession?.sessionId === sessionData.sessionId && currentOwnerId) {
|
|
3942
4324
|
try {
|
|
@@ -3983,15 +4365,19 @@ async function handleFrameCaptureComplete(sessionData) {
|
|
|
3983
4365
|
priority: 2
|
|
3984
4366
|
});
|
|
3985
4367
|
|
|
3986
|
-
// Notify popup if open
|
|
3987
|
-
|
|
3988
|
-
|
|
3989
|
-
|
|
3990
|
-
|
|
3991
|
-
|
|
3992
|
-
|
|
3993
|
-
|
|
3994
|
-
|
|
4368
|
+
// Notify popup if open (but NOT for browser-only recordings - they're in IndexedDB)
|
|
4369
|
+
if (serverMode !== 'browser-only') {
|
|
4370
|
+
chrome.runtime.sendMessage({
|
|
4371
|
+
action: 'frameSessionComplete',
|
|
4372
|
+
sessionId: sessionData.sessionId,
|
|
4373
|
+
sessionName: currentSession?.settings?.sessionName || null,
|
|
4374
|
+
totalFrames: sessionData.totalFrames,
|
|
4375
|
+
duration: sessionData.duration,
|
|
4376
|
+
serverPort: recordingServerPort
|
|
4377
|
+
}).catch(() => {});
|
|
4378
|
+
} else {
|
|
4379
|
+
console.log('[Browser-Only] Skipping frameSessionComplete message - recording is in IndexedDB');
|
|
4380
|
+
}
|
|
3995
4381
|
|
|
3996
4382
|
// Notify content script to hide recording indicator
|
|
3997
4383
|
if (tabIdToNotify) {
|