@dynamicu/chromedebug-mcp 2.7.2 → 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 +547 -56
- 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/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 +157 -39
- package/src/http-server.js +55 -11
- package/src/validation/schemas.js +11 -1
|
@@ -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) {
|
|
@@ -1389,6 +1441,15 @@ chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
|
|
|
1389
1441
|
return; // Don't also inject screen recording logs
|
|
1390
1442
|
}
|
|
1391
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
|
+
|
|
1392
1453
|
// Check for SCREEN recording
|
|
1393
1454
|
if (sessionManager) {
|
|
1394
1455
|
isCurrentlyRecordingAsync().then(async (recording) => {
|
|
@@ -1515,6 +1576,40 @@ chrome.tabs.onRemoved.addListener((tabId, removeInfo) => {
|
|
|
1515
1576
|
console.error(`[Workflow] Error updating storage for closed tab ${tabId}:`, err);
|
|
1516
1577
|
});
|
|
1517
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
|
+
}
|
|
1518
1613
|
});
|
|
1519
1614
|
|
|
1520
1615
|
// Session Manager for robust recording state management
|
|
@@ -1533,6 +1628,10 @@ let isCurrentlyRecording = false; // DEPRECATED: Use isCurrentlyRecordingAsync()
|
|
|
1533
1628
|
let currentRecordingSessionId = null; // DEPRECATED: Use getCurrentSessionIdAsync() instead
|
|
1534
1629
|
let frameCounter = new Map(); // DEPRECATED: Session manager handles frame counting
|
|
1535
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
|
+
|
|
1536
1635
|
// =============================================================================
|
|
1537
1636
|
// INACTIVITY AUTO-STOP - Stop recording if user is inactive
|
|
1538
1637
|
// =============================================================================
|
|
@@ -1798,6 +1897,12 @@ let workflowScreenshotSettings = new Map(); // Map of tabId to screenshot settin
|
|
|
1798
1897
|
let workflowSessionNames = new Map(); // Map of tabId to session name
|
|
1799
1898
|
let workflowLogBuffers = new Map(); // Map of tabId to WorkflowLogBuffer instance
|
|
1800
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
|
+
|
|
1801
1906
|
//=============================================================================
|
|
1802
1907
|
// COMPREHENSIVE ERROR HANDLING SYSTEM
|
|
1803
1908
|
// Designed collaboratively with Second Opinion Analyst and Testing Expert
|
|
@@ -2402,6 +2507,7 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
|
|
|
2402
2507
|
}
|
|
2403
2508
|
|
|
2404
2509
|
if (request.action === 'startRecording') {
|
|
2510
|
+
console.log('[Background] startRecording received settings:', request.settings);
|
|
2405
2511
|
startRecording(request.tabId, request.settings).then(() => {
|
|
2406
2512
|
sendResponse({ success: true });
|
|
2407
2513
|
}).catch((error) => {
|
|
@@ -2710,7 +2816,52 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
|
|
|
2710
2816
|
});
|
|
2711
2817
|
return true;
|
|
2712
2818
|
}
|
|
2713
|
-
|
|
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
|
+
|
|
2714
2865
|
// Handle messages from offscreen document
|
|
2715
2866
|
if (request.target === 'background') {
|
|
2716
2867
|
if (request.type === 'frame-batch-ready') {
|
|
@@ -2719,10 +2870,14 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
|
|
|
2719
2870
|
handleFrameCaptureComplete(request.data);
|
|
2720
2871
|
} else if (request.type === 'show-frame-flash') {
|
|
2721
2872
|
// Relay flash message to content script
|
|
2873
|
+
// console.log('[Background] Relaying frame-flash to tab:', request.tabId);
|
|
2722
2874
|
chrome.tabs.sendMessage(request.tabId, {
|
|
2723
2875
|
action: 'showFrameFlash'
|
|
2724
|
-
}).
|
|
2876
|
+
}).then(() => {
|
|
2877
|
+
// console.log('[Background] Frame flash message sent successfully to tab:', request.tabId);
|
|
2878
|
+
}).catch((error) => {
|
|
2725
2879
|
// Content script might not be available, that's ok
|
|
2880
|
+
// console.log('[Background] Frame flash failed for tab:', request.tabId, error?.message);
|
|
2726
2881
|
});
|
|
2727
2882
|
}
|
|
2728
2883
|
}
|
|
@@ -2823,7 +2978,23 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
|
|
|
2823
2978
|
}
|
|
2824
2979
|
}
|
|
2825
2980
|
}
|
|
2826
|
-
|
|
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
|
+
|
|
2827
2998
|
// Handle getCookies for restore points
|
|
2828
2999
|
if (request.action === 'getCookies' && sender.tab) {
|
|
2829
3000
|
chrome.cookies.getAll({ url: request.url }, (cookies) => {
|
|
@@ -2933,10 +3104,10 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
|
|
|
2933
3104
|
// console.log('[SCREENSHOT-DEBUG] background.js - Sending response with success: true');
|
|
2934
3105
|
sendResponse({ success: true, screenshotData });
|
|
2935
3106
|
}).catch((error) => {
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
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
|
+
}
|
|
2940
3111
|
sendResponse({ success: false, error: error.message });
|
|
2941
3112
|
});
|
|
2942
3113
|
return true;
|
|
@@ -2970,7 +3141,7 @@ async function takeStandaloneSnapshot(tabId, note = '') {
|
|
|
2970
3141
|
const sessionId = `snapshot_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
2971
3142
|
|
|
2972
3143
|
// Capture screenshot
|
|
2973
|
-
const screenshotData = await
|
|
3144
|
+
const screenshotData = await throttledCaptureVisibleTab(null, {
|
|
2974
3145
|
format: 'jpeg',
|
|
2975
3146
|
quality: 30 // Default quality for snapshots
|
|
2976
3147
|
});
|
|
@@ -3083,7 +3254,7 @@ async function captureTabScreenshot(tabId, settings) {
|
|
|
3083
3254
|
// console.log('[SCREENSHOT-DEBUG] captureTabScreenshot - captureOptions:', JSON.stringify(captureOptions));
|
|
3084
3255
|
// console.log('[SCREENSHOT-DEBUG] captureTabScreenshot - Calling chrome.tabs.captureVisibleTab...');
|
|
3085
3256
|
|
|
3086
|
-
const dataUrl = await
|
|
3257
|
+
const dataUrl = await throttledCaptureVisibleTab(windowId, captureOptions);
|
|
3087
3258
|
|
|
3088
3259
|
// console.log('[SCREENSHOT-DEBUG] captureTabScreenshot - captureVisibleTab SUCCESS');
|
|
3089
3260
|
// console.log('[SCREENSHOT-DEBUG] captureTabScreenshot - dataUrl length:', dataUrl?.length);
|
|
@@ -3135,10 +3306,12 @@ async function captureTabScreenshot(tabId, settings) {
|
|
|
3135
3306
|
// console.log('[SCREENSHOT-DEBUG] captureTabScreenshot - Final dataUrl length:', dataUrl?.length);
|
|
3136
3307
|
return dataUrl;
|
|
3137
3308
|
} catch (error) {
|
|
3138
|
-
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
|
|
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
|
+
}
|
|
3142
3315
|
throw error;
|
|
3143
3316
|
}
|
|
3144
3317
|
}
|
|
@@ -3265,6 +3438,13 @@ async function startRecording(tabId, settings = {}) {
|
|
|
3265
3438
|
currentOwnerId = sessionResult.ownerId;
|
|
3266
3439
|
|
|
3267
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
|
+
});
|
|
3268
3448
|
const scheduledStartTime = Date.now() + 3000; // Schedule start 3 seconds in future for countdown synchronization
|
|
3269
3449
|
|
|
3270
3450
|
// STEP 4: Initialize browser-only recording if in browser-only mode
|
|
@@ -3275,7 +3455,8 @@ async function startRecording(tabId, settings = {}) {
|
|
|
3275
3455
|
tabId: tabId,
|
|
3276
3456
|
url: tab.url,
|
|
3277
3457
|
title: tab.title || settings.sessionName || 'Browser Recording',
|
|
3278
|
-
startTime: scheduledStartTime
|
|
3458
|
+
startTime: scheduledStartTime,
|
|
3459
|
+
isVideoMode: settings.videoMode || false
|
|
3279
3460
|
});
|
|
3280
3461
|
}
|
|
3281
3462
|
|
|
@@ -3291,12 +3472,15 @@ async function startRecording(tabId, settings = {}) {
|
|
|
3291
3472
|
logStreamer.setSessionId(sessionId);
|
|
3292
3473
|
}
|
|
3293
3474
|
|
|
3294
|
-
// 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
|
|
3295
3478
|
chrome.storage.local.set({
|
|
3296
3479
|
recordingActive: true,
|
|
3297
3480
|
recordingStartTime: scheduledStartTime,
|
|
3298
3481
|
recordingTabId: tabId,
|
|
3299
|
-
recordingSessionId: sessionId
|
|
3482
|
+
recordingSessionId: sessionId,
|
|
3483
|
+
isVideoModeActive: settings.videoMode || false
|
|
3300
3484
|
});
|
|
3301
3485
|
|
|
3302
3486
|
// Update badge
|
|
@@ -3329,10 +3513,12 @@ async function startRecording(tabId, settings = {}) {
|
|
|
3329
3513
|
}
|
|
3330
3514
|
|
|
3331
3515
|
// Notify content script that recording has started with scheduled time
|
|
3516
|
+
console.log('[Background] Sending recordingStarted to content with videoMode:', settings.videoMode);
|
|
3332
3517
|
chrome.tabs.sendMessage(tabId, {
|
|
3333
3518
|
action: 'recordingStarted',
|
|
3334
3519
|
scheduledStartTime: scheduledStartTime,
|
|
3335
|
-
sessionId: sessionId
|
|
3520
|
+
sessionId: sessionId,
|
|
3521
|
+
videoMode: settings.videoMode || false // Pass video mode for hidden indicator
|
|
3336
3522
|
}).catch(async (error) => {
|
|
3337
3523
|
// Content script might not be injected, try to inject it
|
|
3338
3524
|
// console.log('Could not notify content script, attempting to inject:', error);
|
|
@@ -3358,7 +3544,8 @@ async function startRecording(tabId, settings = {}) {
|
|
|
3358
3544
|
chrome.tabs.sendMessage(tabId, {
|
|
3359
3545
|
action: 'recordingStarted',
|
|
3360
3546
|
scheduledStartTime: scheduledStartTime,
|
|
3361
|
-
sessionId: sessionId
|
|
3547
|
+
sessionId: sessionId,
|
|
3548
|
+
videoMode: settings.videoMode || false
|
|
3362
3549
|
}).catch(() => {
|
|
3363
3550
|
console.error('Failed to show recording indicator even after injection');
|
|
3364
3551
|
});
|
|
@@ -3374,12 +3561,22 @@ async function startRecording(tabId, settings = {}) {
|
|
|
3374
3561
|
await browserRecordingManager.startRecording(sessionId, {
|
|
3375
3562
|
tabId,
|
|
3376
3563
|
url: tab.url,
|
|
3377
|
-
title: tab.title
|
|
3564
|
+
title: tab.title,
|
|
3565
|
+
isVideoMode: settings.videoMode || false
|
|
3378
3566
|
});
|
|
3379
3567
|
// console.log('[Recording] Browser recording initialized for session:', sessionId);
|
|
3380
3568
|
}
|
|
3381
3569
|
|
|
3382
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);
|
|
3383
3580
|
await chrome.runtime.sendMessage({
|
|
3384
3581
|
type: 'start-frame-capture',
|
|
3385
3582
|
target: 'offscreen',
|
|
@@ -3389,11 +3586,7 @@ async function startRecording(tabId, settings = {}) {
|
|
|
3389
3586
|
ownerId: currentOwnerId,
|
|
3390
3587
|
scheduledStartTime: scheduledStartTime,
|
|
3391
3588
|
mode: serverMode, // Pass mode to offscreen document
|
|
3392
|
-
settings:
|
|
3393
|
-
frameRate: settings.frameRate || 1,
|
|
3394
|
-
imageQuality: settings.imageQuality || (serverMode === 'browser-only' ? 15 : 30),
|
|
3395
|
-
frameFlash: settings.frameFlash !== false
|
|
3396
|
-
}
|
|
3589
|
+
settings: frameCaptureSettings
|
|
3397
3590
|
});
|
|
3398
3591
|
|
|
3399
3592
|
// console.log('Recording started in', serverMode, 'mode');
|
|
@@ -3528,10 +3721,20 @@ async function stopRecording() {
|
|
|
3528
3721
|
}
|
|
3529
3722
|
}
|
|
3530
3723
|
|
|
3724
|
+
// Note: stoppedSessionId was already captured at the start of stopRecording()
|
|
3725
|
+
// It's used below for deferred cache cleanup
|
|
3726
|
+
|
|
3531
3727
|
// Clear session cache
|
|
3532
3728
|
currentSession = null;
|
|
3533
3729
|
currentOwnerId = null;
|
|
3534
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
|
+
|
|
3535
3738
|
// Update legacy state for backward compatibility (will be removed)
|
|
3536
3739
|
isCurrentlyRecording = false;
|
|
3537
3740
|
recordingTabId = null;
|
|
@@ -3830,7 +4033,8 @@ async function stopWorkflowRecording(tabId) {
|
|
|
3830
4033
|
screenshotSettings: screenshotSettings || {}, // v2.0.8 fix: ensure it's an object, not null
|
|
3831
4034
|
actions: workflow,
|
|
3832
4035
|
logs: includeLogs ? workflow.flatMap(action => action.logs || []) : [],
|
|
3833
|
-
functionTraces: functionTraces // Include function execution traces
|
|
4036
|
+
functionTraces: functionTraces, // Include function execution traces
|
|
4037
|
+
recordingMode: 'workflow' // Explicitly mark as workflow recording for tab filtering
|
|
3834
4038
|
};
|
|
3835
4039
|
|
|
3836
4040
|
// console.log('[SCREENSHOT-DEBUG] background.stopWorkflowRecording - HTTP POST payload being prepared');
|
|
@@ -3934,6 +4138,278 @@ async function stopCapturingWorkflowLogs(tabId) {
|
|
|
3934
4138
|
return await self.ConsoleInterceptionLibrary.stopConsoleInterception(tabId, WORKFLOW_RECORDING_CONSOLE_CONFIG);
|
|
3935
4139
|
}
|
|
3936
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
|
+
|
|
3937
4413
|
// Delete recording from server
|
|
3938
4414
|
async function deleteRecordingFromServer(recordingId, sendResponse) {
|
|
3939
4415
|
try {
|
|
@@ -4092,9 +4568,13 @@ async function handleFrameBatch(batchData, skipValidation = false) {
|
|
|
4092
4568
|
const ports = await getServerPorts();
|
|
4093
4569
|
let uploadSuccess = false;
|
|
4094
4570
|
let lastError = null;
|
|
4095
|
-
|
|
4096
|
-
// Get session name from current session
|
|
4097
|
-
|
|
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;
|
|
4098
4578
|
|
|
4099
4579
|
for (const port of ports) {
|
|
4100
4580
|
try {
|
|
@@ -4104,6 +4584,8 @@ async function handleFrameBatch(batchData, skipValidation = false) {
|
|
|
4104
4584
|
if (sessionName) {
|
|
4105
4585
|
formData.append('sessionName', sessionName);
|
|
4106
4586
|
}
|
|
4587
|
+
// Always include isVideoMode for proper recording classification
|
|
4588
|
+
formData.append('isVideoMode', isVideoMode.toString());
|
|
4107
4589
|
|
|
4108
4590
|
const uploadResponse = await fetch(`http://localhost:${port}/chromedebug/frame-batch`, {
|
|
4109
4591
|
method: 'POST',
|
|
@@ -4115,33 +4597,41 @@ async function handleFrameBatch(batchData, skipValidation = false) {
|
|
|
4115
4597
|
// console.log(`Frame batch uploaded successfully to port ${port}:`, result);
|
|
4116
4598
|
recordingServerPort = port; // Store the successful port
|
|
4117
4599
|
uploadSuccess = true;
|
|
4118
|
-
|
|
4119
|
-
//
|
|
4120
|
-
|
|
4121
|
-
|
|
4122
|
-
|
|
4123
|
-
|
|
4124
|
-
|
|
4125
|
-
|
|
4126
|
-
|
|
4127
|
-
|
|
4128
|
-
// Append new frames
|
|
4129
|
-
sessionData.frames = sessionData.frames.concat(batchData.frames);
|
|
4130
|
-
await chrome.storage.local.set({ [storageKey]: sessionData });
|
|
4131
|
-
// 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);
|
|
4132
4610
|
|
|
4133
4611
|
// Send frame count update to content script
|
|
4134
4612
|
if (recordingTabId) {
|
|
4135
4613
|
try {
|
|
4136
4614
|
await chrome.tabs.sendMessage(recordingTabId, {
|
|
4137
4615
|
action: 'updateFrameCount',
|
|
4138
|
-
frameCount:
|
|
4616
|
+
frameCount: newCount
|
|
4139
4617
|
});
|
|
4140
4618
|
} catch (e) {
|
|
4141
4619
|
// Tab might be closed, ignore
|
|
4142
4620
|
}
|
|
4143
4621
|
}
|
|
4144
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
|
+
|
|
4145
4635
|
break;
|
|
4146
4636
|
} else {
|
|
4147
4637
|
const errorText = await uploadResponse.text();
|
|
@@ -4667,11 +5157,12 @@ async function handleFrameCaptureComplete(sessionData) {
|
|
|
4667
5157
|
frameCounter.delete(sessionData.sessionId); // Clean up frame counter
|
|
4668
5158
|
screenInteractions = []; // Clear interactions
|
|
4669
5159
|
|
|
4670
|
-
// Update storage
|
|
4671
|
-
chrome.storage.local.set({
|
|
4672
|
-
recordingActive: false,
|
|
5160
|
+
// Update storage - clear isVideoModeActive for consistency
|
|
5161
|
+
chrome.storage.local.set({
|
|
5162
|
+
recordingActive: false,
|
|
4673
5163
|
recordingStartTime: null,
|
|
4674
|
-
recordingTabId: null
|
|
5164
|
+
recordingTabId: null,
|
|
5165
|
+
isVideoModeActive: false
|
|
4675
5166
|
});
|
|
4676
5167
|
|
|
4677
5168
|
// Update badge
|