@dynamicu/chromedebug-mcp 2.7.1 → 2.7.4
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 +18 -0
- package/README.md +226 -16
- package/chrome-extension/background.js +569 -64
- package/chrome-extension/browser-recording-manager.js +34 -0
- package/chrome-extension/content.js +438 -32
- package/chrome-extension/firebase-config.public-sw.js +1 -1
- package/chrome-extension/firebase-config.public.js +1 -1
- package/chrome-extension/frame-capture.js +31 -10
- package/chrome-extension/image-processor.js +193 -0
- package/chrome-extension/manifest.free.json +1 -1
- package/chrome-extension/options.html +2 -2
- package/chrome-extension/options.js +4 -4
- package/chrome-extension/popup.html +82 -4
- package/chrome-extension/popup.js +1106 -38
- package/chrome-extension/pro/frame-editor.html +259 -6
- package/chrome-extension/pro/frame-editor.js +959 -10
- package/chrome-extension/pro/video-exporter.js +917 -0
- package/chrome-extension/pro/video-player.js +545 -0
- package/dist/chromedebug-extension-free.zip +0 -0
- package/package.json +1 -1
- package/scripts/postinstall.js +1 -1
- package/scripts/webpack.config.free.cjs +6 -0
- package/scripts/webpack.config.pro.cjs +6 -0
- package/src/chrome-controller.js +6 -6
- package/src/database.js +226 -39
- package/src/http-server.js +55 -11
- package/src/validation/schemas.js +20 -5
|
@@ -131,6 +131,15 @@ const WORKFLOW_RECORDING_CONSOLE_CONFIG = {
|
|
|
131
131
|
backgroundAction: 'workflowConsoleLog'
|
|
132
132
|
};
|
|
133
133
|
|
|
134
|
+
// Console interception configuration for screenshot recording
|
|
135
|
+
const SCREENSHOT_RECORDING_CONSOLE_CONFIG = {
|
|
136
|
+
overrideFlagName: '__chromePilotScreenshotConsoleOverridden',
|
|
137
|
+
originalConsoleName: '__chromePilotScreenshotOriginalConsole',
|
|
138
|
+
relayFlagName: '__chromePilotScreenshotConsoleRelay',
|
|
139
|
+
messageType: 'chrome-debug-screenshot-console-log',
|
|
140
|
+
backgroundAction: 'screenshotConsoleLog'
|
|
141
|
+
};
|
|
142
|
+
|
|
134
143
|
// Frame queue for handling validation race conditions
|
|
135
144
|
// v2.1.2: Prevents frame loss when lease renewal hasn't propagated yet
|
|
136
145
|
const pendingFrameQueue = new Map(); // sessionId -> array of frame batches
|
|
@@ -607,7 +616,7 @@ class LogTabBuffer {
|
|
|
607
616
|
* could make LogTabBuffer accept a configurable storage key prefix to eliminate duplication.
|
|
608
617
|
*/
|
|
609
618
|
class WorkflowLogBuffer {
|
|
610
|
-
constructor(tabId) {
|
|
619
|
+
constructor(tabId, customStorageKey = null) {
|
|
611
620
|
this.tabId = tabId;
|
|
612
621
|
this.buffer = [];
|
|
613
622
|
this.processing = false;
|
|
@@ -615,7 +624,7 @@ class WorkflowLogBuffer {
|
|
|
615
624
|
this.totalLogsAdded = 0;
|
|
616
625
|
this.totalLogsFlushed = 0;
|
|
617
626
|
this.lastFlushTime = 0;
|
|
618
|
-
this.storageKey = `workflow_${tabId}`;
|
|
627
|
+
this.storageKey = customStorageKey || `workflow_${tabId}`;
|
|
619
628
|
}
|
|
620
629
|
|
|
621
630
|
// Add log to buffer with queuing to prevent race conditions
|
|
@@ -805,6 +814,23 @@ const DATA_PROTECTION_TIERS = {
|
|
|
805
814
|
}
|
|
806
815
|
};
|
|
807
816
|
|
|
817
|
+
// Centralized screenshot capture throttling
|
|
818
|
+
// Chrome's captureVisibleTab has a rate limit of ~2 calls/second
|
|
819
|
+
let lastCaptureTime = 0;
|
|
820
|
+
const CAPTURE_MIN_INTERVAL_MS = 550; // Slightly under Chrome's limit for safety margin
|
|
821
|
+
|
|
822
|
+
async function throttledCaptureVisibleTab(windowId, options) {
|
|
823
|
+
const now = Date.now();
|
|
824
|
+
const elapsed = now - lastCaptureTime;
|
|
825
|
+
|
|
826
|
+
if (elapsed < CAPTURE_MIN_INTERVAL_MS) {
|
|
827
|
+
await new Promise(r => setTimeout(r, CAPTURE_MIN_INTERVAL_MS - elapsed));
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
lastCaptureTime = Date.now();
|
|
831
|
+
return chrome.tabs.captureVisibleTab(windowId, options);
|
|
832
|
+
}
|
|
833
|
+
|
|
808
834
|
// Initialize on startup
|
|
809
835
|
async function initializeServices() {
|
|
810
836
|
try {
|
|
@@ -914,6 +940,9 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
|
|
914
940
|
const handledByOtherListener = [
|
|
915
941
|
'startWorkflowRecording',
|
|
916
942
|
'stopWorkflowRecording',
|
|
943
|
+
'startScreenshotRecording', // v2.7.3: screenshot recording mode
|
|
944
|
+
'stopScreenshotRecording', // v2.7.3: screenshot recording mode
|
|
945
|
+
'captureScreenshotForMode', // v2.7.3: screenshot capture from content script
|
|
917
946
|
'sendToServer',
|
|
918
947
|
'getScreenshotFormats',
|
|
919
948
|
'saveWorkflowRecording',
|
|
@@ -931,6 +960,7 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
|
|
931
960
|
'openFrameEditor',
|
|
932
961
|
'checkConnection',
|
|
933
962
|
'workflowConsoleLog', // v2.0.8: handle workflow console logs
|
|
963
|
+
'screenshotConsoleLog', // v2.7.3: handle screenshot console logs
|
|
934
964
|
'workflowAction', // v2.0.8: handle workflow actions
|
|
935
965
|
'renewLease', // v2.1.2: session manager lease renewal for frame capture
|
|
936
966
|
'frameSessionComplete', // Frame recording completion notification
|
|
@@ -1080,10 +1110,13 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
|
|
1080
1110
|
// Visual feedback system message handlers
|
|
1081
1111
|
case 'start-screen-capture-tracking':
|
|
1082
1112
|
// Forward to content script for visual feedback
|
|
1083
|
-
|
|
1084
|
-
|
|
1113
|
+
// Use message.tabId for offscreen documents (sender.tab is undefined for offscreen)
|
|
1114
|
+
const startTrackingTabId = message.tabId || sender.tab?.id;
|
|
1115
|
+
if (message.sessionId && startTrackingTabId) {
|
|
1116
|
+
chrome.tabs.sendMessage(startTrackingTabId, {
|
|
1085
1117
|
type: 'start-screen-capture-tracking',
|
|
1086
|
-
sessionId: message.sessionId
|
|
1118
|
+
sessionId: message.sessionId,
|
|
1119
|
+
videoMode: message.videoMode || false
|
|
1087
1120
|
}).catch(() => {
|
|
1088
1121
|
// Content script might not be available
|
|
1089
1122
|
// console.log('[Background] Could not forward start-screen-capture-tracking to content script');
|
|
@@ -1094,8 +1127,10 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
|
|
1094
1127
|
|
|
1095
1128
|
case 'stop-screen-capture-tracking':
|
|
1096
1129
|
// Forward to content script
|
|
1097
|
-
|
|
1098
|
-
|
|
1130
|
+
// Use message.tabId for offscreen documents (sender.tab is undefined for offscreen)
|
|
1131
|
+
const stopTrackingTabId = message.tabId || sender.tab?.id;
|
|
1132
|
+
if (stopTrackingTabId) {
|
|
1133
|
+
chrome.tabs.sendMessage(stopTrackingTabId, {
|
|
1099
1134
|
type: 'stop-screen-capture-tracking'
|
|
1100
1135
|
}).catch(() => {
|
|
1101
1136
|
// console.log('[Background] Could not forward stop-screen-capture-tracking to content script');
|
|
@@ -1150,8 +1185,10 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
|
|
1150
1185
|
|
|
1151
1186
|
case 'show-screen-capture-flash':
|
|
1152
1187
|
// Forward flash trigger to content script
|
|
1153
|
-
|
|
1154
|
-
|
|
1188
|
+
// Use message.tabId for offscreen documents (sender.tab is undefined for offscreen)
|
|
1189
|
+
const flashTabId = message.tabId || sender.tab?.id;
|
|
1190
|
+
if (flashTabId) {
|
|
1191
|
+
chrome.tabs.sendMessage(flashTabId, {
|
|
1155
1192
|
type: 'screen-capture-frame-captured'
|
|
1156
1193
|
}).catch(() => {
|
|
1157
1194
|
// console.log('[Background] Could not forward frame flash to content script');
|
|
@@ -1280,6 +1317,21 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
|
|
1280
1317
|
}
|
|
1281
1318
|
break;
|
|
1282
1319
|
|
|
1320
|
+
case 'deleteAllBrowserRecordings':
|
|
1321
|
+
// Delete all browser-only recordings
|
|
1322
|
+
if (browserRecordingManager) {
|
|
1323
|
+
browserRecordingManager.deleteAllRecordings().then(result => {
|
|
1324
|
+
sendResponse(result);
|
|
1325
|
+
}).catch(error => {
|
|
1326
|
+
console.error('[Background] Failed to delete all recordings:', error);
|
|
1327
|
+
sendResponse({ success: false, deletedCount: 0, error: error.message });
|
|
1328
|
+
});
|
|
1329
|
+
return true; // Async response
|
|
1330
|
+
} else {
|
|
1331
|
+
sendResponse({ success: false, deletedCount: 0, error: 'Manager not available' });
|
|
1332
|
+
}
|
|
1333
|
+
break;
|
|
1334
|
+
|
|
1283
1335
|
case 'getBrowserRecordingFrames':
|
|
1284
1336
|
// Get frames for a browser-only recording
|
|
1285
1337
|
if (dataBuffer && message.sessionId) {
|
|
@@ -1375,12 +1427,35 @@ chrome.runtime.onInstalled.addListener(async (details) => {
|
|
|
1375
1427
|
chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
|
|
1376
1428
|
// Inject console logging on navigation during recording
|
|
1377
1429
|
if (changeInfo.status === 'loading') {
|
|
1378
|
-
//
|
|
1430
|
+
// Skip restricted URLs
|
|
1431
|
+
if (!tab.url || tab.url.startsWith('chrome://') || tab.url.startsWith('chrome-extension://')) {
|
|
1432
|
+
return;
|
|
1433
|
+
}
|
|
1434
|
+
|
|
1435
|
+
// Check for WORKFLOW recording first (takes priority)
|
|
1436
|
+
if (workflowRecordingTabs.has(tabId) && workflowIncludeLogs.get(tabId)) {
|
|
1437
|
+
// console.log('[Navigation] Re-injecting workflow console logging for tab:', tabId);
|
|
1438
|
+
self.ConsoleInterceptionLibrary.startConsoleInterception(tabId, WORKFLOW_RECORDING_CONSOLE_CONFIG).catch(err => {
|
|
1439
|
+
console.error('Failed to re-inject workflow console logging:', err);
|
|
1440
|
+
});
|
|
1441
|
+
return; // Don't also inject screen recording logs
|
|
1442
|
+
}
|
|
1443
|
+
|
|
1444
|
+
// Check for SCREENSHOT recording (second priority)
|
|
1445
|
+
if (screenshotRecordingTabs.has(tabId)) {
|
|
1446
|
+
// console.log('[Navigation] Re-injecting screenshot console logging for tab:', tabId);
|
|
1447
|
+
self.ConsoleInterceptionLibrary.startConsoleInterception(tabId, SCREENSHOT_RECORDING_CONSOLE_CONFIG).catch(err => {
|
|
1448
|
+
console.error('Failed to re-inject screenshot console logging:', err);
|
|
1449
|
+
});
|
|
1450
|
+
return; // Don't also inject screen recording logs
|
|
1451
|
+
}
|
|
1452
|
+
|
|
1453
|
+
// Check for SCREEN recording
|
|
1379
1454
|
if (sessionManager) {
|
|
1380
1455
|
isCurrentlyRecordingAsync().then(async (recording) => {
|
|
1381
1456
|
const currentTabId = await getCurrentTabIdAsync();
|
|
1382
|
-
if (recording && tabId === currentTabId
|
|
1383
|
-
// console.log('Re-injecting console logging for
|
|
1457
|
+
if (recording && tabId === currentTabId) {
|
|
1458
|
+
// console.log('[Navigation] Re-injecting screen recording console logging for tab:', tabId);
|
|
1384
1459
|
startCapturingLogs(tabId).catch(err => {
|
|
1385
1460
|
console.error('Failed to re-inject console logging:', err);
|
|
1386
1461
|
});
|
|
@@ -1389,9 +1464,9 @@ chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
|
|
|
1389
1464
|
console.error('Error checking recording state for navigation:', error);
|
|
1390
1465
|
});
|
|
1391
1466
|
} else {
|
|
1392
|
-
// Legacy fallback
|
|
1393
|
-
if (tabId === recordingTabId && isCurrentlyRecording
|
|
1394
|
-
// console.log('Re-injecting console logging for
|
|
1467
|
+
// Legacy fallback for screen recording
|
|
1468
|
+
if (tabId === recordingTabId && isCurrentlyRecording) {
|
|
1469
|
+
// console.log('[Navigation] Re-injecting screen recording console logging (legacy) for tab:', tabId);
|
|
1395
1470
|
startCapturingLogs(tabId).catch(err => {
|
|
1396
1471
|
console.error('Failed to re-inject console logging:', err);
|
|
1397
1472
|
});
|
|
@@ -1501,6 +1576,40 @@ chrome.tabs.onRemoved.addListener((tabId, removeInfo) => {
|
|
|
1501
1576
|
console.error(`[Workflow] Error updating storage for closed tab ${tabId}:`, err);
|
|
1502
1577
|
});
|
|
1503
1578
|
}
|
|
1579
|
+
|
|
1580
|
+
// Clean up any screenshot recording state for closed tabs
|
|
1581
|
+
if (screenshotRecordingTabs.has(tabId)) {
|
|
1582
|
+
// console.log(`[Screenshot] Tab ${tabId} closed during recording, cleaning up`);
|
|
1583
|
+
screenshotRecordingTabs.delete(tabId);
|
|
1584
|
+
screenshotRecordingSettings.delete(tabId);
|
|
1585
|
+
screenshotRecordingSessionIds.delete(tabId);
|
|
1586
|
+
|
|
1587
|
+
// Clean up screenshot log buffer
|
|
1588
|
+
const screenshotBuffer = screenshotLogBuffers.get(tabId);
|
|
1589
|
+
if (screenshotBuffer) {
|
|
1590
|
+
screenshotBuffer.cleanup().then(() => {
|
|
1591
|
+
screenshotLogBuffers.delete(tabId);
|
|
1592
|
+
// console.log(`[Screenshot] Cleaned up log buffer for closed tab ${tabId}`);
|
|
1593
|
+
}).catch(err => {
|
|
1594
|
+
console.error(`[Screenshot] Error cleaning up buffer for closed tab ${tabId}:`, err);
|
|
1595
|
+
screenshotLogBuffers.delete(tabId);
|
|
1596
|
+
});
|
|
1597
|
+
}
|
|
1598
|
+
|
|
1599
|
+
// Remove session storage
|
|
1600
|
+
chrome.storage.session.remove(`screenshot_logs_${tabId}`).catch(err => {
|
|
1601
|
+
console.error(`[Screenshot] Error removing session storage for closed tab ${tabId}:`, err);
|
|
1602
|
+
});
|
|
1603
|
+
|
|
1604
|
+
// Notify popup that recording has stopped (tab was closed)
|
|
1605
|
+
chrome.storage.local.set({
|
|
1606
|
+
screenshotRecording: false,
|
|
1607
|
+
screenshotStartTime: null,
|
|
1608
|
+
screenshotSessionId: null
|
|
1609
|
+
}).catch(err => {
|
|
1610
|
+
console.error(`[Screenshot] Error updating storage for closed tab ${tabId}:`, err);
|
|
1611
|
+
});
|
|
1612
|
+
}
|
|
1504
1613
|
});
|
|
1505
1614
|
|
|
1506
1615
|
// Session Manager for robust recording state management
|
|
@@ -1519,10 +1628,14 @@ let isCurrentlyRecording = false; // DEPRECATED: Use isCurrentlyRecordingAsync()
|
|
|
1519
1628
|
let currentRecordingSessionId = null; // DEPRECATED: Use getCurrentSessionIdAsync() instead
|
|
1520
1629
|
let frameCounter = new Map(); // DEPRECATED: Session manager handles frame counting
|
|
1521
1630
|
|
|
1631
|
+
// Session metadata cache - preserves essential settings (like videoMode) after currentSession is cleared
|
|
1632
|
+
// This allows in-flight frame batches to access session settings even after stopRecording() executes
|
|
1633
|
+
const sessionMetadataCache = new Map();
|
|
1634
|
+
|
|
1522
1635
|
// =============================================================================
|
|
1523
1636
|
// INACTIVITY AUTO-STOP - Stop recording if user is inactive
|
|
1524
1637
|
// =============================================================================
|
|
1525
|
-
const DEFAULT_INACTIVITY_TIMEOUT_MS =
|
|
1638
|
+
const DEFAULT_INACTIVITY_TIMEOUT_MS = 60 * 1000; // 60 seconds default (1 minute)
|
|
1526
1639
|
let lastUserActivityTime = Date.now();
|
|
1527
1640
|
let inactivityCheckInterval = null;
|
|
1528
1641
|
let currentInactivityTimeout = DEFAULT_INACTIVITY_TIMEOUT_MS;
|
|
@@ -1546,7 +1659,7 @@ async function startInactivityMonitoring(tabId) {
|
|
|
1546
1659
|
|
|
1547
1660
|
// Load user's configured timeout from storage
|
|
1548
1661
|
const settings = await chrome.storage.sync.get(['inactivityTimeout']);
|
|
1549
|
-
currentInactivityTimeout = (settings.inactivityTimeout ||
|
|
1662
|
+
currentInactivityTimeout = (settings.inactivityTimeout || 60) * 1000; // Convert seconds to ms (default 60s)
|
|
1550
1663
|
|
|
1551
1664
|
lastUserActivityTime = Date.now();
|
|
1552
1665
|
|
|
@@ -1784,6 +1897,12 @@ let workflowScreenshotSettings = new Map(); // Map of tabId to screenshot settin
|
|
|
1784
1897
|
let workflowSessionNames = new Map(); // Map of tabId to session name
|
|
1785
1898
|
let workflowLogBuffers = new Map(); // Map of tabId to WorkflowLogBuffer instance
|
|
1786
1899
|
|
|
1900
|
+
// Screenshot recording state (click-to-capture mode with countdown)
|
|
1901
|
+
let screenshotRecordingTabs = new Map(); // Map of tabId to recording state
|
|
1902
|
+
let screenshotRecordingSettings = new Map(); // Map of tabId to {countdown, hideCursor}
|
|
1903
|
+
let screenshotRecordingSessionIds = new Map(); // Map of tabId to sessionId
|
|
1904
|
+
let screenshotLogBuffers = new Map(); // Map of tabId to WorkflowLogBuffer instance for console logs
|
|
1905
|
+
|
|
1787
1906
|
//=============================================================================
|
|
1788
1907
|
// COMPREHENSIVE ERROR HANDLING SYSTEM
|
|
1789
1908
|
// Designed collaboratively with Second Opinion Analyst and Testing Expert
|
|
@@ -2388,6 +2507,7 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
|
|
|
2388
2507
|
}
|
|
2389
2508
|
|
|
2390
2509
|
if (request.action === 'startRecording') {
|
|
2510
|
+
console.log('[Background] startRecording received settings:', request.settings);
|
|
2391
2511
|
startRecording(request.tabId, request.settings).then(() => {
|
|
2392
2512
|
sendResponse({ success: true });
|
|
2393
2513
|
}).catch((error) => {
|
|
@@ -2696,7 +2816,52 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
|
|
|
2696
2816
|
});
|
|
2697
2817
|
return true;
|
|
2698
2818
|
}
|
|
2699
|
-
|
|
2819
|
+
|
|
2820
|
+
// Screenshot recording handlers (v2.7.3)
|
|
2821
|
+
if (request.action === 'startScreenshotRecording') {
|
|
2822
|
+
// console.log('[Screenshot] Starting screenshot recording for tab:', request.tabId);
|
|
2823
|
+
startScreenshotRecording(
|
|
2824
|
+
request.tabId,
|
|
2825
|
+
request.settings
|
|
2826
|
+
).then((result) => {
|
|
2827
|
+
sendResponse({ success: true, sessionId: result.sessionId });
|
|
2828
|
+
}).catch((error) => {
|
|
2829
|
+
console.error('[Screenshot] Error starting screenshot recording:', error);
|
|
2830
|
+
sendResponse({ success: false, error: error.message });
|
|
2831
|
+
});
|
|
2832
|
+
return true;
|
|
2833
|
+
}
|
|
2834
|
+
|
|
2835
|
+
if (request.action === 'stopScreenshotRecording') {
|
|
2836
|
+
const tabId = request.tabId || sender.tab?.id;
|
|
2837
|
+
// console.log('[Screenshot] Stopping screenshot recording for tab:', tabId);
|
|
2838
|
+
stopScreenshotRecording(tabId).then(async (result) => {
|
|
2839
|
+
// Track usage for FREE tier after successful recording
|
|
2840
|
+
if (result.success) {
|
|
2841
|
+
const usageResult = await incrementUsageAfterRecording();
|
|
2842
|
+
result.usage = usageResult;
|
|
2843
|
+
}
|
|
2844
|
+
sendResponse(result);
|
|
2845
|
+
}).catch((error) => {
|
|
2846
|
+
console.error('[Screenshot] Error stopping screenshot recording:', error);
|
|
2847
|
+
sendResponse({ success: false, error: error.message });
|
|
2848
|
+
});
|
|
2849
|
+
return true;
|
|
2850
|
+
}
|
|
2851
|
+
|
|
2852
|
+
// Handle screenshot capture requests from content script
|
|
2853
|
+
if (request.action === 'captureScreenshotForMode') {
|
|
2854
|
+
const tabId = request.tabId || sender.tab?.id;
|
|
2855
|
+
// console.log('[Screenshot] Capturing screenshot for tab:', tabId);
|
|
2856
|
+
captureHighQualityScreenshot(tabId).then((screenshot) => {
|
|
2857
|
+
sendResponse({ success: true, screenshot: screenshot });
|
|
2858
|
+
}).catch((error) => {
|
|
2859
|
+
console.error('[Screenshot] Error capturing screenshot:', error);
|
|
2860
|
+
sendResponse({ success: false, error: error.message });
|
|
2861
|
+
});
|
|
2862
|
+
return true;
|
|
2863
|
+
}
|
|
2864
|
+
|
|
2700
2865
|
// Handle messages from offscreen document
|
|
2701
2866
|
if (request.target === 'background') {
|
|
2702
2867
|
if (request.type === 'frame-batch-ready') {
|
|
@@ -2705,10 +2870,14 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
|
|
|
2705
2870
|
handleFrameCaptureComplete(request.data);
|
|
2706
2871
|
} else if (request.type === 'show-frame-flash') {
|
|
2707
2872
|
// Relay flash message to content script
|
|
2873
|
+
// console.log('[Background] Relaying frame-flash to tab:', request.tabId);
|
|
2708
2874
|
chrome.tabs.sendMessage(request.tabId, {
|
|
2709
2875
|
action: 'showFrameFlash'
|
|
2710
|
-
}).
|
|
2876
|
+
}).then(() => {
|
|
2877
|
+
// console.log('[Background] Frame flash message sent successfully to tab:', request.tabId);
|
|
2878
|
+
}).catch((error) => {
|
|
2711
2879
|
// Content script might not be available, that's ok
|
|
2880
|
+
// console.log('[Background] Frame flash failed for tab:', request.tabId, error?.message);
|
|
2712
2881
|
});
|
|
2713
2882
|
}
|
|
2714
2883
|
}
|
|
@@ -2809,7 +2978,23 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
|
|
|
2809
2978
|
}
|
|
2810
2979
|
}
|
|
2811
2980
|
}
|
|
2812
|
-
|
|
2981
|
+
|
|
2982
|
+
// Buffer screenshot console logs using race-safe WorkflowLogBuffer
|
|
2983
|
+
if (request.action === 'screenshotConsoleLog' && sender.tab) {
|
|
2984
|
+
const tabId = sender.tab.id;
|
|
2985
|
+
if (screenshotRecordingTabs.has(tabId)) {
|
|
2986
|
+
const buffer = screenshotLogBuffers.get(tabId);
|
|
2987
|
+
if (buffer) {
|
|
2988
|
+
// Use race-safe buffer to prevent read-modify-write race conditions
|
|
2989
|
+
buffer.addLog(request.log).catch(err => {
|
|
2990
|
+
console.error('[ScreenshotLogBuffer] Error adding log:', err);
|
|
2991
|
+
});
|
|
2992
|
+
} else {
|
|
2993
|
+
console.warn(`[Screenshot] No buffer found for tab ${tabId}, log dropped`);
|
|
2994
|
+
}
|
|
2995
|
+
}
|
|
2996
|
+
}
|
|
2997
|
+
|
|
2813
2998
|
// Handle getCookies for restore points
|
|
2814
2999
|
if (request.action === 'getCookies' && sender.tab) {
|
|
2815
3000
|
chrome.cookies.getAll({ url: request.url }, (cookies) => {
|
|
@@ -2919,10 +3104,10 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
|
|
|
2919
3104
|
// console.log('[SCREENSHOT-DEBUG] background.js - Sending response with success: true');
|
|
2920
3105
|
sendResponse({ success: true, screenshotData });
|
|
2921
3106
|
}).catch((error) => {
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
3107
|
+
// Rate limit errors are expected and handled by throttling - don't spam console
|
|
3108
|
+
if (!error.message?.includes('MAX_CAPTURE_VISIBLE_TAB_CALLS_PER_SECOND')) {
|
|
3109
|
+
console.error('[Screenshot] Workflow capture failed:', error.message);
|
|
3110
|
+
}
|
|
2926
3111
|
sendResponse({ success: false, error: error.message });
|
|
2927
3112
|
});
|
|
2928
3113
|
return true;
|
|
@@ -2956,7 +3141,7 @@ async function takeStandaloneSnapshot(tabId, note = '') {
|
|
|
2956
3141
|
const sessionId = `snapshot_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
2957
3142
|
|
|
2958
3143
|
// Capture screenshot
|
|
2959
|
-
const screenshotData = await
|
|
3144
|
+
const screenshotData = await throttledCaptureVisibleTab(null, {
|
|
2960
3145
|
format: 'jpeg',
|
|
2961
3146
|
quality: 30 // Default quality for snapshots
|
|
2962
3147
|
});
|
|
@@ -3069,7 +3254,7 @@ async function captureTabScreenshot(tabId, settings) {
|
|
|
3069
3254
|
// console.log('[SCREENSHOT-DEBUG] captureTabScreenshot - captureOptions:', JSON.stringify(captureOptions));
|
|
3070
3255
|
// console.log('[SCREENSHOT-DEBUG] captureTabScreenshot - Calling chrome.tabs.captureVisibleTab...');
|
|
3071
3256
|
|
|
3072
|
-
const dataUrl = await
|
|
3257
|
+
const dataUrl = await throttledCaptureVisibleTab(windowId, captureOptions);
|
|
3073
3258
|
|
|
3074
3259
|
// console.log('[SCREENSHOT-DEBUG] captureTabScreenshot - captureVisibleTab SUCCESS');
|
|
3075
3260
|
// console.log('[SCREENSHOT-DEBUG] captureTabScreenshot - dataUrl length:', dataUrl?.length);
|
|
@@ -3121,10 +3306,12 @@ async function captureTabScreenshot(tabId, settings) {
|
|
|
3121
3306
|
// console.log('[SCREENSHOT-DEBUG] captureTabScreenshot - Final dataUrl length:', dataUrl?.length);
|
|
3122
3307
|
return dataUrl;
|
|
3123
3308
|
} catch (error) {
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
|
|
3309
|
+
// Only log unexpected errors, rate limits are handled by throttling
|
|
3310
|
+
if (error.message?.includes('MAX_CAPTURE_VISIBLE_TAB_CALLS_PER_SECOND')) {
|
|
3311
|
+
console.warn('[Screenshot] Rate limited by Chrome, throttling applied');
|
|
3312
|
+
} else {
|
|
3313
|
+
console.error('[Screenshot] Capture failed:', error.message);
|
|
3314
|
+
}
|
|
3128
3315
|
throw error;
|
|
3129
3316
|
}
|
|
3130
3317
|
}
|
|
@@ -3251,6 +3438,13 @@ async function startRecording(tabId, settings = {}) {
|
|
|
3251
3438
|
currentOwnerId = sessionResult.ownerId;
|
|
3252
3439
|
|
|
3253
3440
|
const sessionId = currentSession.sessionId;
|
|
3441
|
+
|
|
3442
|
+
// Cache essential metadata for this session (persists after currentSession is cleared)
|
|
3443
|
+
// This ensures in-flight frame batches can access videoMode even after stopRecording()
|
|
3444
|
+
sessionMetadataCache.set(sessionId, {
|
|
3445
|
+
videoMode: settings.videoMode || false,
|
|
3446
|
+
sessionName: settings.sessionName || null
|
|
3447
|
+
});
|
|
3254
3448
|
const scheduledStartTime = Date.now() + 3000; // Schedule start 3 seconds in future for countdown synchronization
|
|
3255
3449
|
|
|
3256
3450
|
// STEP 4: Initialize browser-only recording if in browser-only mode
|
|
@@ -3261,7 +3455,8 @@ async function startRecording(tabId, settings = {}) {
|
|
|
3261
3455
|
tabId: tabId,
|
|
3262
3456
|
url: tab.url,
|
|
3263
3457
|
title: tab.title || settings.sessionName || 'Browser Recording',
|
|
3264
|
-
startTime: scheduledStartTime
|
|
3458
|
+
startTime: scheduledStartTime,
|
|
3459
|
+
isVideoMode: settings.videoMode || false
|
|
3265
3460
|
});
|
|
3266
3461
|
}
|
|
3267
3462
|
|
|
@@ -3277,12 +3472,15 @@ async function startRecording(tabId, settings = {}) {
|
|
|
3277
3472
|
logStreamer.setSessionId(sessionId);
|
|
3278
3473
|
}
|
|
3279
3474
|
|
|
3280
|
-
// Update storage
|
|
3475
|
+
// Update storage - include isVideoModeActive to prevent race condition with popup
|
|
3476
|
+
// The popup's storage listener checks for recordingActive changes and calls updateRecordingUI()
|
|
3477
|
+
// If isVideoModeActive isn't included, the popup will show wrong button states
|
|
3281
3478
|
chrome.storage.local.set({
|
|
3282
3479
|
recordingActive: true,
|
|
3283
3480
|
recordingStartTime: scheduledStartTime,
|
|
3284
3481
|
recordingTabId: tabId,
|
|
3285
|
-
recordingSessionId: sessionId
|
|
3482
|
+
recordingSessionId: sessionId,
|
|
3483
|
+
isVideoModeActive: settings.videoMode || false
|
|
3286
3484
|
});
|
|
3287
3485
|
|
|
3288
3486
|
// Update badge
|
|
@@ -3315,10 +3513,12 @@ async function startRecording(tabId, settings = {}) {
|
|
|
3315
3513
|
}
|
|
3316
3514
|
|
|
3317
3515
|
// Notify content script that recording has started with scheduled time
|
|
3516
|
+
console.log('[Background] Sending recordingStarted to content with videoMode:', settings.videoMode);
|
|
3318
3517
|
chrome.tabs.sendMessage(tabId, {
|
|
3319
3518
|
action: 'recordingStarted',
|
|
3320
3519
|
scheduledStartTime: scheduledStartTime,
|
|
3321
|
-
sessionId: sessionId
|
|
3520
|
+
sessionId: sessionId,
|
|
3521
|
+
videoMode: settings.videoMode || false // Pass video mode for hidden indicator
|
|
3322
3522
|
}).catch(async (error) => {
|
|
3323
3523
|
// Content script might not be injected, try to inject it
|
|
3324
3524
|
// console.log('Could not notify content script, attempting to inject:', error);
|
|
@@ -3344,7 +3544,8 @@ async function startRecording(tabId, settings = {}) {
|
|
|
3344
3544
|
chrome.tabs.sendMessage(tabId, {
|
|
3345
3545
|
action: 'recordingStarted',
|
|
3346
3546
|
scheduledStartTime: scheduledStartTime,
|
|
3347
|
-
sessionId: sessionId
|
|
3547
|
+
sessionId: sessionId,
|
|
3548
|
+
videoMode: settings.videoMode || false
|
|
3348
3549
|
}).catch(() => {
|
|
3349
3550
|
console.error('Failed to show recording indicator even after injection');
|
|
3350
3551
|
});
|
|
@@ -3360,12 +3561,22 @@ async function startRecording(tabId, settings = {}) {
|
|
|
3360
3561
|
await browserRecordingManager.startRecording(sessionId, {
|
|
3361
3562
|
tabId,
|
|
3362
3563
|
url: tab.url,
|
|
3363
|
-
title: tab.title
|
|
3564
|
+
title: tab.title,
|
|
3565
|
+
isVideoMode: settings.videoMode || false
|
|
3364
3566
|
});
|
|
3365
3567
|
// console.log('[Recording] Browser recording initialized for session:', sessionId);
|
|
3366
3568
|
}
|
|
3367
3569
|
|
|
3368
3570
|
// Send stream ID, session info, and settings to offscreen document for frame capture
|
|
3571
|
+
const frameCaptureSettings = {
|
|
3572
|
+
frameRate: settings.frameRate || 1,
|
|
3573
|
+
imageQuality: settings.imageQuality || (serverMode === 'browser-only' ? 15 : 30),
|
|
3574
|
+
frameFlash: settings.frameFlash !== false,
|
|
3575
|
+
maxWidth: settings.maxWidth, // Pass through for video mode (undefined uses default)
|
|
3576
|
+
maxHeight: settings.maxHeight, // Pass through for video mode (undefined uses default)
|
|
3577
|
+
videoMode: settings.videoMode || false
|
|
3578
|
+
};
|
|
3579
|
+
console.log('[Background] Sending frame capture settings:', frameCaptureSettings);
|
|
3369
3580
|
await chrome.runtime.sendMessage({
|
|
3370
3581
|
type: 'start-frame-capture',
|
|
3371
3582
|
target: 'offscreen',
|
|
@@ -3375,11 +3586,7 @@ async function startRecording(tabId, settings = {}) {
|
|
|
3375
3586
|
ownerId: currentOwnerId,
|
|
3376
3587
|
scheduledStartTime: scheduledStartTime,
|
|
3377
3588
|
mode: serverMode, // Pass mode to offscreen document
|
|
3378
|
-
settings:
|
|
3379
|
-
frameRate: settings.frameRate || 1,
|
|
3380
|
-
imageQuality: settings.imageQuality || (serverMode === 'browser-only' ? 15 : 30),
|
|
3381
|
-
frameFlash: settings.frameFlash !== false
|
|
3382
|
-
}
|
|
3589
|
+
settings: frameCaptureSettings
|
|
3383
3590
|
});
|
|
3384
3591
|
|
|
3385
3592
|
// console.log('Recording started in', serverMode, 'mode');
|
|
@@ -3514,10 +3721,20 @@ async function stopRecording() {
|
|
|
3514
3721
|
}
|
|
3515
3722
|
}
|
|
3516
3723
|
|
|
3724
|
+
// Note: stoppedSessionId was already captured at the start of stopRecording()
|
|
3725
|
+
// It's used below for deferred cache cleanup
|
|
3726
|
+
|
|
3517
3727
|
// Clear session cache
|
|
3518
3728
|
currentSession = null;
|
|
3519
3729
|
currentOwnerId = null;
|
|
3520
3730
|
|
|
3731
|
+
// Schedule deferred cleanup of metadata cache (in-flight batches may still need it)
|
|
3732
|
+
if (stoppedSessionId && sessionMetadataCache.has(stoppedSessionId)) {
|
|
3733
|
+
setTimeout(() => {
|
|
3734
|
+
sessionMetadataCache.delete(stoppedSessionId);
|
|
3735
|
+
}, 10000); // 10 second delay to allow in-flight batches to complete
|
|
3736
|
+
}
|
|
3737
|
+
|
|
3521
3738
|
// Update legacy state for backward compatibility (will be removed)
|
|
3522
3739
|
isCurrentlyRecording = false;
|
|
3523
3740
|
recordingTabId = null;
|
|
@@ -3816,7 +4033,8 @@ async function stopWorkflowRecording(tabId) {
|
|
|
3816
4033
|
screenshotSettings: screenshotSettings || {}, // v2.0.8 fix: ensure it's an object, not null
|
|
3817
4034
|
actions: workflow,
|
|
3818
4035
|
logs: includeLogs ? workflow.flatMap(action => action.logs || []) : [],
|
|
3819
|
-
functionTraces: functionTraces // Include function execution traces
|
|
4036
|
+
functionTraces: functionTraces, // Include function execution traces
|
|
4037
|
+
recordingMode: 'workflow' // Explicitly mark as workflow recording for tab filtering
|
|
3820
4038
|
};
|
|
3821
4039
|
|
|
3822
4040
|
// console.log('[SCREENSHOT-DEBUG] background.stopWorkflowRecording - HTTP POST payload being prepared');
|
|
@@ -3920,6 +4138,278 @@ async function stopCapturingWorkflowLogs(tabId) {
|
|
|
3920
4138
|
return await self.ConsoleInterceptionLibrary.stopConsoleInterception(tabId, WORKFLOW_RECORDING_CONSOLE_CONFIG);
|
|
3921
4139
|
}
|
|
3922
4140
|
|
|
4141
|
+
// ============================================================
|
|
4142
|
+
// SCREENSHOT RECORDING FUNCTIONS (v2.7.3)
|
|
4143
|
+
// Click-to-capture mode with configurable countdown
|
|
4144
|
+
// ============================================================
|
|
4145
|
+
|
|
4146
|
+
/**
|
|
4147
|
+
* Start screenshot recording mode for a tab
|
|
4148
|
+
* @param {number} tabId - The tab ID to start recording on
|
|
4149
|
+
* @param {object} settings - Recording settings {countdown: number, hideCursor: boolean}
|
|
4150
|
+
*/
|
|
4151
|
+
async function startScreenshotRecording(tabId, settings = {}) {
|
|
4152
|
+
try {
|
|
4153
|
+
// console.log('[Screenshot] Starting screenshot recording for tab:', tabId, 'settings:', settings);
|
|
4154
|
+
|
|
4155
|
+
// Check if this tab allows content script injection
|
|
4156
|
+
const tab = await chrome.tabs.get(tabId);
|
|
4157
|
+
if (!tab.url || tab.url.startsWith('chrome://') || tab.url.startsWith('chrome-extension://') || tab.url.startsWith('moz-extension://')) {
|
|
4158
|
+
throw new Error('Cannot record screenshots on restricted pages (chrome:// URLs)');
|
|
4159
|
+
}
|
|
4160
|
+
|
|
4161
|
+
// Generate session ID
|
|
4162
|
+
const sessionId = `screenshot_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
4163
|
+
|
|
4164
|
+
// Store settings
|
|
4165
|
+
screenshotRecordingTabs.set(tabId, true);
|
|
4166
|
+
screenshotRecordingSettings.set(tabId, settings);
|
|
4167
|
+
screenshotRecordingSessionIds.set(tabId, sessionId);
|
|
4168
|
+
|
|
4169
|
+
// Store session ID for recovery
|
|
4170
|
+
await chrome.storage.local.set({ currentScreenshotSessionId: sessionId });
|
|
4171
|
+
|
|
4172
|
+
// Initialize console log capture for screenshot recording
|
|
4173
|
+
// Clear any existing logs and set up log buffer
|
|
4174
|
+
await chrome.storage.session.set({ [`screenshot_logs_${tabId}`]: [] });
|
|
4175
|
+
const buffer = new WorkflowLogBuffer(tabId, `screenshot_logs_${tabId}`);
|
|
4176
|
+
screenshotLogBuffers.set(tabId, buffer);
|
|
4177
|
+
|
|
4178
|
+
// Ensure content script is injected before sending message
|
|
4179
|
+
try {
|
|
4180
|
+
await chrome.scripting.executeScript({
|
|
4181
|
+
target: { tabId: tabId },
|
|
4182
|
+
files: ['content.js']
|
|
4183
|
+
});
|
|
4184
|
+
await chrome.scripting.insertCSS({
|
|
4185
|
+
target: { tabId: tabId },
|
|
4186
|
+
files: ['content.css']
|
|
4187
|
+
});
|
|
4188
|
+
} catch (e) {
|
|
4189
|
+
// Content script might already be injected - this is fine
|
|
4190
|
+
// console.log('[Screenshot] Content script injection note:', e.message);
|
|
4191
|
+
}
|
|
4192
|
+
|
|
4193
|
+
// Start console interception for log capture
|
|
4194
|
+
await self.ConsoleInterceptionLibrary.startConsoleInterception(
|
|
4195
|
+
tabId,
|
|
4196
|
+
SCREENSHOT_RECORDING_CONSOLE_CONFIG
|
|
4197
|
+
);
|
|
4198
|
+
|
|
4199
|
+
// Tell the content script to start screenshot recording mode
|
|
4200
|
+
await chrome.tabs.sendMessage(tabId, {
|
|
4201
|
+
action: 'startScreenshotRecording',
|
|
4202
|
+
settings: settings,
|
|
4203
|
+
tabId: tabId,
|
|
4204
|
+
sessionId: sessionId
|
|
4205
|
+
});
|
|
4206
|
+
|
|
4207
|
+
// console.log('[Screenshot] Screenshot recording started successfully, sessionId:', sessionId);
|
|
4208
|
+
return { success: true, sessionId: sessionId };
|
|
4209
|
+
} catch (error) {
|
|
4210
|
+
console.error('[Screenshot] Error starting screenshot recording:', error);
|
|
4211
|
+
throw error;
|
|
4212
|
+
}
|
|
4213
|
+
}
|
|
4214
|
+
|
|
4215
|
+
/**
|
|
4216
|
+
* Stop screenshot recording and save to server
|
|
4217
|
+
* @param {number} tabId - The tab ID to stop recording on
|
|
4218
|
+
*/
|
|
4219
|
+
async function stopScreenshotRecording(tabId) {
|
|
4220
|
+
try {
|
|
4221
|
+
// console.log('[Screenshot] Stopping screenshot recording for tab:', tabId);
|
|
4222
|
+
|
|
4223
|
+
// Check if recording was active
|
|
4224
|
+
if (!screenshotRecordingTabs.has(tabId)) {
|
|
4225
|
+
throw new Error('No screenshot recording active for this tab');
|
|
4226
|
+
}
|
|
4227
|
+
|
|
4228
|
+
// Get screenshot data from content script
|
|
4229
|
+
const response = await chrome.tabs.sendMessage(tabId, {
|
|
4230
|
+
action: 'getScreenshotData'
|
|
4231
|
+
});
|
|
4232
|
+
|
|
4233
|
+
if (!response || !response.success) {
|
|
4234
|
+
throw new Error('Failed to get screenshots from content script');
|
|
4235
|
+
}
|
|
4236
|
+
|
|
4237
|
+
let screenshots = response.screenshots || [];
|
|
4238
|
+
// console.log(`[Screenshot] Retrieved ${screenshots.length} screenshots`);
|
|
4239
|
+
|
|
4240
|
+
// Get settings before cleanup
|
|
4241
|
+
const sessionId = screenshotRecordingSessionIds.get(tabId);
|
|
4242
|
+
const settings = screenshotRecordingSettings.get(tabId);
|
|
4243
|
+
|
|
4244
|
+
// Flush log buffer and get logs
|
|
4245
|
+
const buffer = screenshotLogBuffers.get(tabId);
|
|
4246
|
+
if (buffer) {
|
|
4247
|
+
await buffer.flush();
|
|
4248
|
+
}
|
|
4249
|
+
|
|
4250
|
+
// Get logs from session storage
|
|
4251
|
+
const storageKey = `screenshot_logs_${tabId}`;
|
|
4252
|
+
const logResult = await chrome.storage.session.get(storageKey);
|
|
4253
|
+
const allLogs = logResult[storageKey] || [];
|
|
4254
|
+
// console.log(`[Screenshot] Retrieved ${allLogs.length} console logs`);
|
|
4255
|
+
|
|
4256
|
+
// Associate logs with screenshots using timestamp windows
|
|
4257
|
+
// Window: 500ms before click to min(next click timestamp, current + 10s)
|
|
4258
|
+
screenshots = screenshots.map((screenshot, index) => {
|
|
4259
|
+
const windowStart = screenshot.timestamp - 500; // 500ms before click
|
|
4260
|
+
const maxWindowEnd = screenshot.timestamp + 10000; // 10s max window
|
|
4261
|
+
const nextTimestamp = index < screenshots.length - 1
|
|
4262
|
+
? screenshots[index + 1].timestamp
|
|
4263
|
+
: screenshot.timestamp + 10000;
|
|
4264
|
+
const windowEnd = Math.min(nextTimestamp, maxWindowEnd);
|
|
4265
|
+
|
|
4266
|
+
const screenshotLogs = allLogs.filter(log =>
|
|
4267
|
+
log.timestamp >= windowStart && log.timestamp < windowEnd
|
|
4268
|
+
);
|
|
4269
|
+
|
|
4270
|
+
return {
|
|
4271
|
+
...screenshot,
|
|
4272
|
+
logs: screenshotLogs
|
|
4273
|
+
};
|
|
4274
|
+
});
|
|
4275
|
+
|
|
4276
|
+
// Stop console interception
|
|
4277
|
+
try {
|
|
4278
|
+
await self.ConsoleInterceptionLibrary.stopConsoleInterception(
|
|
4279
|
+
tabId,
|
|
4280
|
+
SCREENSHOT_RECORDING_CONSOLE_CONFIG
|
|
4281
|
+
);
|
|
4282
|
+
} catch (e) {
|
|
4283
|
+
// Ignore errors when stopping interception
|
|
4284
|
+
}
|
|
4285
|
+
|
|
4286
|
+
// Clean up log buffers
|
|
4287
|
+
if (buffer) {
|
|
4288
|
+
await buffer.cleanup();
|
|
4289
|
+
}
|
|
4290
|
+
screenshotLogBuffers.delete(tabId);
|
|
4291
|
+
await chrome.storage.session.remove(storageKey);
|
|
4292
|
+
|
|
4293
|
+
// Clean up state
|
|
4294
|
+
screenshotRecordingTabs.delete(tabId);
|
|
4295
|
+
screenshotRecordingSettings.delete(tabId);
|
|
4296
|
+
screenshotRecordingSessionIds.delete(tabId);
|
|
4297
|
+
|
|
4298
|
+
// Notify popup that recording has stopped
|
|
4299
|
+
await chrome.storage.local.set({
|
|
4300
|
+
screenshotRecording: false,
|
|
4301
|
+
screenshotStartTime: null,
|
|
4302
|
+
screenshotSessionId: null
|
|
4303
|
+
});
|
|
4304
|
+
|
|
4305
|
+
// Get current URL and title
|
|
4306
|
+
const tab = await chrome.tabs.get(tabId);
|
|
4307
|
+
|
|
4308
|
+
// Try to save to server
|
|
4309
|
+
try {
|
|
4310
|
+
const serverPorts = CONFIG_PORTS.slice(0, 5);
|
|
4311
|
+
let serverResult = null;
|
|
4312
|
+
|
|
4313
|
+
for (const port of serverPorts) {
|
|
4314
|
+
try {
|
|
4315
|
+
// Convert screenshots to action format for workflow storage (with logs)
|
|
4316
|
+
const actions = screenshots.map((screenshot, index) => ({
|
|
4317
|
+
type: 'screenshot',
|
|
4318
|
+
timestamp: screenshot.timestamp,
|
|
4319
|
+
x: screenshot.x || 0,
|
|
4320
|
+
y: screenshot.y || 0,
|
|
4321
|
+
screenshot_data: screenshot.data,
|
|
4322
|
+
logs: screenshot.logs || [],
|
|
4323
|
+
index: index
|
|
4324
|
+
}));
|
|
4325
|
+
|
|
4326
|
+
const payloadData = {
|
|
4327
|
+
sessionId: sessionId,
|
|
4328
|
+
name: null, // Screenshots don't have custom names
|
|
4329
|
+
url: tab.url,
|
|
4330
|
+
title: tab.title,
|
|
4331
|
+
includeLogs: true, // Now capturing logs
|
|
4332
|
+
screenshotSettings: {
|
|
4333
|
+
enabled: true,
|
|
4334
|
+
format: 'jpeg',
|
|
4335
|
+
quality: 100 // High quality for screenshot mode
|
|
4336
|
+
},
|
|
4337
|
+
actions: actions,
|
|
4338
|
+
logs: allLogs, // Include all logs for the session
|
|
4339
|
+
functionTraces: [],
|
|
4340
|
+
recordingMode: 'screenshot' // Mark as screenshot recording
|
|
4341
|
+
};
|
|
4342
|
+
|
|
4343
|
+
const httpResponse = await fetch(`http://localhost:${port}/chromedebug/workflow-recording`, {
|
|
4344
|
+
method: 'POST',
|
|
4345
|
+
headers: {
|
|
4346
|
+
'Content-Type': 'application/json'
|
|
4347
|
+
},
|
|
4348
|
+
body: JSON.stringify(payloadData)
|
|
4349
|
+
});
|
|
4350
|
+
|
|
4351
|
+
if (httpResponse.ok) {
|
|
4352
|
+
serverResult = await httpResponse.json();
|
|
4353
|
+
// console.log(`[Screenshot] Saved to server on port ${port}:`, serverResult);
|
|
4354
|
+
break;
|
|
4355
|
+
}
|
|
4356
|
+
} catch (error) {
|
|
4357
|
+
// console.log(`[Screenshot] Failed to save to server on port ${port}:`, error.message);
|
|
4358
|
+
}
|
|
4359
|
+
}
|
|
4360
|
+
|
|
4361
|
+
// Clean up session ID from storage
|
|
4362
|
+
await chrome.storage.local.remove(['currentScreenshotSessionId']);
|
|
4363
|
+
|
|
4364
|
+
if (serverResult) {
|
|
4365
|
+
return {
|
|
4366
|
+
success: true,
|
|
4367
|
+
screenshotCount: screenshots.length,
|
|
4368
|
+
sessionId: serverResult.workflowId || serverResult.sessionId || sessionId,
|
|
4369
|
+
savedToServer: true
|
|
4370
|
+
};
|
|
4371
|
+
} else {
|
|
4372
|
+
return {
|
|
4373
|
+
success: true,
|
|
4374
|
+
screenshotCount: screenshots.length,
|
|
4375
|
+
sessionId: sessionId,
|
|
4376
|
+
savedToServer: false
|
|
4377
|
+
};
|
|
4378
|
+
}
|
|
4379
|
+
} catch (error) {
|
|
4380
|
+
console.error('[Screenshot] Error saving screenshots:', error);
|
|
4381
|
+
await chrome.storage.local.remove(['currentScreenshotSessionId']);
|
|
4382
|
+
return {
|
|
4383
|
+
success: true,
|
|
4384
|
+
screenshotCount: screenshots.length,
|
|
4385
|
+
sessionId: sessionId,
|
|
4386
|
+
savedToServer: false,
|
|
4387
|
+
error: error.message
|
|
4388
|
+
};
|
|
4389
|
+
}
|
|
4390
|
+
} catch (error) {
|
|
4391
|
+
console.error('[Screenshot] Error stopping screenshot recording:', error);
|
|
4392
|
+
throw error;
|
|
4393
|
+
}
|
|
4394
|
+
}
|
|
4395
|
+
|
|
4396
|
+
/**
|
|
4397
|
+
* Capture a high-quality screenshot for screenshot recording mode
|
|
4398
|
+
* @param {number} tabId - The tab to capture
|
|
4399
|
+
*/
|
|
4400
|
+
async function captureHighQualityScreenshot(tabId) {
|
|
4401
|
+
try {
|
|
4402
|
+
const dataUrl = await throttledCaptureVisibleTab(null, {
|
|
4403
|
+
format: 'jpeg',
|
|
4404
|
+
quality: 100 // Best quality for screenshot mode
|
|
4405
|
+
});
|
|
4406
|
+
return dataUrl;
|
|
4407
|
+
} catch (error) {
|
|
4408
|
+
console.error('[Screenshot] Error capturing screenshot:', error);
|
|
4409
|
+
throw error;
|
|
4410
|
+
}
|
|
4411
|
+
}
|
|
4412
|
+
|
|
3923
4413
|
// Delete recording from server
|
|
3924
4414
|
async function deleteRecordingFromServer(recordingId, sendResponse) {
|
|
3925
4415
|
try {
|
|
@@ -4078,9 +4568,13 @@ async function handleFrameBatch(batchData, skipValidation = false) {
|
|
|
4078
4568
|
const ports = await getServerPorts();
|
|
4079
4569
|
let uploadSuccess = false;
|
|
4080
4570
|
let lastError = null;
|
|
4081
|
-
|
|
4082
|
-
// Get session name from current session
|
|
4083
|
-
|
|
4571
|
+
|
|
4572
|
+
// Get session name and video mode from current session OR cached metadata
|
|
4573
|
+
// The cache persists after stopRecording() clears currentSession, ensuring
|
|
4574
|
+
// in-flight frame batches still have access to videoMode setting
|
|
4575
|
+
const cachedMetadata = sessionMetadataCache.get(batchData.sessionId);
|
|
4576
|
+
const sessionName = currentSession?.settings?.sessionName || cachedMetadata?.sessionName || null;
|
|
4577
|
+
const isVideoMode = currentSession?.settings?.videoMode ?? cachedMetadata?.videoMode ?? false;
|
|
4084
4578
|
|
|
4085
4579
|
for (const port of ports) {
|
|
4086
4580
|
try {
|
|
@@ -4090,6 +4584,8 @@ async function handleFrameBatch(batchData, skipValidation = false) {
|
|
|
4090
4584
|
if (sessionName) {
|
|
4091
4585
|
formData.append('sessionName', sessionName);
|
|
4092
4586
|
}
|
|
4587
|
+
// Always include isVideoMode for proper recording classification
|
|
4588
|
+
formData.append('isVideoMode', isVideoMode.toString());
|
|
4093
4589
|
|
|
4094
4590
|
const uploadResponse = await fetch(`http://localhost:${port}/chromedebug/frame-batch`, {
|
|
4095
4591
|
method: 'POST',
|
|
@@ -4101,33 +4597,41 @@ async function handleFrameBatch(batchData, skipValidation = false) {
|
|
|
4101
4597
|
// console.log(`Frame batch uploaded successfully to port ${port}:`, result);
|
|
4102
4598
|
recordingServerPort = port; // Store the successful port
|
|
4103
4599
|
uploadSuccess = true;
|
|
4104
|
-
|
|
4105
|
-
//
|
|
4106
|
-
|
|
4107
|
-
|
|
4108
|
-
|
|
4109
|
-
|
|
4110
|
-
|
|
4111
|
-
|
|
4112
|
-
|
|
4113
|
-
|
|
4114
|
-
// Append new frames
|
|
4115
|
-
sessionData.frames = sessionData.frames.concat(batchData.frames);
|
|
4116
|
-
await chrome.storage.local.set({ [storageKey]: sessionData });
|
|
4117
|
-
// console.log(`Saved ${batchData.frames.length} frames to local storage, total: ${sessionData.frames.length}`);
|
|
4600
|
+
|
|
4601
|
+
// NOTE: Do NOT store frames to local storage in server mode!
|
|
4602
|
+
// Chrome extension storage.local has a 5MB quota limit.
|
|
4603
|
+
// At 4 FPS with ~100KB/frame, this fills in ~12 seconds.
|
|
4604
|
+
// Frames are already on the server, no backup needed.
|
|
4605
|
+
|
|
4606
|
+
// Track frame count in memory instead
|
|
4607
|
+
const currentCount = frameCounter.get(batchData.sessionId) || 0;
|
|
4608
|
+
const newCount = currentCount + batchData.frames.length;
|
|
4609
|
+
frameCounter.set(batchData.sessionId, newCount);
|
|
4118
4610
|
|
|
4119
4611
|
// Send frame count update to content script
|
|
4120
4612
|
if (recordingTabId) {
|
|
4121
4613
|
try {
|
|
4122
4614
|
await chrome.tabs.sendMessage(recordingTabId, {
|
|
4123
4615
|
action: 'updateFrameCount',
|
|
4124
|
-
frameCount:
|
|
4616
|
+
frameCount: newCount
|
|
4125
4617
|
});
|
|
4126
4618
|
} catch (e) {
|
|
4127
4619
|
// Tab might be closed, ignore
|
|
4128
4620
|
}
|
|
4129
4621
|
}
|
|
4130
4622
|
|
|
4623
|
+
// Clean up metadata cache if recording is stopped
|
|
4624
|
+
// Use a delay to ensure any remaining batches can still access the cache
|
|
4625
|
+
if (!isCurrentlyRecording && sessionMetadataCache.has(batchData.sessionId)) {
|
|
4626
|
+
const sessionToClean = batchData.sessionId;
|
|
4627
|
+
setTimeout(() => {
|
|
4628
|
+
// Only delete if this specific session is no longer active
|
|
4629
|
+
if (currentSession?.sessionId !== sessionToClean) {
|
|
4630
|
+
sessionMetadataCache.delete(sessionToClean);
|
|
4631
|
+
}
|
|
4632
|
+
}, 10000); // 10 second delay for safety margin
|
|
4633
|
+
}
|
|
4634
|
+
|
|
4131
4635
|
break;
|
|
4132
4636
|
} else {
|
|
4133
4637
|
const errorText = await uploadResponse.text();
|
|
@@ -4653,11 +5157,12 @@ async function handleFrameCaptureComplete(sessionData) {
|
|
|
4653
5157
|
frameCounter.delete(sessionData.sessionId); // Clean up frame counter
|
|
4654
5158
|
screenInteractions = []; // Clear interactions
|
|
4655
5159
|
|
|
4656
|
-
// Update storage
|
|
4657
|
-
chrome.storage.local.set({
|
|
4658
|
-
recordingActive: false,
|
|
5160
|
+
// Update storage - clear isVideoModeActive for consistency
|
|
5161
|
+
chrome.storage.local.set({
|
|
5162
|
+
recordingActive: false,
|
|
4659
5163
|
recordingStartTime: null,
|
|
4660
|
-
recordingTabId: null
|
|
5164
|
+
recordingTabId: null,
|
|
5165
|
+
isVideoModeActive: false
|
|
4661
5166
|
});
|
|
4662
5167
|
|
|
4663
5168
|
// Update badge
|