@dynamicu/chromedebug-mcp 2.6.7 → 2.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/CLAUDE.md +17 -1
  2. package/README.md +1 -1
  3. package/chrome-extension/activation-manager.js +10 -10
  4. package/chrome-extension/background.js +1045 -736
  5. package/chrome-extension/browser-recording-manager.js +1 -1
  6. package/chrome-extension/chrome-debug-logger.js +168 -0
  7. package/chrome-extension/chrome-session-manager.js +5 -5
  8. package/chrome-extension/console-interception-library.js +430 -0
  9. package/chrome-extension/content.css +16 -16
  10. package/chrome-extension/content.js +739 -221
  11. package/chrome-extension/data-buffer.js +5 -5
  12. package/chrome-extension/dom-tracker.js +9 -9
  13. package/chrome-extension/extension-config.js +1 -1
  14. package/chrome-extension/firebase-client.js +13 -13
  15. package/chrome-extension/frame-capture.js +20 -38
  16. package/chrome-extension/license-helper.js +33 -7
  17. package/chrome-extension/manifest.free.json +3 -6
  18. package/chrome-extension/network-tracker.js +9 -9
  19. package/chrome-extension/options.html +10 -0
  20. package/chrome-extension/options.js +21 -8
  21. package/chrome-extension/performance-monitor.js +17 -17
  22. package/chrome-extension/popup.html +230 -193
  23. package/chrome-extension/popup.js +146 -458
  24. package/chrome-extension/pro/enhanced-capture.js +406 -0
  25. package/chrome-extension/pro/frame-editor.html +433 -0
  26. package/chrome-extension/pro/frame-editor.js +1567 -0
  27. package/chrome-extension/pro/function-tracker.js +843 -0
  28. package/chrome-extension/pro/jszip.min.js +13 -0
  29. package/chrome-extension/upload-manager.js +7 -7
  30. package/dist/chromedebug-extension-free.zip +0 -0
  31. package/package.json +3 -1
  32. package/scripts/webpack.config.free.cjs +8 -8
  33. package/scripts/webpack.config.pro.cjs +2 -0
  34. package/src/cli.js +2 -2
  35. package/src/database.js +55 -7
  36. package/src/index.js +9 -6
  37. package/src/mcp/server.js +2 -2
  38. package/src/services/process-manager.js +10 -6
  39. package/src/services/process-tracker.js +10 -5
  40. package/src/services/profile-manager.js +17 -2
  41. package/src/validation/schemas.js +12 -11
  42. package/src/index-direct.js +0 -157
  43. package/src/index-modular.js +0 -219
  44. package/src/index-monolithic-backup.js +0 -2230
  45. package/src/legacy/chrome-controller-old.js +0 -1406
  46. package/src/legacy/index-express.js +0 -625
  47. package/src/legacy/index-old.js +0 -977
  48. package/src/legacy/routes.js +0 -260
  49. package/src/legacy/shared-storage.js +0 -101
@@ -1,6 +1,6 @@
1
1
  // Background script to handle server communication and recording
2
2
  const EXTENSION_VERSION = '2.0.4-BUILD-20250119';
3
- console.log(`[background.js] Loaded version: ${EXTENSION_VERSION}`);
3
+ // console.log(`[background.js] Loaded version: ${EXTENSION_VERSION}`);
4
4
 
5
5
  // Import configuration and libraries
6
6
  importScripts('extension-config.js');
@@ -8,13 +8,129 @@ importScripts('pako.min.js');
8
8
  importScripts('data-buffer.js');
9
9
  importScripts('upload-manager.js');
10
10
  importScripts('chrome-session-manager.js');
11
- importScripts('firebase-config.js');
12
- importScripts('license-helper.js');
11
+ importScripts('console-interception-library.js'); // Shared console interception library
13
12
  importScripts('browser-recording-manager.js');
14
13
 
15
14
  const CONFIG_PORTS = CHROMEDEBUG_CONFIG.ports;
16
15
  const DISCOVERY_TIMEOUT = CHROMEDEBUG_CONFIG.discoveryTimeout;
17
16
 
17
+ // ============================================================
18
+ // FREE TIER USAGE TRACKING (Local Storage - No Firebase)
19
+ // ============================================================
20
+ // Daily limit: 5 recordings per day
21
+ // Resets at midnight local time
22
+ // Tracks both workflow and screen recordings
23
+ // ============================================================
24
+
25
+ const FREE_TIER_DAILY_LIMIT = 5;
26
+ const USAGE_STORAGE_KEY = 'chromedebug_daily_usage';
27
+
28
+ /**
29
+ * Check if this is the PRO version
30
+ */
31
+ function isProVersion() {
32
+ const manifest = chrome.runtime.getManifest();
33
+ return manifest.name.includes('PRO');
34
+ }
35
+
36
+ /**
37
+ * Get today's date string in local timezone (YYYY-MM-DD)
38
+ */
39
+ function getTodayDateString() {
40
+ const now = new Date();
41
+ return `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}`;
42
+ }
43
+
44
+ /**
45
+ * Get current usage for today
46
+ * @returns {Promise<{count: number, date: string, limit: number}>}
47
+ */
48
+ async function getUsageToday() {
49
+ // PRO version: unlimited
50
+ if (isProVersion()) {
51
+ return { count: 0, date: getTodayDateString(), limit: Infinity, isPro: true };
52
+ }
53
+
54
+ const result = await chrome.storage.local.get(USAGE_STORAGE_KEY);
55
+ const usage = result[USAGE_STORAGE_KEY] || { count: 0, date: '' };
56
+ const today = getTodayDateString();
57
+
58
+ // Reset if new day
59
+ if (usage.date !== today) {
60
+ return { count: 0, date: today, limit: FREE_TIER_DAILY_LIMIT };
61
+ }
62
+
63
+ return { count: usage.count, date: today, limit: FREE_TIER_DAILY_LIMIT };
64
+ }
65
+
66
+ /**
67
+ * Check if user can start a new recording
68
+ * @returns {Promise<{allowed: boolean, count: number, limit: number, message?: string}>}
69
+ */
70
+ async function checkUsageBeforeRecording() {
71
+ const usage = await getUsageToday();
72
+
73
+ if (usage.isPro) {
74
+ return { allowed: true, count: 0, limit: Infinity, isPro: true };
75
+ }
76
+
77
+ if (usage.count >= usage.limit) {
78
+ return {
79
+ allowed: false,
80
+ count: usage.count,
81
+ limit: usage.limit,
82
+ message: `Daily limit reached (${usage.count}/${usage.limit}). Upgrade to Pro for unlimited recordings.`
83
+ };
84
+ }
85
+
86
+ return { allowed: true, count: usage.count, limit: usage.limit };
87
+ }
88
+
89
+ /**
90
+ * Increment usage count after successful recording
91
+ * @returns {Promise<{success: boolean, count: number, limit: number}>}
92
+ */
93
+ async function incrementUsageAfterRecording() {
94
+ // PRO version: skip tracking
95
+ if (isProVersion()) {
96
+ // console.log('[Usage] PRO version - skipping usage tracking');
97
+ return { success: true, count: 0, limit: Infinity, isPro: true };
98
+ }
99
+
100
+ const today = getTodayDateString();
101
+ const result = await chrome.storage.local.get(USAGE_STORAGE_KEY);
102
+ const usage = result[USAGE_STORAGE_KEY] || { count: 0, date: '' };
103
+
104
+ // Reset if new day, then increment
105
+ const newCount = (usage.date === today) ? usage.count + 1 : 1;
106
+
107
+ await chrome.storage.local.set({
108
+ [USAGE_STORAGE_KEY]: { count: newCount, date: today }
109
+ });
110
+
111
+ // console.log(`[Usage] Recording tracked: ${newCount}/${FREE_TIER_DAILY_LIMIT} today`);
112
+
113
+ return { success: true, count: newCount, limit: FREE_TIER_DAILY_LIMIT };
114
+ }
115
+
116
+ // Console interception configuration for screen recording
117
+ const SCREEN_RECORDING_CONSOLE_CONFIG = {
118
+ overrideFlagName: '__chromePilotConsoleOverridden',
119
+ originalConsoleName: '__chromePilotOriginalConsole',
120
+ relayFlagName: '__chromePilotConsoleRelay',
121
+ messageType: 'chrome-debug-console-log',
122
+ backgroundAction: 'consoleLog'
123
+ };
124
+
125
+ // Console interception configuration for workflow recording
126
+ const WORKFLOW_RECORDING_CONSOLE_CONFIG = {
127
+ overrideFlagName: '__chromePilotWorkflowConsoleOverridden',
128
+ originalConsoleName: '__chromePilotWorkflowOriginalConsole',
129
+ relayFlagName: '__chromePilotWorkflowConsoleRelay',
130
+ messageType: 'chrome-debug-workflow-console-log',
131
+ backgroundAction: 'workflowConsoleLog'
132
+ };
133
+
18
134
  // Frame queue for handling validation race conditions
19
135
  // v2.1.2: Prevents frame loss when lease renewal hasn't propagated yet
20
136
  const pendingFrameQueue = new Map(); // sessionId -> array of frame batches
@@ -38,7 +154,7 @@ class LogStreamer {
38
154
  async init() {
39
155
  // Skip initialization in browser-only mode
40
156
  if (serverMode === 'browser-only') {
41
- console.log('[LogStreamer] Skipping initialization - browser-only mode');
157
+ // console.log('[LogStreamer] Skipping initialization - browser-only mode');
42
158
  return;
43
159
  }
44
160
 
@@ -48,7 +164,7 @@ class LogStreamer {
48
164
  // Start periodic streaming
49
165
  this.startPeriodicStreaming();
50
166
 
51
- console.log('[LogStreamer] Initialized with server availability:', this.serverAvailable);
167
+ // console.log('[LogStreamer] Initialized with server availability:', this.serverAvailable);
52
168
  }
53
169
 
54
170
  async checkServerAvailability() {
@@ -60,7 +176,7 @@ class LogStreamer {
60
176
  });
61
177
  if (response.ok) {
62
178
  this.serverAvailable = true;
63
- console.log(`[LogStreamer] Server available on port ${port}`);
179
+ // console.log(`[LogStreamer] Server available on port ${port}`);
64
180
  return;
65
181
  }
66
182
  } catch (error) {
@@ -68,7 +184,7 @@ class LogStreamer {
68
184
  }
69
185
  }
70
186
  this.serverAvailable = false;
71
- console.log('[LogStreamer] No server available');
187
+ // console.log('[LogStreamer] No server available');
72
188
  }
73
189
 
74
190
  startPeriodicStreaming() {
@@ -111,7 +227,7 @@ class LogStreamer {
111
227
  // This gives frames time to be processed before logs are associated
112
228
  const timeSinceLastFrame = Date.now() - this.lastFrameCapture;
113
229
  if (this.lastFrameCapture > 0 && timeSinceLastFrame < 100) {
114
- console.log('[LogStreamer] Waiting for frame processing before streaming logs');
230
+ // console.log('[LogStreamer] Waiting for frame processing before streaming logs');
115
231
  return; // Wait for next cycle
116
232
  }
117
233
 
@@ -140,7 +256,7 @@ class LogStreamer {
140
256
  // Method to notify LogStreamer when frame is captured
141
257
  notifyFrameCapture() {
142
258
  this.lastFrameCapture = Date.now();
143
- console.log('[LogStreamer] Frame capture notification received');
259
+ // console.log('[LogStreamer] Frame capture notification received');
144
260
  }
145
261
 
146
262
  async sendToServer(batch) {
@@ -164,7 +280,7 @@ class LogStreamer {
164
280
  const result = await response.json();
165
281
  // Only log if we had to try multiple ports (first port failed)
166
282
  if (port !== this.serverPorts[0]) {
167
- console.log(`[LogStreamer] Connected to server on port ${port}`);
283
+ // console.log(`[LogStreamer] Connected to server on port ${port}`);
168
284
  }
169
285
  return result;
170
286
  }
@@ -215,7 +331,7 @@ class LogStreamer {
215
331
  if (now >= item.nextRetry && item.attempts < 3) {
216
332
  try {
217
333
  await this.sendToServer(item.batch);
218
- console.log(`[LogStreamer] Retry successful for batch with ${item.batch.length} logs`);
334
+ // console.log(`[LogStreamer] Retry successful for batch with ${item.batch.length} logs`);
219
335
  } catch (error) {
220
336
  item.attempts++;
221
337
  item.nextRetry = now + (1000 * Math.pow(2, item.attempts)); // Exponential backoff
@@ -273,7 +389,7 @@ class LogBuffer {
273
389
  // Flush all buffers to Chrome storage
274
390
  async flushAll() {
275
391
  if (this.flushInProgress) {
276
- console.log('[LogBuffer] Flush already in progress, waiting for completion...');
392
+ // console.log('[LogBuffer] Flush already in progress, waiting for completion...');
277
393
  // Wait for current flush to complete instead of skipping
278
394
  while (this.flushInProgress) {
279
395
  await new Promise(resolve => setTimeout(resolve, 10));
@@ -286,7 +402,7 @@ class LogBuffer {
286
402
  const startTime = Date.now();
287
403
 
288
404
  try {
289
- console.log(`[LogBuffer] Starting flushAll for ${this.tabBuffers.size} tabs`);
405
+ // console.log(`[LogBuffer] Starting flushAll for ${this.tabBuffers.size} tabs`);
290
406
 
291
407
  for (const [tabId, tabBuffer] of this.tabBuffers) {
292
408
  flushPromises.push(tabBuffer.flush());
@@ -294,7 +410,7 @@ class LogBuffer {
294
410
 
295
411
  await Promise.all(flushPromises);
296
412
  const duration = Date.now() - startTime;
297
- console.log(`[LogBuffer] Successfully flushed all buffers for ${this.tabBuffers.size} tabs in ${duration}ms`);
413
+ // console.log(`[LogBuffer] Successfully flushed all buffers for ${this.tabBuffers.size} tabs in ${duration}ms`);
298
414
  } catch (error) {
299
415
  console.error('[LogBuffer] Error during flush operation:', error);
300
416
  throw error; // Re-throw to ensure proper error handling upstream
@@ -315,7 +431,7 @@ class LogBuffer {
315
431
  clearTab(tabId) {
316
432
  if (this.tabBuffers.has(tabId)) {
317
433
  this.tabBuffers.delete(tabId);
318
- console.log(`[LogBuffer] Cleared buffer for tab ${tabId}`);
434
+ // console.log(`[LogBuffer] Cleared buffer for tab ${tabId}`);
319
435
  }
320
436
  }
321
437
 
@@ -341,7 +457,7 @@ class LogBuffer {
341
457
  // Debug method to log current buffer state
342
458
  logStats() {
343
459
  const stats = this.getStats();
344
- console.log('[LogBuffer] Current statistics:', stats);
460
+ // console.log('[LogBuffer] Current statistics:', stats);
345
461
  return stats;
346
462
  }
347
463
  }
@@ -456,7 +572,7 @@ class LogTabBuffer {
456
572
  }
457
573
 
458
574
  this.totalLogsFlushed += this.buffer.length;
459
- console.log(`[LogBuffer] Flushed ${this.buffer.length} logs for tab ${this.tabId} (total: ${this.totalLogsFlushed})`);
575
+ // console.log(`[LogBuffer] Flushed ${this.buffer.length} logs for tab ${this.tabId} (total: ${this.totalLogsFlushed})`);
460
576
 
461
577
  // Clear the buffer
462
578
  this.buffer = [];
@@ -481,6 +597,162 @@ class LogTabBuffer {
481
597
  }
482
598
  }
483
599
 
600
+ /**
601
+ * WorkflowLogBuffer - Race-safe log buffer for workflow recording
602
+ *
603
+ * Mirrors LogTabBuffer but uses workflow-specific storage keys (`workflow_${tabId}`).
604
+ * This prevents race conditions when multiple logs arrive rapidly during workflow recording.
605
+ *
606
+ * Technical Debt Note: This class duplicates LogTabBuffer logic. Future refactoring
607
+ * could make LogTabBuffer accept a configurable storage key prefix to eliminate duplication.
608
+ */
609
+ class WorkflowLogBuffer {
610
+ constructor(tabId) {
611
+ this.tabId = tabId;
612
+ this.buffer = [];
613
+ this.processing = false;
614
+ this.processingQueue = [];
615
+ this.totalLogsAdded = 0;
616
+ this.totalLogsFlushed = 0;
617
+ this.lastFlushTime = 0;
618
+ this.storageKey = `workflow_${tabId}`;
619
+ }
620
+
621
+ // Add log to buffer with queuing to prevent race conditions
622
+ async addLog(logEntry) {
623
+ return new Promise((resolve, reject) => {
624
+ this.processingQueue.push({
625
+ type: 'add',
626
+ logEntry,
627
+ resolve,
628
+ reject
629
+ });
630
+
631
+ // Process queue if not already processing
632
+ if (!this.processing) {
633
+ this.processQueue();
634
+ }
635
+ });
636
+ }
637
+
638
+ // Flush buffer to Chrome storage
639
+ async flush() {
640
+ return new Promise((resolve, reject) => {
641
+ this.processingQueue.push({
642
+ type: 'flush',
643
+ resolve,
644
+ reject
645
+ });
646
+
647
+ // Process queue if not already processing
648
+ if (!this.processing) {
649
+ this.processQueue();
650
+ }
651
+ });
652
+ }
653
+
654
+ // Sequential processing of queue to prevent race conditions
655
+ async processQueue() {
656
+ if (this.processing) {
657
+ return; // Already processing
658
+ }
659
+
660
+ this.processing = true;
661
+
662
+ try {
663
+ while (this.processingQueue.length > 0) {
664
+ const operation = this.processingQueue.shift();
665
+
666
+ try {
667
+ if (operation.type === 'add') {
668
+ this.buffer.push(operation.logEntry);
669
+ this.totalLogsAdded++;
670
+
671
+ // Auto-flush if buffer gets too large
672
+ if (this.buffer.length >= 100) {
673
+ await this.performFlush();
674
+ }
675
+
676
+ operation.resolve();
677
+ } else if (operation.type === 'flush') {
678
+ await this.performFlush();
679
+ operation.resolve();
680
+ }
681
+ } catch (error) {
682
+ operation.reject(error);
683
+ }
684
+ }
685
+ } finally {
686
+ this.processing = false;
687
+ }
688
+ }
689
+
690
+ // Actual flush operation to Chrome storage
691
+ async performFlush() {
692
+ if (this.buffer.length === 0) {
693
+ return; // Nothing to flush
694
+ }
695
+
696
+ try {
697
+ // Read current logs from storage
698
+ const result = await chrome.storage.session.get(this.storageKey);
699
+ const existingLogs = result[this.storageKey] || [];
700
+
701
+ // Append new logs
702
+ const updatedLogs = [...existingLogs, ...this.buffer];
703
+
704
+ // Check storage size limit
705
+ const estimatedSize = JSON.stringify(updatedLogs).length;
706
+ if (estimatedSize > 500000) { // ~500KB limit
707
+ const beforeCount = updatedLogs.length;
708
+ const retentionPercentage = 0.95;
709
+ const afterCount = Math.floor(updatedLogs.length * retentionPercentage);
710
+ console.warn(`[WorkflowLogBuffer] Storage size limit approached for tab ${this.tabId}, truncating from ${beforeCount} to ${afterCount} logs`);
711
+ const recentLogs = updatedLogs.slice(-afterCount);
712
+ await chrome.storage.session.set({ [this.storageKey]: recentLogs });
713
+ } else {
714
+ await chrome.storage.session.set({ [this.storageKey]: updatedLogs });
715
+ }
716
+
717
+ this.totalLogsFlushed += this.buffer.length;
718
+ // console.log(`[WorkflowLogBuffer] Flushed ${this.buffer.length} logs for tab ${this.tabId} (total: ${this.totalLogsFlushed})`);
719
+
720
+ // Clear the buffer
721
+ this.buffer = [];
722
+ this.lastFlushTime = Date.now();
723
+
724
+ } catch (error) {
725
+ console.error(`[WorkflowLogBuffer] Failed to flush logs for tab ${this.tabId}:`, error);
726
+ throw error;
727
+ }
728
+ }
729
+
730
+ // Get buffer statistics
731
+ getStats() {
732
+ return {
733
+ bufferedLogs: this.buffer.length,
734
+ totalAdded: this.totalLogsAdded,
735
+ totalFlushed: this.totalLogsFlushed,
736
+ processing: this.processing,
737
+ queueLength: this.processingQueue.length,
738
+ lastFlushTime: this.lastFlushTime
739
+ };
740
+ }
741
+
742
+ // Clean up buffer when recording stops
743
+ async cleanup() {
744
+ try {
745
+ // Flush any remaining logs
746
+ if (this.buffer.length > 0) {
747
+ await this.performFlush();
748
+ }
749
+ // console.log(`[WorkflowLogBuffer] Cleaned up buffer for tab ${this.tabId}`);
750
+ } catch (error) {
751
+ console.error(`[WorkflowLogBuffer] Error during cleanup for tab ${this.tabId}:`, error);
752
+ }
753
+ }
754
+ }
755
+
484
756
  // Initialize data buffer and upload manager
485
757
  let dataBuffer = null;
486
758
  let uploadManager = null;
@@ -537,10 +809,10 @@ const DATA_PROTECTION_TIERS = {
537
809
  async function initializeServices() {
538
810
  try {
539
811
  // FIRST: Initialize comprehensive error handling system
540
- console.log('[Background] Initializing comprehensive error handling...');
812
+ // console.log('[Background] Initializing comprehensive error handling...');
541
813
  const errorHandlingSuccess = await initializeErrorHandling();
542
814
  if (errorHandlingSuccess) {
543
- console.log('[Background] Comprehensive error handling initialized successfully');
815
+ // console.log('[Background] Comprehensive error handling initialized successfully');
544
816
  } else {
545
817
  console.warn('[Background] Error handling initialization failed - continuing with basic functionality');
546
818
  }
@@ -548,16 +820,16 @@ async function initializeServices() {
548
820
  // Initialize the data buffer
549
821
  dataBuffer = new DataBuffer();
550
822
  await dataBuffer.init();
551
- console.log('[Background] Data buffer initialized');
823
+ // console.log('[Background] Data buffer initialized');
552
824
 
553
825
  // Only create upload manager if data buffer initialized successfully
554
826
  try {
555
827
  uploadManager = new UploadManager(dataBuffer);
556
828
  const serverAvailable = await uploadManager.init();
557
829
  if (serverAvailable) {
558
- console.log('[Background] Upload manager initialized with server');
830
+ // console.log('[Background] Upload manager initialized with server');
559
831
  } else {
560
- console.log('[Background] Upload manager initialized but no server available yet');
832
+ // console.log('[Background] Upload manager initialized but no server available yet');
561
833
  }
562
834
  } catch (uploadErr) {
563
835
  console.error('[Background] Failed to initialize upload manager:', uploadErr);
@@ -569,7 +841,7 @@ async function initializeServices() {
569
841
  try {
570
842
  logStreamer = new LogStreamer();
571
843
  await logStreamer.init();
572
- console.log('[Background] Log streamer initialized');
844
+ // console.log('[Background] Log streamer initialized');
573
845
  } catch (streamErr) {
574
846
  console.error('[Background] Failed to initialize log streamer:', streamErr);
575
847
  logStreamer = null;
@@ -578,7 +850,7 @@ async function initializeServices() {
578
850
  // Initialize log buffer for race-condition-free storage
579
851
  try {
580
852
  logBuffer = new LogBuffer();
581
- console.log('[Background] Log buffer initialized');
853
+ // console.log('[Background] Log buffer initialized');
582
854
  } catch (bufferErr) {
583
855
  console.error('[Background] Failed to initialize log buffer:', bufferErr);
584
856
  logBuffer = null;
@@ -587,7 +859,7 @@ async function initializeServices() {
587
859
  // Initialize browser recording manager for browser-only mode
588
860
  try {
589
861
  browserRecordingManager = new BrowserRecordingManager(dataBuffer);
590
- console.log('[Background] Browser recording manager initialized');
862
+ // console.log('[Background] Browser recording manager initialized');
591
863
  } catch (recordingErr) {
592
864
  console.error('[Background] Failed to initialize browser recording manager:', recordingErr);
593
865
  browserRecordingManager = null;
@@ -597,12 +869,12 @@ async function initializeServices() {
597
869
  try {
598
870
  if (!sessionManager) {
599
871
  sessionManager = new ChromeExtensionSessionManager();
600
- console.log('[Background] Session manager initialized');
872
+ // console.log('[Background] Session manager initialized');
601
873
  }
602
874
 
603
875
  // Recover any stuck sessions on startup
604
876
  await sessionManager.recoverSessions();
605
- console.log('[Background] Session recovery completed');
877
+ // console.log('[Background] Session recovery completed');
606
878
  } catch (sessionErr) {
607
879
  console.error('[Background] Failed to initialize session manager:', sessionErr);
608
880
  // Session manager critical for recording - but don't fail completely
@@ -655,10 +927,14 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
655
927
  'restoreFromPoint',
656
928
  'getWebSocketPort',
657
929
  'startRecording',
930
+ 'stopRecording',
931
+ 'openFrameEditor',
658
932
  'checkConnection',
659
933
  'workflowConsoleLog', // v2.0.8: handle workflow console logs
660
934
  'workflowAction', // v2.0.8: handle workflow actions
661
- 'renewLease' // v2.1.2: session manager lease renewal for frame capture
935
+ 'renewLease', // v2.1.2: session manager lease renewal for frame capture
936
+ 'frameSessionComplete', // Frame recording completion notification
937
+ 'recordingStopped' // Recording stopped notification for popup refresh
662
938
  ];
663
939
 
664
940
  if (handledByOtherListener.includes(messageType)) {
@@ -704,7 +980,27 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
704
980
  sendResponse({ status: 'error', message: error.message });
705
981
  }
706
982
  break;
707
-
983
+
984
+ case 'getUsage':
985
+ // Get current usage count for popup display
986
+ getUsageToday().then(usage => {
987
+ sendResponse({ success: true, ...usage });
988
+ }).catch(error => {
989
+ console.error('[Usage] Failed to get usage:', error);
990
+ sendResponse({ success: false, error: error.message });
991
+ });
992
+ return true; // Async response
993
+
994
+ case 'checkUsageLimit':
995
+ // Check if user can start a new recording
996
+ checkUsageBeforeRecording().then(result => {
997
+ sendResponse(result);
998
+ }).catch(error => {
999
+ console.error('[Usage] Failed to check usage limit:', error);
1000
+ sendResponse({ allowed: true, error: error.message }); // Fail-open
1001
+ });
1002
+ return true; // Async response
1003
+
708
1004
  case 'UPLOAD_BATCH':
709
1005
  // Add events to the background's DataBuffer for upload
710
1006
  if (message.events && message.recordingId) {
@@ -716,12 +1012,12 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
716
1012
  }));
717
1013
 
718
1014
  dataBuffer.addBatch(eventsWithRecordingId).then(() => {
719
- console.log(`[Background] Added ${eventsWithRecordingId.length} events to buffer for recording ${message.recordingId}`);
1015
+ // console.log(`[Background] Added ${eventsWithRecordingId.length} events to buffer for recording ${message.recordingId}`);
720
1016
  // Create a batch if we have enough events
721
1017
  return dataBuffer.createBatch(message.recordingId);
722
1018
  }).then(batch => {
723
1019
  if (batch) {
724
- console.log(`[Background] Created batch ${batch.id} with ${batch.events.length} events`);
1020
+ // console.log(`[Background] Created batch ${batch.id} with ${batch.events.length} events`);
725
1021
  // Process upload queue if upload manager is available
726
1022
  if (uploadManager) {
727
1023
  uploadManager.processUploadQueue();
@@ -790,7 +1086,7 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
790
1086
  sessionId: message.sessionId
791
1087
  }).catch(() => {
792
1088
  // Content script might not be available
793
- console.log('[Background] Could not forward start-screen-capture-tracking to content script');
1089
+ // console.log('[Background] Could not forward start-screen-capture-tracking to content script');
794
1090
  });
795
1091
  }
796
1092
  sendResponse({ success: true });
@@ -802,7 +1098,7 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
802
1098
  chrome.tabs.sendMessage(sender.tab.id, {
803
1099
  type: 'stop-screen-capture-tracking'
804
1100
  }).catch(() => {
805
- console.log('[Background] Could not forward stop-screen-capture-tracking to content script');
1101
+ // console.log('[Background] Could not forward stop-screen-capture-tracking to content script');
806
1102
  });
807
1103
  }
808
1104
  sendResponse({ success: true });
@@ -832,7 +1128,7 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
832
1128
  frameIndex: frameIndex
833
1129
  };
834
1130
  screenInteractions.push(interaction);
835
- console.log('[Background] Visual feedback interaction recorded:', interaction.type, 'Total:', screenInteractions.length);
1131
+ // console.log('[Background] Visual feedback interaction recorded:', interaction.type, 'Total:', screenInteractions.length);
836
1132
  }
837
1133
  }).catch(error => {
838
1134
  console.error('Error validating recording state for interaction:', error);
@@ -845,7 +1141,7 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
845
1141
  frameIndex: frameCounter.get(currentRecordingSessionId) || 0
846
1142
  };
847
1143
  screenInteractions.push(interaction);
848
- console.log('[Background] Visual feedback interaction recorded:', interaction.type, 'Total:', screenInteractions.length);
1144
+ // console.log('[Background] Visual feedback interaction recorded:', interaction.type, 'Total:', screenInteractions.length);
849
1145
  }
850
1146
  }
851
1147
  }
@@ -858,7 +1154,7 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
858
1154
  chrome.tabs.sendMessage(sender.tab.id, {
859
1155
  type: 'screen-capture-frame-captured'
860
1156
  }).catch(() => {
861
- console.log('[Background] Could not forward frame flash to content script');
1157
+ // console.log('[Background] Could not forward frame flash to content script');
862
1158
  });
863
1159
  }
864
1160
  // Don't send response to avoid warnings
@@ -869,7 +1165,7 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
869
1165
  if (logBuffer && sender.tab) {
870
1166
  const tabId = sender.tab.id;
871
1167
  const testLogsCount = message.count || 1000;
872
- console.log(`[Background] Starting LogBuffer race condition test with ${testLogsCount} logs for tab ${tabId}`);
1168
+ // console.log(`[Background] Starting LogBuffer race condition test with ${testLogsCount} logs for tab ${tabId}`);
873
1169
 
874
1170
  // Simulate rapid log generation like a real high-volume scenario
875
1171
  const startTime = Date.now();
@@ -892,8 +1188,8 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
892
1188
  Promise.all(logPromises).then(() => {
893
1189
  const endTime = Date.now();
894
1190
  const stats = logBuffer.getStats();
895
- console.log(`[Background] LogBuffer test completed in ${endTime - startTime}ms`);
896
- console.log(`[Background] Test results:`, stats);
1191
+ // console.log(`[Background] LogBuffer test completed in ${endTime - startTime}ms`);
1192
+ // console.log(`[Background] Test results:`, stats);
897
1193
 
898
1194
  sendResponse({
899
1195
  success: true,
@@ -923,7 +1219,7 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
923
1219
  // DEBUG: Get current LogBuffer statistics
924
1220
  if (logBuffer) {
925
1221
  const stats = logBuffer.getStats();
926
- console.log('[Background] LogBuffer stats requested:', stats);
1222
+ // console.log('[Background] LogBuffer stats requested:', stats);
927
1223
  sendResponse({
928
1224
  success: true,
929
1225
  stats: stats
@@ -938,10 +1234,10 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
938
1234
 
939
1235
  case 'getBrowserRecordings':
940
1236
  // Get all browser-only recordings from IndexedDB
941
- console.log('[Background] getBrowserRecordings - Manager exists:', !!browserRecordingManager);
1237
+ // console.log('[Background] getBrowserRecordings - Manager exists:', !!browserRecordingManager);
942
1238
  if (browserRecordingManager) {
943
1239
  browserRecordingManager.listRecordings().then(recordings => {
944
- console.log('[Background] Recordings retrieved from manager:', recordings);
1240
+ // console.log('[Background] Recordings retrieved from manager:', recordings);
945
1241
  sendResponse(recordings);
946
1242
  }).catch(error => {
947
1243
  console.error('[Background] Failed to get browser recordings:', error);
@@ -988,7 +1284,7 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
988
1284
  // Get frames for a browser-only recording
989
1285
  if (dataBuffer && message.sessionId) {
990
1286
  dataBuffer.getBrowserFrames(message.sessionId).then(frames => {
991
- console.log(`[DEBUG] getBrowserRecordingFrames: Retrieved ${frames.length} frames from IndexedDB`);
1287
+ // console.log(`[DEBUG] getBrowserRecordingFrames: Retrieved ${frames.length} frames from IndexedDB`);
992
1288
 
993
1289
  // Debug first frame to see structure
994
1290
  if (frames.length > 0) {
@@ -1002,7 +1298,7 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
1002
1298
  frameIndex: firstFrame.frameIndex,
1003
1299
  allKeys: Object.keys(firstFrame)
1004
1300
  };
1005
- console.log('[DEBUG] First frame structure:', JSON.stringify(debugInfo, null, 2));
1301
+ // console.log('[DEBUG] First frame structure:', JSON.stringify(debugInfo, null, 2));
1006
1302
  }
1007
1303
 
1008
1304
  // Transform frames to match frame editor's expected structure
@@ -1019,7 +1315,7 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
1019
1315
  logsCount: transformedFrames[0].logs ? transformedFrames[0].logs.length : 0,
1020
1316
  allKeys: Object.keys(transformedFrames[0])
1021
1317
  } : 'No frames';
1022
- console.log('[DEBUG] Transformed first frame:', JSON.stringify(transformDebug, null, 2));
1318
+ // console.log('[DEBUG] Transformed first frame:', JSON.stringify(transformDebug, null, 2));
1023
1319
 
1024
1320
  sendResponse({ success: true, frames: transformedFrames });
1025
1321
  }).catch(error => {
@@ -1045,107 +1341,23 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
1045
1341
  default:
1046
1342
  // Don't warn or respond for messages that should be handled by other listener (v2.0.6)
1047
1343
  // This prevents intercepting workflow recording messages
1048
- console.log('[Background v2.0.6] Message not handled by this listener:', messageType);
1344
+ // console.log('[Background v2.0.6] Message not handled by this listener:', messageType);
1049
1345
  // Don't send response - let other listener handle it
1050
1346
  return;
1051
1347
  }
1052
1348
  });
1053
1349
 
1054
- // Auto-activate PRO license on startup if activation file exists
1055
- async function checkAndActivateLicense() {
1056
- try {
1057
- console.log('[License Auto-Activation] Checking for activation file...');
1058
-
1059
- // Try to fetch the activation file from the extension directory
1060
- const activationUrl = chrome.runtime.getURL('license-activation.json');
1061
- const response = await fetch(activationUrl);
1062
-
1063
- if (!response.ok) {
1064
- console.log('[License Auto-Activation] No activation file found - using normal license flow');
1065
- return;
1066
- }
1067
-
1068
- const activationData = await response.json();
1069
- console.log('[License Auto-Activation] Found activation file:', {
1070
- tier: activationData.tier,
1071
- activated_at: activationData.activated_at
1072
- });
1073
-
1074
- // Check if license is already cached and valid
1075
- const cached = await chrome.storage.local.get('chromedebug_license_cache');
1076
- if (cached.chromedebug_license_cache?.valid && cached.chromedebug_license_cache?.tier === 'pro') {
1077
- console.log('[License Auto-Activation] License already activated and cached');
1078
- return;
1079
- }
1080
-
1081
- // Activate the license
1082
- const licenseKey = activationData.license_key;
1083
- console.log('[License Auto-Activation] Activating license...');
1084
-
1085
- // Call LemonSqueezy activation via Firebase Cloud Function
1086
- const instanceId = crypto.randomUUID();
1087
- await chrome.storage.local.set({
1088
- 'chromedebug_instance_id': instanceId,
1089
- 'ls_instance_id': instanceId,
1090
- 'ls_license_key': licenseKey
1091
- });
1092
-
1093
- const activationResponse = await fetch(`${FUNCTIONS_URL}/activateLicense`, {
1094
- method: 'POST',
1095
- headers: {'Content-Type': 'application/json'},
1096
- body: JSON.stringify({
1097
- licenseKey,
1098
- instanceId,
1099
- instanceName: `ChromeDebug-${navigator.userAgent.match(/Chrome\/(\d+)/)?.[1] || 'Unknown'}`
1100
- })
1101
- });
1102
-
1103
- if (!activationResponse.ok) {
1104
- throw new Error(`Activation failed: HTTP ${activationResponse.status}`);
1105
- }
1106
-
1107
- const activationResult = await activationResponse.json();
1108
- console.log('[License Auto-Activation] Activation result:', activationResult);
1109
-
1110
- if (activationResult.activated || activationResult.valid) {
1111
- // Cache the license status
1112
- const licenseCache = {
1113
- valid: true,
1114
- tier: 'pro',
1115
- licenseKey: licenseKey.substring(0, 8) + '...',
1116
- cachedAt: Date.now(),
1117
- graceUntil: Date.now() + (30 * 24 * 60 * 60 * 1000) // 30 days
1118
- };
1119
-
1120
- await chrome.storage.local.set({
1121
- 'chromedebug_license_cache': licenseCache
1122
- });
1123
-
1124
- console.log('[License Auto-Activation] ✓ PRO license activated successfully!');
1125
- console.log('[License Auto-Activation] Cached license status:', licenseCache);
1126
- } else {
1127
- console.error('[License Auto-Activation] Activation returned invalid status:', activationResult);
1128
- }
1129
-
1130
- } catch (error) {
1131
- // Don't block extension startup on activation errors
1132
- console.error('[License Auto-Activation] Failed to auto-activate license:', error);
1133
- console.log('[License Auto-Activation] Extension will continue with normal license flow');
1134
- }
1135
- }
1350
+ // License checking removed - using separate Chrome Web Store listings for FREE and PRO
1136
1351
 
1137
1352
  // Session recovery on startup - handle any stuck recordings from previous session
1138
1353
  chrome.runtime.onStartup.addListener(async () => {
1139
- console.log('[Background] Extension startup detected - checking for stuck sessions');
1140
-
1141
- // Check for PRO license auto-activation first
1142
- await checkAndActivateLicense();
1354
+ // console.log('[Background] Extension startup detected - checking for stuck sessions');
1143
1355
 
1144
1356
  if (sessionManager) {
1145
1357
  try {
1146
1358
  // Use existing recovery method from session manager
1147
1359
  await sessionManager.recoverSessions();
1148
- console.log('[Background] Startup session recovery completed');
1360
+ // console.log('[Background] Startup session recovery completed');
1149
1361
  } catch (error) {
1150
1362
  console.error('[Background] Failed to recover sessions on startup:', error);
1151
1363
  }
@@ -1154,12 +1366,9 @@ chrome.runtime.onStartup.addListener(async () => {
1154
1366
  }
1155
1367
  });
1156
1368
 
1157
- // Extension install/update - also check for license activation
1369
+ // Extension install/update
1158
1370
  chrome.runtime.onInstalled.addListener(async (details) => {
1159
- console.log('[Background] Extension installed/updated:', details.reason);
1160
-
1161
- // Check for PRO license auto-activation on install/update
1162
- await checkAndActivateLicense();
1371
+ // console.log('[Background] Extension installed/updated:', details.reason);
1163
1372
  });
1164
1373
 
1165
1374
  // Listen for tab updates to handle restore points
@@ -1171,7 +1380,7 @@ chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
1171
1380
  isCurrentlyRecordingAsync().then(async (recording) => {
1172
1381
  const currentTabId = await getCurrentTabIdAsync();
1173
1382
  if (recording && tabId === currentTabId && tab.url && !tab.url.startsWith('chrome://') && !tab.url.startsWith('chrome-extension://')) {
1174
- console.log('Re-injecting console logging for navigation during recording');
1383
+ // console.log('Re-injecting console logging for navigation during recording');
1175
1384
  startCapturingLogs(tabId).catch(err => {
1176
1385
  console.error('Failed to re-inject console logging:', err);
1177
1386
  });
@@ -1182,7 +1391,7 @@ chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
1182
1391
  } else {
1183
1392
  // Legacy fallback
1184
1393
  if (tabId === recordingTabId && isCurrentlyRecording && tab.url && !tab.url.startsWith('chrome://') && !tab.url.startsWith('chrome-extension://')) {
1185
- console.log('Re-injecting console logging for navigation during recording');
1394
+ // console.log('Re-injecting console logging for navigation during recording');
1186
1395
  startCapturingLogs(tabId).catch(err => {
1187
1396
  console.error('Failed to re-inject console logging:', err);
1188
1397
  });
@@ -1257,6 +1466,43 @@ chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
1257
1466
  }
1258
1467
  });
1259
1468
 
1469
+ // Defensive cleanup when tabs are closed
1470
+ chrome.tabs.onRemoved.addListener((tabId, removeInfo) => {
1471
+ // Clean up any workflow recording state for closed tabs
1472
+ if (workflowRecordingTabs.has(tabId)) {
1473
+ // console.log(`[Workflow] Tab ${tabId} closed during recording, cleaning up`);
1474
+ workflowRecordingTabs.delete(tabId);
1475
+ workflowIncludeLogs.delete(tabId);
1476
+ workflowScreenshotSettings.delete(tabId);
1477
+ workflowSessionNames.delete(tabId);
1478
+
1479
+ // Clean up WorkflowLogBuffer
1480
+ const buffer = workflowLogBuffers.get(tabId);
1481
+ if (buffer) {
1482
+ buffer.cleanup().then(() => {
1483
+ workflowLogBuffers.delete(tabId);
1484
+ // console.log(`[Workflow] Cleaned up WorkflowLogBuffer for closed tab ${tabId}`);
1485
+ }).catch(err => {
1486
+ console.error(`[Workflow] Error cleaning up buffer for closed tab ${tabId}:`, err);
1487
+ workflowLogBuffers.delete(tabId);
1488
+ });
1489
+ }
1490
+
1491
+ // Remove session storage
1492
+ chrome.storage.session.remove(`workflow_${tabId}`).catch(err => {
1493
+ console.error(`[Workflow] Error removing session storage for closed tab ${tabId}:`, err);
1494
+ });
1495
+
1496
+ // Notify popup that recording has stopped (tab was closed)
1497
+ chrome.storage.local.set({
1498
+ workflowRecording: false,
1499
+ workflowStartTime: null
1500
+ }).catch(err => {
1501
+ console.error(`[Workflow] Error updating storage for closed tab ${tabId}:`, err);
1502
+ });
1503
+ }
1504
+ });
1505
+
1260
1506
  // Session Manager for robust recording state management
1261
1507
  let sessionManager = null;
1262
1508
  let currentSession = null; // Cache current session state
@@ -1267,17 +1513,154 @@ let hasOffscreenDocument = false;
1267
1513
  let screenInteractions = [];
1268
1514
  let recordingServerPort = null; // Track which port is being used for current recording
1269
1515
 
1270
- // Legacy compatibility variables (DEPRECATED - remove after migration is complete)
1271
- let recordingTabId = null; // DEPRECATED: Use getCurrentTabIdAsync() instead
1272
- let isCurrentlyRecording = false; // DEPRECATED: Use isCurrentlyRecordingAsync() instead
1273
- let currentRecordingSessionId = null; // DEPRECATED: Use getCurrentSessionIdAsync() instead
1274
- let frameCounter = new Map(); // DEPRECATED: Session manager handles frame counting
1516
+ // Legacy compatibility variables (DEPRECATED - remove after migration is complete)
1517
+ let recordingTabId = null; // DEPRECATED: Use getCurrentTabIdAsync() instead
1518
+ let isCurrentlyRecording = false; // DEPRECATED: Use isCurrentlyRecordingAsync() instead
1519
+ let currentRecordingSessionId = null; // DEPRECATED: Use getCurrentSessionIdAsync() instead
1520
+ let frameCounter = new Map(); // DEPRECATED: Session manager handles frame counting
1521
+
1522
+ // =============================================================================
1523
+ // INACTIVITY AUTO-STOP - Stop recording if user is inactive
1524
+ // =============================================================================
1525
+ const DEFAULT_INACTIVITY_TIMEOUT_MS = 10 * 1000; // 10 seconds default
1526
+ let lastUserActivityTime = Date.now();
1527
+ let inactivityCheckInterval = null;
1528
+ let currentInactivityTimeout = DEFAULT_INACTIVITY_TIMEOUT_MS;
1529
+ let inactivityMonitoringStarted = false; // Track if monitoring has started
1530
+ let inactivityFallbackTimer = null; // Fallback timer if countdownComplete not received
1531
+ let inactivityDisabled = false; // User can disable for current session
1532
+
1533
+ /**
1534
+ * Start inactivity monitoring for a recording
1535
+ * @param {number} tabId - Tab ID to monitor
1536
+ */
1537
+ async function startInactivityMonitoring(tabId) {
1538
+ // Mark as started (for fallback timer check)
1539
+ inactivityMonitoringStarted = true;
1540
+
1541
+ // Clear fallback timer since we're starting properly
1542
+ if (inactivityFallbackTimer) {
1543
+ clearTimeout(inactivityFallbackTimer);
1544
+ inactivityFallbackTimer = null;
1545
+ }
1546
+
1547
+ // Load user's configured timeout from storage
1548
+ const settings = await chrome.storage.sync.get(['inactivityTimeout']);
1549
+ currentInactivityTimeout = (settings.inactivityTimeout || 10) * 1000; // Convert seconds to ms (default 10s)
1550
+
1551
+ lastUserActivityTime = Date.now();
1552
+
1553
+ // Tell content script to start tracking activity AND send config for countdown display
1554
+ try {
1555
+ await chrome.tabs.sendMessage(tabId, {
1556
+ action: 'startActivityTracking',
1557
+ inactivityTimeoutMs: currentInactivityTimeout,
1558
+ disabled: inactivityDisabled
1559
+ });
1560
+ } catch (error) {
1561
+ console.warn('[Inactivity] Could not start activity tracking in content script:', error.message);
1562
+ }
1563
+
1564
+ // Clear any existing interval
1565
+ if (inactivityCheckInterval) {
1566
+ clearInterval(inactivityCheckInterval);
1567
+ }
1568
+
1569
+ // If disabled, don't start the check interval
1570
+ if (inactivityDisabled) {
1571
+ return;
1572
+ }
1573
+
1574
+ // Check for inactivity every 2 seconds
1575
+ inactivityCheckInterval = setInterval(async () => {
1576
+ // Skip if disabled
1577
+ if (inactivityDisabled) return;
1578
+
1579
+ const timeSinceActivity = Date.now() - lastUserActivityTime;
1580
+ const timeRemaining = Math.max(0, currentInactivityTimeout - timeSinceActivity);
1581
+
1582
+ // Send countdown update to content script
1583
+ try {
1584
+ await chrome.tabs.sendMessage(tabId, {
1585
+ action: 'updateInactivityCountdown',
1586
+ timeRemaining: Math.ceil(timeRemaining / 1000),
1587
+ disabled: inactivityDisabled
1588
+ });
1589
+ } catch (e) {
1590
+ // Tab might be closed, ignore
1591
+ }
1592
+
1593
+ if (timeSinceActivity >= currentInactivityTimeout) {
1594
+ // Stop the recording first
1595
+ await stopRecording();
1596
+
1597
+ // Show notification with link to settings
1598
+ try {
1599
+ await chrome.notifications.create('inactivity-stop', {
1600
+ type: 'basic',
1601
+ iconUrl: 'icon128.png',
1602
+ title: 'Recording Auto-Stopped',
1603
+ message: `Recording stopped after ${Math.round(currentInactivityTimeout / 1000)}s of inactivity. Click to adjust timeout in Settings.`,
1604
+ priority: 2
1605
+ });
1606
+ } catch (e) {
1607
+ console.warn('[Inactivity] Could not show notification:', e.message);
1608
+ }
1609
+ }
1610
+ }, 2000); // Check every 2 seconds
1611
+ }
1612
+
1613
+ /**
1614
+ * Stop inactivity monitoring
1615
+ * @param {number} tabId - Tab ID that was being monitored
1616
+ */
1617
+ async function stopInactivityMonitoring(tabId) {
1618
+ if (inactivityCheckInterval) {
1619
+ clearInterval(inactivityCheckInterval);
1620
+ inactivityCheckInterval = null;
1621
+ }
1622
+
1623
+ // Clear fallback timer
1624
+ if (inactivityFallbackTimer) {
1625
+ clearTimeout(inactivityFallbackTimer);
1626
+ inactivityFallbackTimer = null;
1627
+ }
1628
+
1629
+ // Reset state for next recording
1630
+ inactivityMonitoringStarted = false;
1631
+ inactivityDisabled = false; // Reset disabled state for next session
1632
+
1633
+ // Tell content script to stop tracking
1634
+ if (tabId) {
1635
+ try {
1636
+ await chrome.tabs.sendMessage(tabId, { action: 'stopActivityTracking' });
1637
+ } catch (error) {
1638
+ // Tab might be closed, that's ok
1639
+ }
1640
+ }
1641
+ }
1642
+
1643
+ /**
1644
+ * Handle user activity report from content script
1645
+ * @param {number} timestamp - Activity timestamp
1646
+ */
1647
+ function handleUserActivity(timestamp) {
1648
+ lastUserActivityTime = timestamp || Date.now();
1649
+ }
1650
+
1651
+ // Handle notification clicks - open settings page when inactivity notification is clicked
1652
+ chrome.notifications.onClicked.addListener((notificationId) => {
1653
+ if (notificationId === 'inactivity-stop') {
1654
+ chrome.runtime.openOptionsPage();
1655
+ chrome.notifications.clear(notificationId);
1656
+ }
1657
+ });
1275
1658
 
1276
1659
  // Initialize session manager
1277
1660
  (async function initializeSessionManager() {
1278
1661
  try {
1279
1662
  sessionManager = new ChromeExtensionSessionManager();
1280
- console.log('[ChromeSessionManager] Initialized successfully');
1663
+ // console.log('[ChromeSessionManager] Initialized successfully');
1281
1664
  } catch (error) {
1282
1665
  console.error('[ChromeSessionManager] Failed to initialize:', error);
1283
1666
  }
@@ -1399,7 +1782,7 @@ let workflowRecordingTabs = new Map(); // Map of tabId to recording state
1399
1782
  let workflowIncludeLogs = new Map(); // Map of tabId to includeLogsInExport setting
1400
1783
  let workflowScreenshotSettings = new Map(); // Map of tabId to screenshot settings
1401
1784
  let workflowSessionNames = new Map(); // Map of tabId to session name
1402
- let workflowUserIds = new Map(); // Map of tabId to userId for license tracking
1785
+ let workflowLogBuffers = new Map(); // Map of tabId to WorkflowLogBuffer instance
1403
1786
 
1404
1787
  //=============================================================================
1405
1788
  // COMPREHENSIVE ERROR HANDLING SYSTEM
@@ -1413,7 +1796,7 @@ let workflowUserIds = new Map(); // Map of tabId to userId for license tracking
1413
1796
  */
1414
1797
  async function initializeErrorHandling() {
1415
1798
  try {
1416
- console.log('[ErrorHandling] Initializing comprehensive error handling system...');
1799
+ // console.log('[ErrorHandling] Initializing comprehensive error handling system...');
1417
1800
 
1418
1801
  // Generate session ID if not exists
1419
1802
  if (!errorHandlingState.sessionId) {
@@ -1436,11 +1819,11 @@ async function initializeErrorHandling() {
1436
1819
  errorHandlingState.isInitialized = true;
1437
1820
  errorHandlingState.currentState = 'NORMAL_OPERATION';
1438
1821
 
1439
- console.log('[ErrorHandling] System initialization complete:', {
1440
- sessionId: errorHandlingState.sessionId,
1441
- state: errorHandlingState.currentState,
1442
- circuitBreaker: errorHandlingState.circuitBreakerState
1443
- });
1822
+ // console.log('[ErrorHandling] System initialization complete:', {
1823
+ // sessionId: errorHandlingState.sessionId,
1824
+ // state: errorHandlingState.currentState,
1825
+ // circuitBreaker: errorHandlingState.circuitBreakerState
1826
+ // });
1444
1827
 
1445
1828
  return true;
1446
1829
  } catch (error) {
@@ -1465,7 +1848,7 @@ async function initializeBackupStorage() {
1465
1848
  };
1466
1849
 
1467
1850
  request.onsuccess = () => {
1468
- console.log('[ErrorHandling] IndexedDB initialized successfully');
1851
+ // console.log('[ErrorHandling] IndexedDB initialized successfully');
1469
1852
  resolve(request.result);
1470
1853
  };
1471
1854
 
@@ -1495,7 +1878,7 @@ async function initializeBackupStorage() {
1495
1878
  errorStore.createIndex('timestamp', 'timestamp', { unique: false });
1496
1879
  }
1497
1880
 
1498
- console.log('[ErrorHandling] IndexedDB schema created');
1881
+ // console.log('[ErrorHandling] IndexedDB schema created');
1499
1882
  };
1500
1883
  });
1501
1884
  }
@@ -1506,7 +1889,7 @@ async function initializeBackupStorage() {
1506
1889
  function initializeCircuitBreaker() {
1507
1890
  errorHandlingState.circuitBreakerState = 'CLOSED';
1508
1891
  errorHandlingState.failedSaveCount = 0;
1509
- console.log('[ErrorHandling] Circuit breaker initialized in CLOSED state');
1892
+ // console.log('[ErrorHandling] Circuit breaker initialized in CLOSED state');
1510
1893
  }
1511
1894
 
1512
1895
  /**
@@ -1519,7 +1902,7 @@ async function setupNotificationSystem() {
1519
1902
  await chrome.notifications.clear(notificationId);
1520
1903
  }
1521
1904
 
1522
- console.log('[ErrorHandling] Notification system ready');
1905
+ // console.log('[ErrorHandling] Notification system ready');
1523
1906
  }
1524
1907
 
1525
1908
  /**
@@ -1558,7 +1941,7 @@ async function cleanupOldBackups() {
1558
1941
  }
1559
1942
  };
1560
1943
 
1561
- console.log('[ErrorHandling] Cleanup completed for data older than', ERROR_HANDLING_CONFIG.backupRetentionDays, 'days');
1944
+ // console.log('[ErrorHandling] Cleanup completed for data older than', ERROR_HANDLING_CONFIG.backupRetentionDays, 'days');
1562
1945
  };
1563
1946
  } catch (error) {
1564
1947
  console.error('[ErrorHandling] Failed to cleanup old backups:', error);
@@ -1585,13 +1968,13 @@ async function handleSaveAttempt(saveFunction, data, context = {}) {
1585
1968
  if (errorHandlingState.circuitBreakerState === 'OPEN') {
1586
1969
  const timeSinceLastFailure = Date.now() - (errorHandlingState.lastError?.timestamp || 0);
1587
1970
  if (timeSinceLastFailure < ERROR_HANDLING_CONFIG.resetTimeout) {
1588
- console.log('[ErrorHandling] Circuit breaker OPEN, attempting backup instead');
1971
+ // console.log('[ErrorHandling] Circuit breaker OPEN, attempting backup instead');
1589
1972
  await backupFailedData(data, context);
1590
1973
  return { success: false, circuitBreakerOpen: true };
1591
1974
  } else {
1592
1975
  // Try half-open
1593
1976
  errorHandlingState.circuitBreakerState = 'HALF_OPEN';
1594
- console.log('[ErrorHandling] Circuit breaker moving to HALF_OPEN state');
1977
+ // console.log('[ErrorHandling] Circuit breaker moving to HALF_OPEN state');
1595
1978
  }
1596
1979
  }
1597
1980
 
@@ -1602,7 +1985,7 @@ async function handleSaveAttempt(saveFunction, data, context = {}) {
1602
1985
  if (result && result.success !== false) {
1603
1986
  errorHandlingState.circuitBreakerState = 'CLOSED';
1604
1987
  errorHandlingState.failedSaveCount = 0;
1605
- console.log('[ErrorHandling] Save successful, circuit breaker CLOSED');
1988
+ // console.log('[ErrorHandling] Save successful, circuit breaker CLOSED');
1606
1989
  return { success: true, result };
1607
1990
  } else {
1608
1991
  throw new Error('Save function returned failure');
@@ -1642,7 +2025,7 @@ async function handleSaveFailure(error, data, context, attempt) {
1642
2025
  // Attempt backup
1643
2026
  try {
1644
2027
  await backupFailedData(data, context);
1645
- console.log('[ErrorHandling] Data backed up successfully');
2028
+ // console.log('[ErrorHandling] Data backed up successfully');
1646
2029
  } catch (backupError) {
1647
2030
  console.error('[ErrorHandling] CRITICAL: Backup also failed:', backupError);
1648
2031
  await createUserNotification('CRITICAL', 'Data loss imminent - manual intervention required');
@@ -1738,7 +2121,7 @@ async function backupFailedData(data, context) {
1738
2121
  const store = transaction.objectStore('sessionBackups');
1739
2122
 
1740
2123
  store.add(backupData);
1741
- console.log('[ErrorHandling] Data backed up with session ID:', errorHandlingState.sessionId);
2124
+ // console.log('[ErrorHandling] Data backed up with session ID:', errorHandlingState.sessionId);
1742
2125
  };
1743
2126
 
1744
2127
  // Also add to retained interactions if relevant
@@ -1779,7 +2162,7 @@ async function createUserNotification(priority, message) {
1779
2162
  }
1780
2163
 
1781
2164
  await chrome.notifications.create(notificationId, notificationOptions);
1782
- console.log('[ErrorHandling] User notification created:', notificationId);
2165
+ // console.log('[ErrorHandling] User notification created:', notificationId);
1783
2166
 
1784
2167
  // Set up notification click handlers
1785
2168
  chrome.notifications.onButtonClicked.addListener((notifId, buttonIndex) => {
@@ -1799,16 +2182,16 @@ async function createUserNotification(priority, message) {
1799
2182
  async function handleNotificationAction(buttonIndex, priority) {
1800
2183
  switch (buttonIndex) {
1801
2184
  case 0: // Retry Save
1802
- console.log('[ErrorHandling] User requested retry');
2185
+ // console.log('[ErrorHandling] User requested retry');
1803
2186
  await attemptRecovery();
1804
2187
  break;
1805
2188
  case 1: // View Details
1806
- console.log('[ErrorHandling] User requested error details');
2189
+ // console.log('[ErrorHandling] User requested error details');
1807
2190
  // Could open an options page or log details
1808
2191
  break;
1809
2192
  case 2: // Download Backup (for critical errors)
1810
2193
  if (priority === 'CRITICAL') {
1811
- console.log('[ErrorHandling] User requested backup download');
2194
+ // console.log('[ErrorHandling] User requested backup download');
1812
2195
  await downloadBackupData();
1813
2196
  }
1814
2197
  break;
@@ -1828,14 +2211,14 @@ async function attemptRecovery() {
1828
2211
 
1829
2212
  store.getAll().onsuccess = (event) => {
1830
2213
  const backups = event.target.result;
1831
- console.log('[ErrorHandling] Found', backups.length, 'backup records for recovery');
2214
+ // console.log('[ErrorHandling] Found', backups.length, 'backup records for recovery');
1832
2215
 
1833
2216
  // Process each backup
1834
2217
  backups.forEach(async (backup) => {
1835
2218
  try {
1836
2219
  // Attempt to save the backed up data
1837
2220
  // This would call the original save function with the backed up data
1838
- console.log('[ErrorHandling] Attempting recovery for session:', backup.sessionId);
2221
+ // console.log('[ErrorHandling] Attempting recovery for session:', backup.sessionId);
1839
2222
  } catch (error) {
1840
2223
  console.error('[ErrorHandling] Recovery failed for session:', backup.sessionId, error);
1841
2224
  }
@@ -1875,10 +2258,10 @@ async function downloadBackupData() {
1875
2258
 
1876
2259
  chrome.downloads.download({
1877
2260
  url: url,
1878
- filename: `chrome-pilot-backup-${Date.now()}.json`
2261
+ filename: `chrome-debug-backup-${Date.now()}.json`
1879
2262
  });
1880
2263
 
1881
- console.log('[ErrorHandling] Backup data download initiated');
2264
+ // console.log('[ErrorHandling] Backup data download initiated');
1882
2265
  };
1883
2266
  };
1884
2267
  };
@@ -1894,7 +2277,7 @@ async function createOffscreenDocument() {
1894
2277
  const hasDocument = await chrome.offscreen.hasDocument();
1895
2278
  if (hasDocument) {
1896
2279
  hasOffscreenDocument = true;
1897
- console.log('Offscreen document already exists');
2280
+ // console.log('Offscreen document already exists');
1898
2281
  return;
1899
2282
  }
1900
2283
 
@@ -1905,12 +2288,12 @@ async function createOffscreenDocument() {
1905
2288
  justification: 'Recording tab frames for Chrome Debug'
1906
2289
  });
1907
2290
  hasOffscreenDocument = true;
1908
- console.log('Created new offscreen document');
2291
+ // console.log('Created new offscreen document');
1909
2292
  } catch (error) {
1910
2293
  if (error.message.includes('Only a single offscreen document may be created')) {
1911
2294
  // Document already exists, just mark it as available
1912
2295
  hasOffscreenDocument = true;
1913
- console.log('Offscreen document already exists (caught error)');
2296
+ // console.log('Offscreen document already exists (caught error)');
1914
2297
  } else {
1915
2298
  throw error;
1916
2299
  }
@@ -1928,7 +2311,7 @@ async function closeOffscreenDocument() {
1928
2311
  }
1929
2312
 
1930
2313
  chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
1931
- console.log('Received message:', request.action || request.type || 'unknown');
2314
+ // console.log('Received message:', request.action || request.type || 'unknown');
1932
2315
 
1933
2316
  if (request.action === 'sendToServer') {
1934
2317
  // Primary port is 3028 - try it first, then fallback to others if needed
@@ -1936,11 +2319,11 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
1936
2319
 
1937
2320
  async function tryPorts() {
1938
2321
  let lastError = null;
1939
- console.log('Trying to connect to server...');
2322
+ // console.log('Trying to connect to server...');
1940
2323
 
1941
2324
  for (const port of ports) {
1942
2325
  try {
1943
- console.log(`Trying port ${port}...`);
2326
+ // console.log(`Trying port ${port}...`);
1944
2327
  const controller = new AbortController();
1945
2328
  const timeoutId = setTimeout(() => controller.abort(), DISCOVERY_TIMEOUT);
1946
2329
 
@@ -1957,7 +2340,7 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
1957
2340
 
1958
2341
  if (response.ok) {
1959
2342
  const result = await response.json();
1960
- console.log(`Success on port ${port}:`, result);
2343
+ // console.log(`Success on port ${port}:`, result);
1961
2344
  sendResponse({ success: true, result, port });
1962
2345
  return;
1963
2346
  } else {
@@ -1965,7 +2348,7 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
1965
2348
  try {
1966
2349
  const errorData = await response.json();
1967
2350
  if (errorData.message || errorData.error) {
1968
- console.log(`Port ${port} error:`, errorData);
2351
+ // console.log(`Port ${port} error:`, errorData);
1969
2352
  sendResponse({
1970
2353
  success: false,
1971
2354
  result: errorData,
@@ -1977,11 +2360,11 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
1977
2360
  // Response wasn't JSON
1978
2361
  }
1979
2362
  lastError = `Port ${port}: HTTP ${response.status}`;
1980
- console.log(lastError);
2363
+ // console.log(lastError);
1981
2364
  }
1982
2365
  } catch (e) {
1983
2366
  lastError = `Port ${port}: ${e.message}`;
1984
- console.log(lastError);
2367
+ // console.log(lastError);
1985
2368
  }
1986
2369
  }
1987
2370
 
@@ -2015,8 +2398,13 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
2015
2398
  }
2016
2399
 
2017
2400
  if (request.action === 'stopRecording') {
2018
- stopRecording().then(() => {
2019
- sendResponse({ success: true });
2401
+ stopRecording().then(async (result) => {
2402
+ // Track usage for FREE tier after successful screen recording
2403
+ if (result.success) {
2404
+ const usageResult = await incrementUsageAfterRecording();
2405
+ result.usage = usageResult; // Include updated usage in response
2406
+ }
2407
+ sendResponse(result); // Pass through { success: true, sessionId: ..., usage: ... }
2020
2408
  }).catch((error) => {
2021
2409
  console.error('Error stopping recording:', error);
2022
2410
  sendResponse({ success: false, error: error.message });
@@ -2024,6 +2412,23 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
2024
2412
  return true;
2025
2413
  }
2026
2414
 
2415
+ if (request.action === 'openFrameEditor') {
2416
+ // Content scripts can't create tabs, so handle it here
2417
+ const sessionId = request.sessionId;
2418
+ const type = request.type; // 'workflow' or undefined for screen recordings
2419
+ const editorUrl = type === 'workflow'
2420
+ ? chrome.runtime.getURL(`pro/frame-editor.html?sessionId=${sessionId}&type=workflow`)
2421
+ : chrome.runtime.getURL(`pro/frame-editor.html?sessionId=${sessionId}`);
2422
+
2423
+ chrome.tabs.create({ url: editorUrl }).then(() => {
2424
+ sendResponse({ success: true });
2425
+ }).catch((error) => {
2426
+ console.error('Error opening frame editor:', error);
2427
+ sendResponse({ success: false, error: error.message });
2428
+ });
2429
+ return true;
2430
+ }
2431
+
2027
2432
  // Get quota information for browser-only mode
2028
2433
  if (request.action === 'getQuotaInfo') {
2029
2434
  getQuotaInfo().then((quotaInfo) => {
@@ -2037,7 +2442,7 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
2037
2442
 
2038
2443
  // Session manager lease renewal handler
2039
2444
  if (request.action === 'renewLease') {
2040
- console.log('[Background] RENEW LEASE - Session:', request.sessionId, 'Manager exists:', !!sessionManager);
2445
+ // console.log('[Background] RENEW LEASE - Session:', request.sessionId, 'Manager exists:', !!sessionManager);
2041
2446
 
2042
2447
  if (!sessionManager) {
2043
2448
  console.error('[Background] Session manager not available');
@@ -2053,7 +2458,7 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
2053
2458
  };
2054
2459
 
2055
2460
  sessionManager.renewLease(leaseRequest).then((result) => {
2056
- console.log('[Background] Lease renewed - Success:', result.success);
2461
+ // console.log('[Background] Lease renewed - Success:', result.success);
2057
2462
  sendResponse(result);
2058
2463
  }).catch((error) => {
2059
2464
  console.error('[Background] Error renewing lease:', error);
@@ -2087,6 +2492,45 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
2087
2492
  return true;
2088
2493
  }
2089
2494
 
2495
+ // User activity handler for inactivity tracking
2496
+ if (request.action === 'userActivity') {
2497
+ handleUserActivity(request.timestamp);
2498
+ sendResponse({ success: true });
2499
+ return false; // Synchronous response
2500
+ }
2501
+
2502
+ // Countdown complete handler - content script finished 3-2-1 countdown
2503
+ if (request.action === 'countdownComplete') {
2504
+ // Start inactivity monitoring now that countdown is done
2505
+ // Use isCurrentlyRecording (the actual state variable)
2506
+ if (isCurrentlyRecording && recordingTabId && !inactivityMonitoringStarted) {
2507
+ startInactivityMonitoring(recordingTabId);
2508
+ }
2509
+ sendResponse({ success: true });
2510
+ return false;
2511
+ }
2512
+
2513
+ // Toggle inactivity timeout (user clicked disable/enable button)
2514
+ if (request.action === 'toggleInactivityTimeout') {
2515
+ inactivityDisabled = !inactivityDisabled;
2516
+
2517
+ if (inactivityDisabled) {
2518
+ // Stop the check interval but keep tracking activity
2519
+ if (inactivityCheckInterval) {
2520
+ clearInterval(inactivityCheckInterval);
2521
+ inactivityCheckInterval = null;
2522
+ }
2523
+ } else {
2524
+ // Restart monitoring
2525
+ if (recordingTabId && !inactivityCheckInterval) {
2526
+ startInactivityMonitoring(recordingTabId);
2527
+ }
2528
+ }
2529
+
2530
+ sendResponse({ success: true, disabled: inactivityDisabled });
2531
+ return false;
2532
+ }
2533
+
2090
2534
  // Force stop session handler (emergency)
2091
2535
  if (request.action === 'forceStopSession') {
2092
2536
  if (!sessionManager) {
@@ -2140,7 +2584,7 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
2140
2584
  */
2141
2585
  /*
2142
2586
  if (request.action === 'takeStandaloneSnapshot') {
2143
- console.log('Taking standalone snapshot for tab:', request.tabId);
2587
+ // console.log('Taking standalone snapshot for tab:', request.tabId);
2144
2588
  takeStandaloneSnapshot(request.tabId, request.note).then((result) => {
2145
2589
  sendResponse({ success: true, sessionId: result.sessionId });
2146
2590
  }).catch((error) => {
@@ -2188,7 +2632,7 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
2188
2632
  }
2189
2633
 
2190
2634
  if (request.action === 'startRecordingFromContent') {
2191
- console.log('Starting recording from content script');
2635
+ // console.log('Starting recording from content script');
2192
2636
  const tabId = sender.tab.id;
2193
2637
 
2194
2638
  // Store recording state and show notification
@@ -2212,28 +2656,14 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
2212
2656
  }
2213
2657
 
2214
2658
  if (request.action === 'deleteRecording') {
2215
- console.log('Deleting recording:', request.recordingId);
2659
+ // console.log('Deleting recording:', request.recordingId);
2216
2660
  deleteRecordingFromServer(request.recordingId, sendResponse);
2217
2661
  return true;
2218
2662
  }
2219
2663
 
2220
- // License check handler for workflow recording
2221
- if (request.action === 'checkLicenseForWorkflow') {
2222
- console.log('[License] popup.js requesting license check for workflow recording');
2223
- LicenseHelper.checkLicenseBeforeRecording().then((licenseCheck) => {
2224
- console.log('[License] License check result for popup:', licenseCheck);
2225
- sendResponse(licenseCheck);
2226
- }).catch((error) => {
2227
- console.error('[License] Error checking license:', error);
2228
- // Fail-open: allow recording on error
2229
- sendResponse({ allowed: true, error: error.message });
2230
- });
2231
- return true;
2232
- }
2233
-
2234
2664
  // Workflow recording handlers
2235
2665
  if (request.action === 'startWorkflowRecording') {
2236
- console.log('Starting workflow recording for tab:', request.tabId);
2666
+ // console.log('Starting workflow recording for tab:', request.tabId);
2237
2667
  startWorkflowRecording(
2238
2668
  request.tabId,
2239
2669
  request.includeLogsInExport,
@@ -2249,9 +2679,17 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
2249
2679
  }
2250
2680
 
2251
2681
  if (request.action === 'stopWorkflowRecording') {
2252
- console.log('Stopping workflow recording for tab:', request.tabId);
2253
- stopWorkflowRecording(request.tabId).then((workflow) => {
2254
- sendResponse({ success: true, workflow: workflow });
2682
+ const tabId = request.tabId || sender.tab?.id;
2683
+ // console.log('Stopping workflow recording for tab:', tabId);
2684
+ // stopWorkflowRecording already returns { success: true, workflow: {...}, savedToServer: bool }
2685
+ // so we pass it through directly - don't double-wrap!
2686
+ stopWorkflowRecording(tabId).then(async (result) => {
2687
+ // Track usage for FREE tier after successful recording
2688
+ if (result.success) {
2689
+ const usageResult = await incrementUsageAfterRecording();
2690
+ result.usage = usageResult; // Include updated usage in response
2691
+ }
2692
+ sendResponse(result);
2255
2693
  }).catch((error) => {
2256
2694
  console.error('Error stopping workflow recording:', error);
2257
2695
  sendResponse({ success: false, error: error.message });
@@ -2338,7 +2776,7 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
2338
2776
  frameIndex: frameIndex
2339
2777
  };
2340
2778
  screenInteractions.push(interaction);
2341
- console.log('Screen interaction recorded:', interaction.type, 'Total:', screenInteractions.length);
2779
+ // console.log('Screen interaction recorded:', interaction.type, 'Total:', screenInteractions.length);
2342
2780
  }
2343
2781
  }).catch(error => {
2344
2782
  console.error('Error validating recording state for screen interaction:', error);
@@ -2351,23 +2789,24 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
2351
2789
  frameIndex: frameCounter.get(currentRecordingSessionId) || 0
2352
2790
  };
2353
2791
  screenInteractions.push(interaction);
2354
- console.log('Screen interaction recorded:', interaction.type, 'Total:', screenInteractions.length);
2792
+ // console.log('Screen interaction recorded:', interaction.type, 'Total:', screenInteractions.length);
2355
2793
  }
2356
2794
  }
2357
2795
  }
2358
2796
 
2359
- // Buffer workflow console logs
2797
+ // Buffer workflow console logs using race-safe WorkflowLogBuffer
2360
2798
  if (request.action === 'workflowConsoleLog' && sender.tab) {
2361
2799
  const tabId = sender.tab.id;
2362
2800
  if (workflowRecordingTabs.has(tabId) && workflowIncludeLogs.get(tabId)) {
2363
- // Store logs in session storage for workflow recording
2364
- chrome.storage.session.get(`workflow_${tabId}`).then(result => {
2365
- const logs = result[`workflow_${tabId}`] || [];
2366
- logs.push(request.log);
2367
- chrome.storage.session.set({ [`workflow_${tabId}`]: logs });
2368
- }).catch(err => {
2369
- console.error('Error buffering workflow log:', err);
2370
- });
2801
+ const buffer = workflowLogBuffers.get(tabId);
2802
+ if (buffer) {
2803
+ // Use race-safe buffer to prevent read-modify-write race conditions
2804
+ buffer.addLog(request.log).catch(err => {
2805
+ console.error('[WorkflowLogBuffer] Error adding log:', err);
2806
+ });
2807
+ } else {
2808
+ console.warn(`[Workflow] No buffer found for tab ${tabId}, log dropped`);
2809
+ }
2371
2810
  }
2372
2811
  }
2373
2812
 
@@ -2466,18 +2905,18 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
2466
2905
 
2467
2906
  // Handle workflow screenshot capture
2468
2907
  if (request.action === 'captureWorkflowScreenshot' && sender.tab) {
2469
- console.log('[SCREENSHOT-DEBUG] background.js - Received captureWorkflowScreenshot message');
2908
+ // console.log('[SCREENSHOT-DEBUG] background.js - Received captureWorkflowScreenshot message');
2470
2909
  const tabId = sender.tab.id;
2471
2910
  const settings = request.settings || {};
2472
- console.log('[SCREENSHOT-DEBUG] background.js - tabId:', tabId);
2473
- console.log('[SCREENSHOT-DEBUG] background.js - settings:', JSON.stringify(settings));
2911
+ // console.log('[SCREENSHOT-DEBUG] background.js - tabId:', tabId);
2912
+ // console.log('[SCREENSHOT-DEBUG] background.js - settings:', JSON.stringify(settings));
2474
2913
 
2475
2914
  captureTabScreenshot(tabId, settings).then((screenshotData) => {
2476
- console.log('[SCREENSHOT-DEBUG] background.js - captureTabScreenshot succeeded');
2477
- console.log('[SCREENSHOT-DEBUG] background.js - screenshotData type:', typeof screenshotData);
2478
- console.log('[SCREENSHOT-DEBUG] background.js - screenshotData length:', screenshotData?.length);
2479
- console.log('[SCREENSHOT-DEBUG] background.js - screenshotData preview:', screenshotData?.substring(0, 100));
2480
- console.log('[SCREENSHOT-DEBUG] background.js - Sending response with success: true');
2915
+ // console.log('[SCREENSHOT-DEBUG] background.js - captureTabScreenshot succeeded');
2916
+ // console.log('[SCREENSHOT-DEBUG] background.js - screenshotData type:', typeof screenshotData);
2917
+ // console.log('[SCREENSHOT-DEBUG] background.js - screenshotData length:', screenshotData?.length);
2918
+ // console.log('[SCREENSHOT-DEBUG] background.js - screenshotData preview:', screenshotData?.substring(0, 100));
2919
+ // console.log('[SCREENSHOT-DEBUG] background.js - Sending response with success: true');
2481
2920
  sendResponse({ success: true, screenshotData });
2482
2921
  }).catch((error) => {
2483
2922
  console.error('[SCREENSHOT-DEBUG] background.js - captureTabScreenshot FAILED');
@@ -2511,7 +2950,7 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
2511
2950
  /*
2512
2951
  async function takeStandaloneSnapshot(tabId, note = '') {
2513
2952
  try {
2514
- console.log('Taking standalone snapshot for tab:', tabId);
2953
+ // console.log('Taking standalone snapshot for tab:', tabId);
2515
2954
 
2516
2955
  // Create a unique session ID for the snapshot
2517
2956
  const sessionId = `snapshot_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
@@ -2587,7 +3026,7 @@ async function takeStandaloneSnapshot(tabId, note = '') {
2587
3026
 
2588
3027
  if (response.ok) {
2589
3028
  const result = await response.json();
2590
- console.log('Snapshot uploaded successfully:', sessionId);
3029
+ // console.log('Snapshot uploaded successfully:', sessionId);
2591
3030
  uploadSuccess = true;
2592
3031
  break;
2593
3032
  }
@@ -2611,34 +3050,34 @@ async function takeStandaloneSnapshot(tabId, note = '') {
2611
3050
 
2612
3051
  // Capture screenshot for workflow recording
2613
3052
  async function captureTabScreenshot(tabId, settings) {
2614
- console.log('[SCREENSHOT-DEBUG] captureTabScreenshot - ENTRY');
2615
- console.log('[SCREENSHOT-DEBUG] captureTabScreenshot - tabId:', tabId);
2616
- console.log('[SCREENSHOT-DEBUG] captureTabScreenshot - settings:', JSON.stringify(settings));
3053
+ // console.log('[SCREENSHOT-DEBUG] captureTabScreenshot - ENTRY');
3054
+ // console.log('[SCREENSHOT-DEBUG] captureTabScreenshot - tabId:', tabId);
3055
+ // console.log('[SCREENSHOT-DEBUG] captureTabScreenshot - settings:', JSON.stringify(settings));
2617
3056
 
2618
3057
  try {
2619
3058
  // Get the tab to find its window
2620
- console.log('[SCREENSHOT-DEBUG] captureTabScreenshot - Getting tab info...');
3059
+ // console.log('[SCREENSHOT-DEBUG] captureTabScreenshot - Getting tab info...');
2621
3060
  const tab = await chrome.tabs.get(tabId);
2622
3061
  const windowId = tab.windowId;
2623
- console.log('[SCREENSHOT-DEBUG] captureTabScreenshot - windowId:', windowId);
2624
- console.log('[SCREENSHOT-DEBUG] captureTabScreenshot - tab.url:', tab.url);
3062
+ // console.log('[SCREENSHOT-DEBUG] captureTabScreenshot - windowId:', windowId);
3063
+ // console.log('[SCREENSHOT-DEBUG] captureTabScreenshot - tab.url:', tab.url);
2625
3064
 
2626
3065
  const captureOptions = {
2627
3066
  format: settings.format || 'jpeg',
2628
3067
  quality: settings.quality || 30
2629
3068
  };
2630
- console.log('[SCREENSHOT-DEBUG] captureTabScreenshot - captureOptions:', JSON.stringify(captureOptions));
2631
- console.log('[SCREENSHOT-DEBUG] captureTabScreenshot - Calling chrome.tabs.captureVisibleTab...');
3069
+ // console.log('[SCREENSHOT-DEBUG] captureTabScreenshot - captureOptions:', JSON.stringify(captureOptions));
3070
+ // console.log('[SCREENSHOT-DEBUG] captureTabScreenshot - Calling chrome.tabs.captureVisibleTab...');
2632
3071
 
2633
3072
  const dataUrl = await chrome.tabs.captureVisibleTab(windowId, captureOptions);
2634
3073
 
2635
- console.log('[SCREENSHOT-DEBUG] captureTabScreenshot - captureVisibleTab SUCCESS');
2636
- console.log('[SCREENSHOT-DEBUG] captureTabScreenshot - dataUrl length:', dataUrl?.length);
2637
- console.log('[SCREENSHOT-DEBUG] captureTabScreenshot - dataUrl prefix:', dataUrl?.substring(0, 100));
3074
+ // console.log('[SCREENSHOT-DEBUG] captureTabScreenshot - captureVisibleTab SUCCESS');
3075
+ // console.log('[SCREENSHOT-DEBUG] captureTabScreenshot - dataUrl length:', dataUrl?.length);
3076
+ // console.log('[SCREENSHOT-DEBUG] captureTabScreenshot - dataUrl prefix:', dataUrl?.substring(0, 100));
2638
3077
 
2639
3078
  // If resolution is specified, resize the image
2640
3079
  if (settings.maxWidth || settings.maxHeight) {
2641
- console.log('[SCREENSHOT-DEBUG] captureTabScreenshot - Resizing required, maxWidth:', settings.maxWidth, 'maxHeight:', settings.maxHeight);
3080
+ // console.log('[SCREENSHOT-DEBUG] captureTabScreenshot - Resizing required, maxWidth:', settings.maxWidth, 'maxHeight:', settings.maxHeight);
2642
3081
  // Create an image element to get dimensions
2643
3082
  const img = new Image();
2644
3083
  const canvas = new OffscreenCanvas(1, 1);
@@ -2678,8 +3117,8 @@ async function captureTabScreenshot(tabId, settings) {
2678
3117
  });
2679
3118
  }
2680
3119
 
2681
- console.log('[SCREENSHOT-DEBUG] captureTabScreenshot - No resize needed, returning dataUrl');
2682
- console.log('[SCREENSHOT-DEBUG] captureTabScreenshot - Final dataUrl length:', dataUrl?.length);
3120
+ // console.log('[SCREENSHOT-DEBUG] captureTabScreenshot - No resize needed, returning dataUrl');
3121
+ // console.log('[SCREENSHOT-DEBUG] captureTabScreenshot - Final dataUrl length:', dataUrl?.length);
2683
3122
  return dataUrl;
2684
3123
  } catch (error) {
2685
3124
  console.error('[SCREENSHOT-DEBUG] captureTabScreenshot - EXCEPTION CAUGHT');
@@ -2693,7 +3132,7 @@ async function captureTabScreenshot(tabId, settings) {
2693
3132
  // Start recording
2694
3133
  // Detect if server is available (server mode vs browser-only mode)
2695
3134
  async function detectServerMode() {
2696
- console.log('[ServerDetection] Checking server availability...');
3135
+ // console.log('[ServerDetection] Checking server availability...');
2697
3136
 
2698
3137
  for (const port of CONFIG_PORTS) {
2699
3138
  try {
@@ -2707,7 +3146,7 @@ async function detectServerMode() {
2707
3146
  ]);
2708
3147
 
2709
3148
  if (response.ok) {
2710
- console.log(`[ServerDetection] Server detected on port ${port}`);
3149
+ // console.log(`[ServerDetection] Server detected on port ${port}`);
2711
3150
  serverMode = 'server';
2712
3151
  return 'server';
2713
3152
  }
@@ -2716,7 +3155,7 @@ async function detectServerMode() {
2716
3155
  }
2717
3156
  }
2718
3157
 
2719
- console.log('[ServerDetection] No server detected - using browser-only mode');
3158
+ // console.log('[ServerDetection] No server detected - using browser-only mode');
2720
3159
  serverMode = 'browser-only';
2721
3160
  return 'browser-only';
2722
3161
  }
@@ -2742,30 +3181,9 @@ async function getQuotaInfo() {
2742
3181
 
2743
3182
  async function startRecording(tabId, settings = {}) {
2744
3183
  try {
2745
- // STEP 1: Detect server mode before starting
3184
+ // Detect server mode before starting
2746
3185
  await detectServerMode();
2747
- console.log(`[Recording] Mode: ${serverMode}`);
2748
-
2749
- // STEP 2: Check license and usage limits BEFORE recording
2750
- console.log('[License] Checking license before recording...');
2751
- const licenseCheck = await LicenseHelper.checkLicenseBeforeRecording();
2752
- console.log('[License] License check result:', licenseCheck);
2753
-
2754
- if (!licenseCheck.allowed) {
2755
- // Show notification to user
2756
- chrome.notifications.create({
2757
- type: 'basic',
2758
- iconUrl: chrome.runtime.getURL('icon48.png'),
2759
- title: 'Recording Limit Reached',
2760
- message: licenseCheck.message || 'Upgrade to Pro for unlimited recordings.',
2761
- priority: 2
2762
- });
2763
-
2764
- throw new Error(licenseCheck.message || 'Recording not allowed');
2765
- }
2766
-
2767
- // Store userId for later usage tracking
2768
- const userId = licenseCheck.userId;
3186
+ // console.log(`[Recording] Mode: ${serverMode}`);
2769
3187
 
2770
3188
  // Check if session manager is available
2771
3189
  if (!sessionManager) {
@@ -2786,12 +3204,12 @@ async function startRecording(tabId, settings = {}) {
2786
3204
 
2787
3205
  // Start capturing console logs FIRST, before any other setup
2788
3206
  const logsStarted = await startCapturingLogs(tabId);
2789
- console.log('Console log capture started:', logsStarted);
3207
+ // console.log('Console log capture started:', logsStarted);
2790
3208
 
2791
3209
  // Give console interception time to settle (increased from 100ms to 500ms for reliability)
2792
3210
  if (logsStarted) {
2793
3211
  await new Promise(resolve => setTimeout(resolve, 500));
2794
- console.log('[Recording] Console interception ready');
3212
+ // console.log('[Recording] Console interception ready');
2795
3213
  } else {
2796
3214
  console.warn('[Recording] Console capture unavailable for this page - recording will proceed without console logs');
2797
3215
  }
@@ -2804,7 +3222,7 @@ async function startRecording(tabId, settings = {}) {
2804
3222
  targetTabId: tabId
2805
3223
  });
2806
3224
 
2807
- console.log('Got stream ID:', streamId);
3225
+ // console.log('Got stream ID:', streamId);
2808
3226
 
2809
3227
  // Create session with session manager
2810
3228
  const sessionConfig = {
@@ -2837,7 +3255,7 @@ async function startRecording(tabId, settings = {}) {
2837
3255
 
2838
3256
  // STEP 4: Initialize browser-only recording if in browser-only mode
2839
3257
  if (serverMode === 'browser-only' && browserRecordingManager) {
2840
- console.log('[Recording] Initializing browser-only recording in IndexedDB');
3258
+ // console.log('[Recording] Initializing browser-only recording in IndexedDB');
2841
3259
  const tab = await chrome.tabs.get(tabId);
2842
3260
  await browserRecordingManager.startRecording(sessionId, {
2843
3261
  tabId: tabId,
@@ -2870,7 +3288,19 @@ async function startRecording(tabId, settings = {}) {
2870
3288
  // Update badge
2871
3289
  chrome.action.setBadgeText({ text: 'REC' });
2872
3290
  chrome.action.setBadgeBackgroundColor({ color: '#FF0000' });
2873
-
3291
+
3292
+ // DON'T start inactivity monitoring here - wait for countdownComplete message
3293
+ // This ensures the 10s timer starts AFTER the 3s visual countdown, not before
3294
+ // Set a fallback timer in case content script doesn't send countdownComplete
3295
+ inactivityMonitoringStarted = false;
3296
+ if (inactivityFallbackTimer) clearTimeout(inactivityFallbackTimer);
3297
+ inactivityFallbackTimer = setTimeout(async () => {
3298
+ // Use isCurrentlyRecording (the actual state variable)
3299
+ if (isCurrentlyRecording && !inactivityMonitoringStarted) {
3300
+ await startInactivityMonitoring(tabId);
3301
+ }
3302
+ }, 5000); // 5s = 3s countdown + 2s buffer
3303
+
2874
3304
  // If on a restricted page, notify user
2875
3305
  const tab = await chrome.tabs.get(tabId);
2876
3306
  if (tab.url && (tab.url.startsWith('chrome://') || tab.url.startsWith('chrome-extension://'))) {
@@ -2891,12 +3321,12 @@ async function startRecording(tabId, settings = {}) {
2891
3321
  sessionId: sessionId
2892
3322
  }).catch(async (error) => {
2893
3323
  // Content script might not be injected, try to inject it
2894
- console.log('Could not notify content script, attempting to inject:', error);
3324
+ // console.log('Could not notify content script, attempting to inject:', error);
2895
3325
 
2896
3326
  // First check if this tab allows content script injection
2897
3327
  const tab = await chrome.tabs.get(tabId);
2898
3328
  if (!tab.url || tab.url.startsWith('chrome://') || tab.url.startsWith('chrome-extension://') || tab.url.startsWith('moz-extension://')) {
2899
- console.log('Cannot inject content script into restricted URL:', tab.url);
3329
+ // console.log('Cannot inject content script into restricted URL:', tab.url);
2900
3330
  return;
2901
3331
  }
2902
3332
 
@@ -2932,7 +3362,7 @@ async function startRecording(tabId, settings = {}) {
2932
3362
  url: tab.url,
2933
3363
  title: tab.title
2934
3364
  });
2935
- console.log('[Recording] Browser recording initialized for session:', sessionId);
3365
+ // console.log('[Recording] Browser recording initialized for session:', sessionId);
2936
3366
  }
2937
3367
 
2938
3368
  // Send stream ID, session info, and settings to offscreen document for frame capture
@@ -2952,7 +3382,7 @@ async function startRecording(tabId, settings = {}) {
2952
3382
  }
2953
3383
  });
2954
3384
 
2955
- console.log('Recording started in', serverMode, 'mode');
3385
+ // console.log('Recording started in', serverMode, 'mode');
2956
3386
  } catch (error) {
2957
3387
  console.error('Error in startRecording:', error);
2958
3388
  throw error;
@@ -2965,16 +3395,26 @@ async function stopRecording() {
2965
3395
  // Check if session manager is available
2966
3396
  if (!sessionManager) {
2967
3397
  console.warn('Session manager not available, stopping recording anyway');
2968
- return;
3398
+ return { success: false, error: 'Session manager not available' };
2969
3399
  }
2970
3400
 
2971
3401
  // Check if currently recording via session manager
2972
3402
  const currentlyRecording = await isCurrentlyRecordingAsync();
2973
3403
  if (!currentlyRecording) {
2974
- console.log('No active recording session to stop');
2975
- return;
3404
+ // console.log('No active recording session to stop');
3405
+ return { success: false, error: 'No active recording' };
2976
3406
  }
2977
3407
 
3408
+ // Store sessionId before clearing it
3409
+ const stoppedSessionId = currentSession?.sessionId;
3410
+
3411
+ // Stop inactivity monitoring
3412
+ await stopInactivityMonitoring(recordingTabId);
3413
+
3414
+ // Capture frame count and duration for the response
3415
+ let recordedFrameCount = 0;
3416
+ let recordedDuration = 0;
3417
+
2978
3418
  // Stop recording with session manager
2979
3419
  if (currentSession?.sessionId && currentOwnerId) {
2980
3420
  const stopResult = await sessionManager.stopRecording(currentSession.sessionId, currentOwnerId);
@@ -2982,18 +3422,15 @@ async function stopRecording() {
2982
3422
  console.error('Failed to stop session:', stopResult.error?.message);
2983
3423
  // Continue with cleanup anyway
2984
3424
  } else {
2985
- console.log(`Recording stopped. Duration: ${stopResult.sessionDuration}ms, Frames: ${stopResult.frameCount}`);
3425
+ recordedFrameCount = stopResult.frameCount || 0;
3426
+ recordedDuration = stopResult.sessionDuration || 0;
3427
+ // console.log(`Recording stopped. Duration: ${recordedDuration}ms, Frames: ${recordedFrameCount}`);
2986
3428
 
2987
3429
  // Finalize browser-only recording if in browser-only mode
2988
3430
  if (serverMode === 'browser-only' && browserRecordingManager) {
2989
- console.log('[Browser-Only] Finalizing browser recording in IndexedDB');
3431
+ // console.log('[Browser-Only] Finalizing browser recording in IndexedDB');
2990
3432
  await browserRecordingManager.stopRecording(currentSession.sessionId);
2991
3433
  }
2992
-
2993
- // STEP 2: Track usage AFTER successful recording completion
2994
- console.log('[License] Tracking usage after recording completion...');
2995
- const usageResult = await LicenseHelper.trackUsageAfterRecording();
2996
- console.log('[License] Usage tracking result:', usageResult);
2997
3434
  }
2998
3435
  }
2999
3436
 
@@ -3002,10 +3439,10 @@ async function stopRecording() {
3002
3439
  let flushCompleted = false;
3003
3440
  if (logBuffer) {
3004
3441
  try {
3005
- console.log('[Background] Flushing all buffered logs before stop...');
3442
+ // console.log('[Background] Flushing all buffered logs before stop...');
3006
3443
  await logBuffer.flushAll();
3007
3444
  flushCompleted = true;
3008
- console.log('[Background] Successfully flushed all buffered logs');
3445
+ // console.log('[Background] Successfully flushed all buffered logs');
3009
3446
  } catch (flushError) {
3010
3447
  console.error('[Background] Failed to flush logs during stop:', flushError);
3011
3448
  // Continue with stop process even if flush fails
@@ -3023,6 +3460,29 @@ async function stopRecording() {
3023
3460
  target: 'offscreen'
3024
3461
  });
3025
3462
 
3463
+ // CRITICAL: Store recordingTabId BEFORE clearing it (for cleanup)
3464
+ const previousRecordingTabId = recordingTabId;
3465
+ // console.log('[Background] previousRecordingTabId:', previousRecordingTabId, 'stoppedSessionId:', stoppedSessionId);
3466
+
3467
+ // CRITICAL FIX: Send stop-screen-capture-tracking message to recording tab WITH sessionId
3468
+ // so it can show the completion UI
3469
+ if (previousRecordingTabId && stoppedSessionId) {
3470
+ try {
3471
+ // console.log('[Background] Sending recording-complete-show-ui message to tab:', previousRecordingTabId, 'with sessionId:', stoppedSessionId);
3472
+ await chrome.tabs.sendMessage(previousRecordingTabId, {
3473
+ type: 'recording-complete-show-ui',
3474
+ sessionId: stoppedSessionId
3475
+ }).catch((err) => {
3476
+ // console.log('[Background] Could not send completion UI message to recording tab:', err);
3477
+ });
3478
+ // console.log('[Background] Successfully sent recording-complete-show-ui message');
3479
+ } catch (error) {
3480
+ console.error('[Background] Failed to send completion UI message:', error);
3481
+ }
3482
+ } else {
3483
+ // console.log('[Background] NOT sending recording-complete-show-ui - missing previousRecordingTabId or stoppedSessionId');
3484
+ }
3485
+
3026
3486
  // CRITICAL FIX: Send stop-screen-capture-tracking message to ALL content scripts
3027
3487
  try {
3028
3488
  const tabs = await chrome.tabs.query({});
@@ -3032,23 +3492,33 @@ async function stopRecording() {
3032
3492
  type: 'stop-screen-capture-tracking'
3033
3493
  }).catch(() => {
3034
3494
  // Ignore errors for tabs that don't have content scripts
3035
- console.log(`[Background] Could not send cleanup message to tab ${tab.id}`);
3495
+ // console.log(`[Background] Could not send cleanup message to tab ${tab.id}`);
3036
3496
  });
3037
3497
  }
3038
3498
  return Promise.resolve();
3039
3499
  });
3040
3500
  await Promise.all(cleanupPromises);
3041
- console.log('[Background] Cleanup messages sent to all content scripts');
3501
+ // console.log('[Background] Cleanup messages sent to all content scripts');
3042
3502
  } catch (error) {
3043
3503
  console.error('[Background] Failed to send cleanup messages to content scripts:', error);
3044
3504
  }
3045
3505
 
3506
+ // CRITICAL FIX: Restore original console methods and remove event listeners
3507
+ if (previousRecordingTabId) {
3508
+ try {
3509
+ // console.log('[Background] Stopping console log capture for tab:', previousRecordingTabId);
3510
+ await stopCapturingLogs(previousRecordingTabId);
3511
+ } catch (cleanupError) {
3512
+ console.error('[Background] Failed to cleanup console interceptor:', cleanupError);
3513
+ // Continue with stop process even if cleanup fails
3514
+ }
3515
+ }
3516
+
3046
3517
  // Clear session cache
3047
3518
  currentSession = null;
3048
3519
  currentOwnerId = null;
3049
3520
 
3050
3521
  // Update legacy state for backward compatibility (will be removed)
3051
- const previousRecordingTabId = recordingTabId; // Store for logBuffer cleanup
3052
3522
  isCurrentlyRecording = false;
3053
3523
  recordingTabId = null;
3054
3524
  currentRecordingSessionId = null;
@@ -3058,11 +3528,56 @@ async function stopRecording() {
3058
3528
  logBuffer.clearTab(previousRecordingTabId);
3059
3529
  }
3060
3530
 
3061
- console.log('Stop message sent to offscreen document');
3531
+ // CRITICAL: Clear pending frame queue to prevent infinite retry loop
3532
+ if (stoppedSessionId && pendingFrameQueue.has(stoppedSessionId)) {
3533
+ // console.log(`[FrameQueue] Clearing ${pendingFrameQueue.get(stoppedSessionId).length} pending frames for stopped session ${stoppedSessionId}`);
3534
+ pendingFrameQueue.delete(stoppedSessionId);
3535
+ }
3536
+
3537
+ // console.log('Stop message sent to offscreen document');
3538
+
3539
+ // Notify popup to refresh recordings list
3540
+ chrome.runtime.sendMessage({
3541
+ action: 'recordingStopped',
3542
+ sessionId: stoppedSessionId
3543
+ }).catch(() => {
3544
+ // Popup might not be open, that's okay
3545
+ // console.log('[Background] Popup not available to notify of recording stop');
3546
+ });
3547
+
3548
+ // Format duration as human-readable string (e.g., "1m 30s" or "45s")
3549
+ const formatDurationForDisplay = (ms) => {
3550
+ const seconds = Math.floor(ms / 1000);
3551
+ if (seconds < 60) {
3552
+ return `${seconds}s`;
3553
+ }
3554
+ const minutes = Math.floor(seconds / 60);
3555
+ const remainingSeconds = seconds % 60;
3556
+ return remainingSeconds > 0 ? `${minutes}m ${remainingSeconds}s` : `${minutes}m`;
3557
+ };
3558
+
3559
+ // Return success with sessionId, frameCount and duration for popup display
3560
+ return {
3561
+ success: true,
3562
+ sessionId: stoppedSessionId,
3563
+ frameCount: recordedFrameCount,
3564
+ duration: formatDurationForDisplay(recordedDuration)
3565
+ };
3062
3566
  } catch (error) {
3063
3567
  console.error('Error in stopRecording:', error);
3064
3568
  // Clear session cache even on error
3065
3569
  const errorRecordingTabId = recordingTabId; // Store for cleanup
3570
+
3571
+ // CRITICAL FIX: Clean up console interceptor even on error
3572
+ if (errorRecordingTabId) {
3573
+ try {
3574
+ // console.log('[Background] Emergency cleanup of console interceptor for tab:', errorRecordingTabId);
3575
+ await stopCapturingLogs(errorRecordingTabId);
3576
+ } catch (cleanupError) {
3577
+ console.error('[Background] Failed to cleanup console interceptor during error handling:', cleanupError);
3578
+ }
3579
+ }
3580
+
3066
3581
  currentSession = null;
3067
3582
  currentOwnerId = null;
3068
3583
  isCurrentlyRecording = false;
@@ -3081,238 +3596,27 @@ async function stopRecording() {
3081
3596
 
3082
3597
 
3083
3598
  // Capture console logs from the recording tab
3599
+ // REFACTORED: Now uses shared console-interception-library.js
3084
3600
  async function startCapturingLogs(tabId) {
3085
- // Check if this tab allows content script injection
3086
- const tab = await chrome.tabs.get(tabId);
3087
- if (!tab.url || tab.url.startsWith('chrome://') || tab.url.startsWith('chrome-extension://') || tab.url.startsWith('moz-extension://')) {
3088
- console.log('Cannot inject console logger into restricted URL:', tab.url);
3089
- console.warn('WARNING: Console logs cannot be captured on restricted pages (chrome://, chrome-extension://, etc.)');
3090
- console.warn('To capture console logs, please navigate to a regular web page before recording.');
3091
- return false;
3092
- }
3093
-
3094
- // First inject the main world console interceptor
3095
- try {
3096
- const results = await chrome.scripting.executeScript({
3097
- target: { tabId: tabId },
3098
- world: 'MAIN',
3099
- func: () => {
3100
- // Check if we've already overridden console methods
3101
- if (window.__chromePilotConsoleOverridden) {
3102
- console.log('[Chrome Debug] Console already overridden, skipping');
3103
- return 'already_installed';
3104
- }
3105
- window.__chromePilotConsoleOverridden = true;
3106
- console.log('[Chrome Debug] Installing console interceptor');
3107
-
3108
- // Override console methods to capture logs - complete coverage
3109
- const originalLog = console.log;
3110
- const originalError = console.error;
3111
- const originalWarn = console.warn;
3112
- const originalInfo = console.info;
3113
- const originalDebug = console.debug;
3114
- const originalTrace = console.trace;
3115
- const originalTable = console.table;
3116
- const originalDir = console.dir;
3117
- const originalGroup = console.group;
3118
- const originalGroupEnd = console.groupEnd;
3119
- const originalTime = console.time;
3120
- const originalTimeEnd = console.timeEnd;
3121
- const originalCount = console.count;
3122
-
3123
- const sendLog = (level, args) => {
3124
- try {
3125
- // Pre-serialize arguments to strings to avoid structured clone errors
3126
- const serializedArgs = args.map(arg => {
3127
- try {
3128
- if (arg === null) return 'null';
3129
- if (arg === undefined) return 'undefined';
3130
- if (typeof arg === 'function') return '[Function: ' + (arg.name || 'anonymous') + ']';
3131
- if (arg instanceof Element) return '[DOM Element: ' + arg.tagName + ']';
3132
- if (typeof arg === 'object') {
3133
- // Try to stringify, but limit depth to avoid circular references
3134
- let stringified = JSON.stringify(arg, null, 2);
3135
-
3136
- // Check if this looks like a base64 image and truncate it
3137
- if (stringified.includes('data:image/') && stringified.length > 1000) {
3138
- const match = stringified.match(/data:image\/([^;]+);base64,(.{0,100})/);
3139
- if (match) {
3140
- return `[Base64 Image: ${match[1]}, ${stringified.length} bytes total, truncated...]`;
3141
- }
3142
- }
3143
-
3144
- // Truncate any extremely large strings
3145
- const maxLength = 5000;
3146
- if (stringified.length > maxLength) {
3147
- return stringified.substring(0, maxLength) + `... [TRUNCATED: ${stringified.length} total bytes]`;
3148
- }
3149
-
3150
- return stringified;
3151
- }
3152
-
3153
- // Also check for base64 strings directly
3154
- const strValue = String(arg);
3155
- if (strValue.includes('data:image/') && strValue.length > 1000) {
3156
- const match = strValue.match(/data:image\/([^;]+);base64,(.{0,100})/);
3157
- if (match) {
3158
- return `[Base64 Image: ${match[1]}, ${strValue.length} bytes total, truncated...]`;
3159
- }
3160
- }
3161
-
3162
- // Truncate any extremely large strings
3163
- if (strValue.length > 5000) {
3164
- return strValue.substring(0, 5000) + `... [TRUNCATED: ${strValue.length} total bytes]`;
3165
- }
3166
-
3167
- return strValue;
3168
- } catch (e) {
3169
- return '[Object: could not serialize]';
3170
- }
3171
- });
3172
-
3173
- // Post message to content script
3174
- window.postMessage({
3175
- type: 'chrome-pilot-console-log',
3176
- log: {
3177
- level,
3178
- message: serializedArgs.join(' '),
3179
- timestamp: Date.now()
3180
- }
3181
- }, '*');
3182
- } catch (e) {
3183
- // Ignore errors when sending logs
3184
- }
3185
- };
3186
-
3187
- console.log = (...args) => {
3188
- sendLog('log', args);
3189
- originalLog.apply(console, args);
3190
- };
3191
-
3192
- console.error = (...args) => {
3193
- sendLog('error', args);
3194
- originalError.apply(console, args);
3195
- };
3196
-
3197
- console.warn = (...args) => {
3198
- sendLog('warn', args);
3199
- originalWarn.apply(console, args);
3200
- };
3201
-
3202
- console.info = (...args) => {
3203
- sendLog('info', args);
3204
- originalInfo.apply(console, args);
3205
- };
3206
-
3207
- console.debug = (...args) => {
3208
- sendLog('debug', args);
3209
- originalDebug.apply(console, args);
3210
- };
3211
-
3212
- console.trace = (...args) => {
3213
- sendLog('trace', args);
3214
- originalTrace.apply(console, args);
3215
- };
3216
-
3217
- console.table = (...args) => {
3218
- sendLog('table', args);
3219
- originalTable.apply(console, args);
3220
- };
3221
-
3222
- console.dir = (...args) => {
3223
- sendLog('dir', args);
3224
- originalDir.apply(console, args);
3225
- };
3226
-
3227
- console.group = (...args) => {
3228
- sendLog('group', args);
3229
- originalGroup.apply(console, args);
3230
- };
3231
-
3232
- console.groupEnd = (...args) => {
3233
- sendLog('groupEnd', args);
3234
- originalGroupEnd.apply(console, args);
3235
- };
3236
-
3237
- console.time = (...args) => {
3238
- sendLog('time', args);
3239
- originalTime.apply(console, args);
3240
- };
3241
-
3242
- console.timeEnd = (...args) => {
3243
- sendLog('timeEnd', args);
3244
- originalTimeEnd.apply(console, args);
3245
- };
3246
-
3247
- console.count = (...args) => {
3248
- sendLog('count', args);
3249
- originalCount.apply(console, args);
3250
- };
3251
-
3252
- return 'console_installed';
3253
- }
3254
- });
3255
-
3256
- console.log('[Console Injection] MAIN world script injected successfully:', results);
3257
-
3258
- // Then inject a content script to relay messages from main world to background
3259
- await chrome.scripting.executeScript({
3260
- target: { tabId: tabId },
3261
- func: () => {
3262
- // Listen for messages from main world
3263
- window.addEventListener('message', (event) => {
3264
- if (event.data && event.data.type === 'chrome-pilot-console-log') {
3265
- // Forward to background script
3266
- chrome.runtime.sendMessage({
3267
- action: 'consoleLog',
3268
- log: event.data.log
3269
- });
3270
- }
3271
- });
3272
- }
3273
- });
3601
+ // console.log('[Screen Recording] Starting console interception using shared library');
3602
+ return await self.ConsoleInterceptionLibrary.startConsoleInterception(tabId, SCREEN_RECORDING_CONSOLE_CONFIG);
3603
+ }
3274
3604
 
3275
- console.log('[Console Injection] Content script relay injected successfully');
3276
- return true; // Successfully started capturing logs
3277
- } catch (error) {
3278
- console.error('[Console Injection] Failed to inject console interceptor:', error);
3279
- console.error('[Console Injection] Error details:', error.message, error.stack);
3280
- return false; // Failed to inject console interceptor
3281
- }
3605
+ // REFACTORED: Now uses shared console-interception-library.js via proper namespace
3606
+ async function stopCapturingLogs(tabId) {
3607
+ // console.log('[Screen Recording] Stopping console interception using shared library');
3608
+ return await self.ConsoleInterceptionLibrary.stopConsoleInterception(tabId, SCREEN_RECORDING_CONSOLE_CONFIG);
3282
3609
  }
3283
3610
 
3284
3611
  // Workflow Recording Functions
3285
3612
  async function startWorkflowRecording(tabId, includeLogsInExport, sessionName = null, screenshotSettings = null) {
3286
3613
  try {
3287
- console.log('Starting workflow recording for tab:', tabId);
3288
-
3289
- // STEP 1: Check license and usage limits BEFORE recording (same pattern as startRecording)
3290
- console.log('[License] Checking license before workflow recording...');
3291
- const licenseCheck = await LicenseHelper.checkLicenseBeforeRecording();
3292
- console.log('[License] License check result:', licenseCheck);
3293
-
3294
- if (!licenseCheck.allowed) {
3295
- // Show notification to user (same pattern as screen recording)
3296
- chrome.notifications.create({
3297
- type: 'basic',
3298
- iconUrl: chrome.runtime.getURL('icon128.png'),
3299
- title: 'Recording Limit Reached',
3300
- message: licenseCheck.message || 'Daily limit reached. Upgrade to Pro for unlimited workflow recordings.',
3301
- buttons: [{ title: 'Upgrade to Pro' }],
3302
- priority: 2
3303
- });
3304
-
3305
- throw new Error(licenseCheck.message || 'Workflow recording not allowed');
3306
- }
3307
-
3308
- // Store userId for later usage tracking
3309
- const userId = licenseCheck.userId;
3310
- workflowUserIds.set(tabId, userId);
3614
+ // console.log('Starting workflow recording for tab:', tabId);
3311
3615
 
3312
3616
  // Check if this tab allows content script injection
3313
3617
  const tab = await chrome.tabs.get(tabId);
3314
3618
  if (!tab.url || tab.url.startsWith('chrome://') || tab.url.startsWith('chrome-extension://') || tab.url.startsWith('moz-extension://')) {
3315
- console.log('Cannot start workflow recording on restricted URL:', tab.url);
3619
+ // console.log('Cannot start workflow recording on restricted URL:', tab.url);
3316
3620
  throw new Error('Cannot record workflows on restricted pages (chrome:// URLs)');
3317
3621
  }
3318
3622
 
@@ -3326,107 +3630,18 @@ async function startWorkflowRecording(tabId, includeLogsInExport, sessionName =
3326
3630
  const workflowId = `workflow_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
3327
3631
  await chrome.storage.local.set({ currentWorkflowId: workflowId });
3328
3632
 
3329
- // Clear any existing logs for this tab
3633
+ // Clear any existing logs for this tab and initialize race-safe buffer
3330
3634
  await chrome.storage.session.set({ [`workflow_${tabId}`]: [] });
3331
-
3332
- // Inject main world console interceptor (similar to frame recording)
3333
- await chrome.scripting.executeScript({
3334
- target: { tabId: tabId },
3335
- world: 'MAIN',
3336
- func: () => {
3337
- // Check if already injected
3338
- if (window.__chromePilotWorkflowConsoleOverridden) {
3339
- return;
3340
- }
3341
- window.__chromePilotWorkflowConsoleOverridden = true;
3342
-
3343
- // Override console methods to capture logs
3344
- const originalLog = console.log;
3345
- const originalError = console.error;
3346
- const originalWarn = console.warn;
3347
- const originalInfo = console.info;
3348
-
3349
- const sendLog = (level, args) => {
3350
- try {
3351
- // Pre-serialize arguments to strings to avoid structured clone errors
3352
- const serializedArgs = args.map(arg => {
3353
- try {
3354
- if (arg === null) return 'null';
3355
- if (arg === undefined) return 'undefined';
3356
- if (typeof arg === 'function') return '[Function: ' + (arg.name || 'anonymous') + ']';
3357
- if (arg instanceof Element) return '[DOM Element: ' + arg.tagName + ']';
3358
- if (typeof arg === 'object') {
3359
- // Try to stringify, but limit depth to avoid circular references
3360
- return JSON.stringify(arg, null, 2);
3361
- }
3362
- return String(arg);
3363
- } catch (e) {
3364
- return '[Object: could not serialize]';
3365
- }
3366
- });
3367
-
3368
- // Post message to content script
3369
- window.postMessage({
3370
- type: 'chrome-pilot-workflow-console-log',
3371
- log: {
3372
- level,
3373
- message: serializedArgs.join(' '),
3374
- timestamp: Date.now()
3375
- }
3376
- }, '*');
3377
- } catch (e) {
3378
- // Ignore errors when sending logs
3379
- }
3380
- };
3381
-
3382
- console.log = (...args) => {
3383
- sendLog('log', args);
3384
- originalLog.apply(console, args);
3385
- };
3386
-
3387
- console.error = (...args) => {
3388
- sendLog('error', args);
3389
- originalError.apply(console, args);
3390
- };
3391
-
3392
- console.warn = (...args) => {
3393
- sendLog('warn', args);
3394
- originalWarn.apply(console, args);
3395
- };
3396
-
3397
- console.info = (...args) => {
3398
- sendLog('info', args);
3399
- originalInfo.apply(console, args);
3400
- };
3401
-
3402
- // Also capture window errors
3403
- window.addEventListener('error', (event) => {
3404
- sendLog('exception', [`${event.message} at ${event.filename}:${event.lineno}:${event.colno}`]);
3405
- });
3406
-
3407
- window.addEventListener('unhandledrejection', (event) => {
3408
- sendLog('exception', [`Unhandled Promise Rejection: ${event.reason}`]);
3409
- });
3410
- }
3411
- });
3412
-
3413
- // Inject content script to relay messages and handle workflow recording
3414
- await chrome.scripting.executeScript({
3415
- target: { tabId: tabId },
3416
- func: (includeLogsInExport) => {
3417
- // Listen for console log messages from main world
3418
- window.addEventListener('message', (event) => {
3419
- if (event.data && event.data.type === 'chrome-pilot-workflow-console-log') {
3420
- // Forward to background script
3421
- chrome.runtime.sendMessage({
3422
- action: 'workflowConsoleLog',
3423
- log: event.data.log
3424
- });
3425
- }
3426
- });
3427
- },
3428
- args: [includeLogsInExport]
3429
- });
3635
+
3636
+ // Initialize WorkflowLogBuffer for race-safe log handling
3637
+ if (includeLogsInExport) {
3638
+ const buffer = new WorkflowLogBuffer(tabId);
3639
+ workflowLogBuffers.set(tabId, buffer);
3640
+ // console.log(`[Workflow] Initialized WorkflowLogBuffer for tab ${tabId}`);
3641
+ }
3642
+
3643
+ // REFACTORED: Use shared console interception library via proper namespace (replaces 150+ lines of duplicate code)
3644
+ await self.ConsoleInterceptionLibrary.startConsoleInterception(tabId, WORKFLOW_RECORDING_CONSOLE_CONFIG);
3430
3645
 
3431
3646
  // Ensure content script is injected
3432
3647
  try {
@@ -3440,16 +3655,18 @@ async function startWorkflowRecording(tabId, includeLogsInExport, sessionName =
3440
3655
  });
3441
3656
  } catch (e) {
3442
3657
  // Content script might already be injected
3443
- console.log('Content script injection attempt:', e.message);
3658
+ // console.log('Content script injection attempt:', e.message);
3444
3659
  }
3445
3660
 
3446
3661
  // Tell the content script to start recording
3662
+ // Include tabId so content script can use it when stopping recording
3447
3663
  await chrome.tabs.sendMessage(tabId, {
3448
3664
  action: 'startWorkflowRecording',
3449
- screenshotSettings: screenshotSettings
3665
+ screenshotSettings: screenshotSettings,
3666
+ tabId: tabId
3450
3667
  });
3451
3668
 
3452
- console.log('Workflow recording started successfully');
3669
+ // console.log('Workflow recording started successfully');
3453
3670
  } catch (error) {
3454
3671
  console.error('Error starting workflow recording:', error);
3455
3672
  throw error;
@@ -3458,58 +3675,84 @@ async function startWorkflowRecording(tabId, includeLogsInExport, sessionName =
3458
3675
 
3459
3676
  async function stopWorkflowRecording(tabId) {
3460
3677
  try {
3461
- console.log('Stopping workflow recording for tab:', tabId);
3678
+ // console.log('Stopping workflow recording for tab:', tabId);
3462
3679
 
3463
3680
  // Check if recording was active
3464
3681
  if (!workflowRecordingTabs.has(tabId)) {
3465
3682
  throw new Error('No workflow recording active for this tab');
3466
3683
  }
3467
3684
 
3468
- // Get workflow from content script
3685
+ // Get workflow from content script using a different action to avoid circular dependency
3686
+ // (mini menu also sends 'stopWorkflowRecording' to background, creating confusion)
3469
3687
  const response = await chrome.tabs.sendMessage(tabId, {
3470
- action: 'stopWorkflowRecording'
3688
+ action: 'getWorkflowData'
3471
3689
  });
3472
-
3690
+
3473
3691
  if (!response || !response.success) {
3474
3692
  throw new Error('Failed to get workflow from content script');
3475
3693
  }
3476
-
3694
+
3695
+ // CRITICAL FIX: Restore original console methods and remove event listeners
3696
+ try {
3697
+ // console.log('[Workflow] Stopping console log capture for tab:', tabId);
3698
+ await stopCapturingWorkflowLogs(tabId);
3699
+ } catch (cleanupError) {
3700
+ console.error('[Workflow] Failed to cleanup console interceptor:', cleanupError);
3701
+ // Continue with stop process even if cleanup fails
3702
+ }
3703
+
3477
3704
  // Handle both old format (array) and new format (object with actions and functionTraces)
3478
3705
  let workflowData = response.workflow || [];
3479
3706
  let workflow = Array.isArray(workflowData) ? workflowData : (workflowData.actions || []);
3480
3707
  let functionTraces = Array.isArray(workflowData) ? [] : (workflowData.functionTraces || []);
3481
3708
 
3482
- console.log(`[Background] Received ${workflow.length} actions and ${functionTraces.length} function traces`);
3483
- console.log('[SCREENSHOT-DEBUG] background.stopWorkflowRecording - Checking actions for screenshot_data');
3709
+ // console.log(`[Background] Received ${workflow.length} actions and ${functionTraces.length} function traces`);
3710
+ // console.log('[SCREENSHOT-DEBUG] background.stopWorkflowRecording - Checking actions for screenshot_data');
3484
3711
 
3485
3712
  const actionsWithScreenshots = workflow.filter(a => a.screenshot_data);
3486
- console.log('[SCREENSHOT-DEBUG] background.stopWorkflowRecording - Actions WITH screenshot_data:', actionsWithScreenshots.length);
3713
+ // console.log('[SCREENSHOT-DEBUG] background.stopWorkflowRecording - Actions WITH screenshot_data:', actionsWithScreenshots.length);
3487
3714
 
3488
3715
  workflow.forEach((action, index) => {
3489
- console.log(`[SCREENSHOT-DEBUG] background.stopWorkflowRecording - Action ${index}:`, {
3490
- type: action.type,
3491
- hasScreenshotData: !!action.screenshot_data,
3492
- screenshotDataLength: action.screenshot_data?.length,
3493
- screenshotPreview: action.screenshot_data?.substring(0, 50)
3494
- });
3716
+ // console.log(`[SCREENSHOT-DEBUG] background.stopWorkflowRecording - Action ${index}:`, {
3717
+ // type: action.type,
3718
+ // hasScreenshotData: !!action.screenshot_data,
3719
+ // screenshotDataLength: action.screenshot_data?.length,
3720
+ // screenshotPreview: action.screenshot_data?.substring(0, 50)
3721
+ // });
3495
3722
  });
3496
3723
 
3497
- // If logs should be included, get them from session storage
3724
+ // If logs should be included, flush buffer and get them from session storage
3498
3725
  if (workflowIncludeLogs.get(tabId)) {
3726
+ // CRITICAL: Flush any buffered logs before retrieval to ensure all logs are captured
3727
+ const buffer = workflowLogBuffers.get(tabId);
3728
+ if (buffer) {
3729
+ await buffer.flush();
3730
+ // console.log(`[Workflow] Flushed buffer before retrieval, stats:`, buffer.getStats());
3731
+ }
3732
+
3499
3733
  const result = await chrome.storage.session.get(`workflow_${tabId}`);
3500
3734
  const logs = result[`workflow_${tabId}`] || [];
3501
3735
 
3502
- // Associate logs with workflow actions
3736
+ // Associate logs with workflow actions using NON-OVERLAPPING windows
3737
+ // Each log appears in exactly ONE action to avoid duplicates
3503
3738
  workflow = workflow.map((action, index) => {
3504
- // Find logs that occurred around this action
3739
+ // Calculate non-overlapping time window:
3740
+ // - Each action owns logs from ITS timestamp until the NEXT action's timestamp
3741
+ // - First action also includes 500ms before it
3742
+ // - Last action includes 5000ms after it
3743
+ const windowStart = index === 0
3744
+ ? action.timestamp - 500 // First action: include 500ms before
3745
+ : action.timestamp; // Other actions: start at this action's timestamp
3746
+
3747
+ const windowEnd = index < workflow.length - 1
3748
+ ? workflow[index + 1].timestamp // Until next action's timestamp
3749
+ : action.timestamp + 5000; // Last action: 5000ms after
3750
+
3751
+ // Use >= for start and < for end to ensure non-overlapping windows
3505
3752
  const actionLogs = logs.filter(log => {
3506
- // Get time window
3507
- const prevActionTime = index > 0 ? workflow[index - 1].timestamp : action.timestamp - 500;
3508
- const nextActionTime = index < workflow.length - 1 ? workflow[index + 1].timestamp : action.timestamp + 5000;
3509
-
3510
- return log.timestamp > prevActionTime && log.timestamp < nextActionTime;
3753
+ return log.timestamp >= windowStart && log.timestamp < windowEnd;
3511
3754
  });
3512
-
3755
+
3513
3756
  if (actionLogs.length > 0) {
3514
3757
  return { ...action, logs: actionLogs };
3515
3758
  }
@@ -3521,30 +3764,48 @@ async function stopWorkflowRecording(tabId) {
3521
3764
  const sessionName = workflowSessionNames.get(tabId);
3522
3765
  const screenshotSettings = workflowScreenshotSettings.get(tabId);
3523
3766
  const includeLogs = workflowIncludeLogs.get(tabId) || false;
3524
- const userId = workflowUserIds.get(tabId); // Get userId for usage tracking
3525
3767
 
3526
3768
  // Clean up
3527
3769
  workflowRecordingTabs.delete(tabId);
3528
3770
  workflowIncludeLogs.delete(tabId);
3529
3771
  workflowScreenshotSettings.delete(tabId);
3530
3772
  workflowSessionNames.delete(tabId);
3531
- workflowUserIds.delete(tabId); // Clean up userId tracking
3773
+
3774
+ // Clean up WorkflowLogBuffer
3775
+ const bufferToCleanup = workflowLogBuffers.get(tabId);
3776
+ if (bufferToCleanup) {
3777
+ await bufferToCleanup.cleanup();
3778
+ workflowLogBuffers.delete(tabId);
3779
+ // console.log(`[Workflow] Cleaned up WorkflowLogBuffer for tab ${tabId}`);
3780
+ }
3781
+
3532
3782
  await chrome.storage.session.remove(`workflow_${tabId}`);
3533
-
3534
- console.log('Workflow recording stopped, returning workflow:', workflow);
3783
+
3784
+ // Notify popup and other extension components that recording has stopped
3785
+ // This is critical for popup UI sync when stopping from mini-menu
3786
+ await chrome.storage.local.set({
3787
+ workflowRecording: false,
3788
+ workflowStartTime: null
3789
+ });
3790
+
3791
+ // console.log('Workflow recording stopped, returning workflow:', workflow);
3535
3792
 
3536
3793
  // Try to save to server
3537
3794
  try {
3538
3795
  const serverPorts = CONFIG_PORTS.slice(0, 5); // Use first 5 configured ports for workflow recording
3539
3796
  let serverResult = null;
3540
-
3797
+
3541
3798
  // Get current URL and title
3542
3799
  const tab = await chrome.tabs.get(tabId);
3543
- const sessionId = `workflow_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
3800
+
3801
+ // Retrieve the workflow ID that was generated at start
3802
+ const storedData = await chrome.storage.local.get(['currentWorkflowId']);
3803
+ const sessionId = storedData.currentWorkflowId || `workflow_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
3804
+ // console.log('[Workflow] Using sessionId for save:', sessionId);
3544
3805
 
3545
3806
  for (const port of serverPorts) {
3546
3807
  try {
3547
- console.log(`[Workflow] Attempting to save to server on port ${port}...`);
3808
+ // console.log(`[Workflow] Attempting to save to server on port ${port}...`);
3548
3809
 
3549
3810
  const payloadData = {
3550
3811
  sessionId: sessionId,
@@ -3558,19 +3819,19 @@ async function stopWorkflowRecording(tabId) {
3558
3819
  functionTraces: functionTraces // Include function execution traces
3559
3820
  };
3560
3821
 
3561
- console.log('[SCREENSHOT-DEBUG] background.stopWorkflowRecording - HTTP POST payload being prepared');
3562
- console.log('[SCREENSHOT-DEBUG] background.stopWorkflowRecording - Payload actions count:', payloadData.actions.length);
3822
+ // console.log('[SCREENSHOT-DEBUG] background.stopWorkflowRecording - HTTP POST payload being prepared');
3823
+ // console.log('[SCREENSHOT-DEBUG] background.stopWorkflowRecording - Payload actions count:', payloadData.actions.length);
3563
3824
 
3564
3825
  const payloadActionsWithScreenshots = payloadData.actions.filter(a => a.screenshot_data);
3565
- console.log('[SCREENSHOT-DEBUG] background.stopWorkflowRecording - Payload actions WITH screenshot_data:', payloadActionsWithScreenshots.length);
3826
+ // console.log('[SCREENSHOT-DEBUG] background.stopWorkflowRecording - Payload actions WITH screenshot_data:', payloadActionsWithScreenshots.length);
3566
3827
 
3567
3828
  payloadData.actions.forEach((action, index) => {
3568
- console.log(`[SCREENSHOT-DEBUG] background.stopWorkflowRecording - Payload Action ${index}:`, {
3569
- type: action.type,
3570
- hasScreenshotData: !!action.screenshot_data,
3571
- screenshotDataLength: action.screenshot_data?.length,
3572
- screenshotPreview: action.screenshot_data?.substring(0, 50)
3573
- });
3829
+ // console.log(`[SCREENSHOT-DEBUG] background.stopWorkflowRecording - Payload Action ${index}:`, {
3830
+ // type: action.type,
3831
+ // hasScreenshotData: !!action.screenshot_data,
3832
+ // screenshotDataLength: action.screenshot_data?.length,
3833
+ // screenshotPreview: action.screenshot_data?.substring(0, 50)
3834
+ // });
3574
3835
  });
3575
3836
 
3576
3837
  const response = await fetch(`http://localhost:${port}/chromedebug/workflow-recording`, {
@@ -3583,31 +3844,26 @@ async function stopWorkflowRecording(tabId) {
3583
3844
 
3584
3845
  if (response.ok) {
3585
3846
  serverResult = await response.json();
3586
- console.log(`[Workflow] Successfully saved to server on port ${port}`, serverResult);
3587
-
3588
- // STEP 2: Track usage AFTER successful workflow recording completion (same pattern as stopRecording)
3589
- if (userId) {
3590
- console.log('[License] Tracking usage after workflow recording completion...');
3591
- const usageResult = await LicenseHelper.trackUsageAfterRecording(userId);
3592
- console.log('[License] Usage tracking result:', usageResult);
3593
- }
3594
-
3847
+ // console.log(`[Workflow] Successfully saved to server on port ${port}`, serverResult);
3595
3848
  break;
3596
3849
  } else {
3597
- console.log(`[Workflow] Failed on port ${port}: ${response.status} ${response.statusText}`);
3850
+ // console.log(`[Workflow] Failed on port ${port}: ${response.status} ${response.statusText}`);
3598
3851
  const errorText = await response.text();
3599
- console.log(`[Workflow] Error response:`, errorText);
3852
+ // console.log(`[Workflow] Error response:`, errorText);
3600
3853
  }
3601
3854
  } catch (error) {
3602
- console.log(`Failed to save to server on port ${port}:`, error.message);
3855
+ // console.log(`Failed to save to server on port ${port}:`, error.message);
3603
3856
  }
3604
3857
  }
3605
3858
 
3606
3859
  if (serverResult) {
3860
+ // Clean up currentWorkflowId after successful save
3861
+ await chrome.storage.local.remove(['currentWorkflowId']);
3862
+
3607
3863
  return {
3608
3864
  success: true,
3609
3865
  workflow: {
3610
- sessionId: sessionId,
3866
+ sessionId: serverResult.workflowId || serverResult.sessionId || sessionId,
3611
3867
  url: tab.url,
3612
3868
  title: tab.title,
3613
3869
  actions: workflow,
@@ -3617,17 +3873,36 @@ async function stopWorkflowRecording(tabId) {
3617
3873
  serverResult: serverResult
3618
3874
  };
3619
3875
  } else {
3876
+ // Server not available - clean up but return local sessionId
3877
+ await chrome.storage.local.remove(['currentWorkflowId']);
3878
+
3620
3879
  return {
3621
3880
  success: true,
3622
- workflow: workflow,
3881
+ workflow: {
3882
+ sessionId: sessionId,
3883
+ url: tab.url,
3884
+ title: tab.title,
3885
+ actions: workflow,
3886
+ logs: []
3887
+ },
3623
3888
  savedToServer: false
3624
3889
  };
3625
3890
  }
3626
3891
  } catch (error) {
3627
3892
  console.error('Error saving workflow:', error);
3893
+ // Clean up on error too
3894
+ await chrome.storage.local.remove(['currentWorkflowId']);
3895
+
3896
+ // Return structured response even on error
3628
3897
  return {
3629
3898
  success: true,
3630
- workflow: workflow,
3899
+ workflow: {
3900
+ sessionId: sessionId,
3901
+ url: tab?.url || 'unknown',
3902
+ title: tab?.title || 'Untitled',
3903
+ actions: workflow,
3904
+ logs: []
3905
+ },
3631
3906
  savedToServer: false,
3632
3907
  error: error.message
3633
3908
  };
@@ -3638,6 +3913,13 @@ async function stopWorkflowRecording(tabId) {
3638
3913
  }
3639
3914
  }
3640
3915
 
3916
+ // CRITICAL FIX: Stop workflow console log capture and restore original methods
3917
+ // REFACTORED: Now uses shared console-interception-library.js via proper namespace
3918
+ async function stopCapturingWorkflowLogs(tabId) {
3919
+ // console.log('[Workflow Recording] Stopping console interception using shared library');
3920
+ return await self.ConsoleInterceptionLibrary.stopConsoleInterception(tabId, WORKFLOW_RECORDING_CONSOLE_CONFIG);
3921
+ }
3922
+
3641
3923
  // Delete recording from server
3642
3924
  async function deleteRecordingFromServer(recordingId, sendResponse) {
3643
3925
  try {
@@ -3673,7 +3955,7 @@ async function deleteRecordingFromServer(recordingId, sendResponse) {
3673
3955
 
3674
3956
  if (deleteResult.success) {
3675
3957
  deleted = true;
3676
- console.log('Recording deleted successfully');
3958
+ // console.log('Recording deleted successfully');
3677
3959
  sendResponse({ success: true });
3678
3960
  return;
3679
3961
  } else {
@@ -3695,15 +3977,30 @@ async function retryPendingFrames(sessionId) {
3695
3977
  const pending = pendingFrameQueue.get(sessionId);
3696
3978
  if (!pending || pending.length === 0) return;
3697
3979
 
3698
- console.log(`[FrameQueue] Retrying ${pending.length} pending frame batches for session ${sessionId}`);
3980
+ // console.log(`[FrameQueue] Retrying ${pending.length} pending frame batches for session ${sessionId}`);
3981
+
3982
+ // Check if this is still the active recording session
3983
+ if (currentSession?.sessionId !== sessionId) {
3984
+ // console.log(`[FrameQueue] Session ${sessionId} is no longer active, clearing pending frames`);
3985
+ pendingFrameQueue.delete(sessionId);
3986
+ return;
3987
+ }
3699
3988
 
3700
3989
  // Validate session now
3701
3990
  if (sessionManager) {
3702
3991
  const validationResult = await sessionManager.isSessionValid(sessionId);
3703
3992
  if (!validationResult.valid) {
3704
3993
  console.warn(`[FrameQueue] Session still invalid during retry: ${validationResult.reason}`);
3705
- // Try again after another delay
3706
- setTimeout(() => retryPendingFrames(sessionId), 200);
3994
+ // Try again after another delay (max 3 retries)
3995
+ const retryCount = pending[0]?.retryCount || 0;
3996
+ if (retryCount < 3) {
3997
+ // Mark all batches with retry count
3998
+ pending.forEach(batch => batch.retryCount = retryCount + 1);
3999
+ setTimeout(() => retryPendingFrames(sessionId), 200);
4000
+ } else {
4001
+ console.error(`[FrameQueue] Max retries (3) exceeded for session ${sessionId}, clearing queue`);
4002
+ pendingFrameQueue.delete(sessionId);
4003
+ }
3707
4004
  return;
3708
4005
  }
3709
4006
  }
@@ -3716,12 +4013,12 @@ async function retryPendingFrames(sessionId) {
3716
4013
  await handleFrameBatch({ sessionId, frames }, true); // skipValidation=true
3717
4014
  }
3718
4015
 
3719
- console.log(`[FrameQueue] Successfully processed ${batches.length} pending batches`);
4016
+ // console.log(`[FrameQueue] Successfully processed ${batches.length} pending batches`);
3720
4017
  }
3721
4018
 
3722
4019
  // Handle frame batches from frame capture
3723
4020
  async function handleFrameBatch(batchData, skipValidation = false) {
3724
- console.log(`Processing frame batch for session ${batchData.sessionId}`);
4021
+ // console.log(`Processing frame batch for session ${batchData.sessionId}`);
3725
4022
 
3726
4023
  try {
3727
4024
  const sessionId = batchData.sessionId;
@@ -3775,7 +4072,7 @@ async function handleFrameBatch(batchData, skipValidation = false) {
3775
4072
  frameCounter.set(sessionId, currentFrameIndex);
3776
4073
  }
3777
4074
 
3778
- console.log(`Assigning frame indices ${frameCounter.get(sessionId) - indexedFrames.length} to ${currentFrameIndex - 1}`);
4075
+ // console.log(`Assigning frame indices ${frameCounter.get(sessionId) - indexedFrames.length} to ${currentFrameIndex - 1}`);
3779
4076
 
3780
4077
  // Find available server port using settings
3781
4078
  const ports = await getServerPorts();
@@ -3801,7 +4098,7 @@ async function handleFrameBatch(batchData, skipValidation = false) {
3801
4098
 
3802
4099
  if (uploadResponse.ok) {
3803
4100
  const result = await uploadResponse.json();
3804
- console.log(`Frame batch uploaded successfully to port ${port}:`, result);
4101
+ // console.log(`Frame batch uploaded successfully to port ${port}:`, result);
3805
4102
  recordingServerPort = port; // Store the successful port
3806
4103
  uploadSuccess = true;
3807
4104
 
@@ -3817,8 +4114,20 @@ async function handleFrameBatch(batchData, skipValidation = false) {
3817
4114
  // Append new frames
3818
4115
  sessionData.frames = sessionData.frames.concat(batchData.frames);
3819
4116
  await chrome.storage.local.set({ [storageKey]: sessionData });
3820
- console.log(`Saved ${batchData.frames.length} frames to local storage, total: ${sessionData.frames.length}`);
3821
-
4117
+ // console.log(`Saved ${batchData.frames.length} frames to local storage, total: ${sessionData.frames.length}`);
4118
+
4119
+ // Send frame count update to content script
4120
+ if (recordingTabId) {
4121
+ try {
4122
+ await chrome.tabs.sendMessage(recordingTabId, {
4123
+ action: 'updateFrameCount',
4124
+ frameCount: sessionData.frames.length
4125
+ });
4126
+ } catch (e) {
4127
+ // Tab might be closed, ignore
4128
+ }
4129
+ }
4130
+
3822
4131
  break;
3823
4132
  } else {
3824
4133
  const errorText = await uploadResponse.text();
@@ -3827,16 +4136,16 @@ async function handleFrameBatch(batchData, skipValidation = false) {
3827
4136
  }
3828
4137
  } catch (e) {
3829
4138
  lastError = e.message;
3830
- console.log(`Failed to upload frame batch to port ${port}:`, e.message);
4139
+ // console.log(`Failed to upload frame batch to port ${port}:`, e.message);
3831
4140
  }
3832
4141
  }
3833
4142
 
3834
4143
  if (!uploadSuccess) {
3835
- console.log(`No server available - checking if browser-only mode`);
4144
+ // console.log(`No server available - checking if browser-only mode`);
3836
4145
 
3837
4146
  // If in browser-only mode, store frames in IndexedDB
3838
4147
  if (serverMode === 'browser-only' && browserRecordingManager) {
3839
- console.log(`[Browser-Only] Storing ${indexedFrames.length} frames in IndexedDB`);
4148
+ // console.log(`[Browser-Only] Storing ${indexedFrames.length} frames in IndexedDB`);
3840
4149
  // Use the browser recording session ID, not the frame capture session ID
3841
4150
  const recordingSessionId = currentRecordingSessionId || batchData.sessionId;
3842
4151
  for (const frame of indexedFrames) {
@@ -3853,7 +4162,7 @@ async function handleFrameBatch(batchData, skipValidation = false) {
3853
4162
  absoluteTimestamp: frame.absoluteTimestamp // Don't use Date.now() fallback - preserve actual capture time
3854
4163
  });
3855
4164
  }
3856
- console.log(`[Browser-Only] Frames stored successfully in IndexedDB for session ${recordingSessionId}`);
4165
+ // console.log(`[Browser-Only] Frames stored successfully in IndexedDB for session ${recordingSessionId}`);
3857
4166
  } else {
3858
4167
  // Server mode but no server available - show error
3859
4168
  console.error(`CRITICAL: Failed to upload frame batch to any Chrome Debug server port. Last error: ${lastError}`);
@@ -3893,7 +4202,7 @@ async function getServerPorts() {
3893
4202
  // Try to discover the active server port
3894
4203
  const discoveredPort = await discoverActiveServerPort();
3895
4204
  if (discoveredPort) {
3896
- console.log(`Discovered Chrome Debug server on port ${discoveredPort}`);
4205
+ // console.log(`Discovered Chrome Debug server on port ${discoveredPort}`);
3897
4206
  return [discoveredPort, ...defaultPorts.filter(p => p !== discoveredPort)];
3898
4207
  }
3899
4208
 
@@ -3931,7 +4240,7 @@ async function discoverActiveServerPort() {
3931
4240
 
3932
4241
  // Handle frame capture session completion
3933
4242
  async function handleFrameCaptureComplete(sessionData) {
3934
- console.log(`Frame capture session ${sessionData.sessionId} completed with ${sessionData.totalFrames} frames`);
4243
+ // console.log(`Frame capture session ${sessionData.sessionId} completed with ${sessionData.totalFrames} frames`);
3935
4244
 
3936
4245
  try {
3937
4246
  // Store the tab ID and session ID before clearing
@@ -3949,7 +4258,7 @@ async function handleFrameCaptureComplete(sessionData) {
3949
4258
  let waitedTime = 0;
3950
4259
  let flushCompleted = false;
3951
4260
 
3952
- console.log('[Background] Waiting for LogBuffer flush completion...');
4261
+ // console.log('[Background] Waiting for LogBuffer flush completion...');
3953
4262
 
3954
4263
  // Poll for flush completion status
3955
4264
  while (waitedTime < maxWaitTime && !flushCompleted) {
@@ -3963,7 +4272,7 @@ async function handleFrameCaptureComplete(sessionData) {
3963
4272
  }
3964
4273
 
3965
4274
  if (flushCompleted) {
3966
- console.log(`[Background] LogBuffer flush completed after ${waitedTime}ms`);
4275
+ // console.log(`[Background] LogBuffer flush completed after ${waitedTime}ms`);
3967
4276
  } else {
3968
4277
  console.warn(`[Background] LogBuffer flush verification timed out after ${waitedTime}ms`);
3969
4278
  }
@@ -3971,9 +4280,9 @@ async function handleFrameCaptureComplete(sessionData) {
3971
4280
  // Retrieve buffered logs for this tab
3972
4281
  const logsResult = await chrome.storage.session.get(String(tabIdToNotify));
3973
4282
  bufferedLogs = logsResult[tabIdToNotify] || [];
3974
- console.log(`Retrieved ${bufferedLogs.length} buffered logs for post-processing`);
4283
+ // console.log(`Retrieved ${bufferedLogs.length} buffered logs for post-processing`);
3975
4284
  if (bufferedLogs.length > 0) {
3976
- console.log('First log:', bufferedLogs[0]);
4285
+ // console.log('First log:', bufferedLogs[0]);
3977
4286
  }
3978
4287
 
3979
4288
  // Clean up flush completion marker
@@ -3984,7 +4293,7 @@ async function handleFrameCaptureComplete(sessionData) {
3984
4293
  // Fallback: Try to read logs anyway
3985
4294
  const logsResult = await chrome.storage.session.get(String(tabIdToNotify));
3986
4295
  bufferedLogs = logsResult[tabIdToNotify] || [];
3987
- console.log(`Fallback: Retrieved ${bufferedLogs.length} buffered logs`);
4296
+ // console.log(`Fallback: Retrieved ${bufferedLogs.length} buffered logs`);
3988
4297
  }
3989
4298
 
3990
4299
  // Clear the buffered logs
@@ -3993,7 +4302,7 @@ async function handleFrameCaptureComplete(sessionData) {
3993
4302
  // Send logs to server for association with frames with retry logic
3994
4303
  // Skip server communication in browser-only mode (logs already in IndexedDB)
3995
4304
  if (bufferedLogs.length > 0 && serverMode !== 'browser-only') {
3996
- console.log(`[Server Mode] Associating ${bufferedLogs.length} logs with server...`);
4305
+ // console.log(`[Server Mode] Associating ${bufferedLogs.length} logs with server...`);
3997
4306
 
3998
4307
  // Retry logic to handle race conditions
3999
4308
  const maxRetries = 3;
@@ -4001,15 +4310,15 @@ async function handleFrameCaptureComplete(sessionData) {
4001
4310
  let success = false;
4002
4311
  let lastErrorDetails = null;
4003
4312
 
4004
- console.log(`[LOG-ASSOC-DEBUG] ========== STARTING LOG ASSOCIATION ==========`);
4005
- console.log(`[LOG-ASSOC-DEBUG] Session ID: ${sessionIdToUse}`);
4006
- console.log(`[LOG-ASSOC-DEBUG] Number of logs to associate: ${bufferedLogs.length}`);
4007
- console.log(`[LOG-ASSOC-DEBUG] Tab ID: ${tabIdToNotify}`);
4313
+ // console.log(`[LOG-ASSOC-DEBUG] ========== STARTING LOG ASSOCIATION ==========`);
4314
+ // console.log(`[LOG-ASSOC-DEBUG] Session ID: ${sessionIdToUse}`);
4315
+ // console.log(`[LOG-ASSOC-DEBUG] Number of logs to associate: ${bufferedLogs.length}`);
4316
+ // console.log(`[LOG-ASSOC-DEBUG] Tab ID: ${tabIdToNotify}`);
4008
4317
 
4009
4318
  // PRE-FLIGHT CHECK: Verify frames exist in database before attempting log association
4010
4319
  try {
4011
4320
  const ports = await getServerPorts();
4012
- console.log(`[LOG-ASSOC-DEBUG] PRE-FLIGHT: Checking if frames exist for session ${sessionIdToUse}...`);
4321
+ // console.log(`[LOG-ASSOC-DEBUG] PRE-FLIGHT: Checking if frames exist for session ${sessionIdToUse}...`);
4013
4322
  for (const port of ports) {
4014
4323
  try {
4015
4324
  const checkResponse = await fetch(`http://localhost:${port}/chromedebug/frame-session/${sessionIdToUse}`, {
@@ -4019,7 +4328,7 @@ async function handleFrameCaptureComplete(sessionData) {
4019
4328
  if (checkResponse.ok) {
4020
4329
  const sessionData = await checkResponse.json();
4021
4330
  const frameCount = sessionData.frames?.length || 0;
4022
- console.log(`[LOG-ASSOC-DEBUG] PRE-FLIGHT: Found ${frameCount} frames on port ${port}`);
4331
+ // console.log(`[LOG-ASSOC-DEBUG] PRE-FLIGHT: Found ${frameCount} frames on port ${port}`);
4023
4332
  if (frameCount === 0) {
4024
4333
  console.warn(`[LOG-ASSOC-DEBUG] ⚠️ WARNING: Session exists but has 0 frames! This will cause log association to fail.`);
4025
4334
  console.warn(`[LOG-ASSOC-DEBUG] Session data:`, sessionData);
@@ -4027,7 +4336,7 @@ async function handleFrameCaptureComplete(sessionData) {
4027
4336
  break;
4028
4337
  }
4029
4338
  } catch (e) {
4030
- console.log(`[LOG-ASSOC-DEBUG] PRE-FLIGHT: Could not check port ${port}:`, e.message);
4339
+ // console.log(`[LOG-ASSOC-DEBUG] PRE-FLIGHT: Could not check port ${port}:`, e.message);
4031
4340
  }
4032
4341
  }
4033
4342
  } catch (preflightError) {
@@ -4037,22 +4346,22 @@ async function handleFrameCaptureComplete(sessionData) {
4037
4346
  while (retryCount < maxRetries && !success) {
4038
4347
  try {
4039
4348
  const ports = await getServerPorts();
4040
- console.log(`[LOG-ASSOC-DEBUG] Attempt ${retryCount + 1}/${maxRetries}: Trying ports:`, ports);
4349
+ // console.log(`[LOG-ASSOC-DEBUG] Attempt ${retryCount + 1}/${maxRetries}: Trying ports:`, ports);
4041
4350
 
4042
4351
  for (const port of ports) {
4043
4352
  try {
4044
- console.log(`[LOG-ASSOC-DEBUG] Attempting port ${port}...`);
4353
+ // console.log(`[LOG-ASSOC-DEBUG] Attempting port ${port}...`);
4045
4354
  const requestBody = {
4046
4355
  sessionId: sessionIdToUse,
4047
4356
  logs: bufferedLogs
4048
4357
  };
4049
- console.log(`[LOG-ASSOC-DEBUG] Request payload: sessionId=${sessionIdToUse}, logs.length=${bufferedLogs.length}`);
4358
+ // console.log(`[LOG-ASSOC-DEBUG] Request payload: sessionId=${sessionIdToUse}, logs.length=${bufferedLogs.length}`);
4050
4359
  if (bufferedLogs.length > 0) {
4051
- console.log(`[LOG-ASSOC-DEBUG] First log sample:`, {
4052
- timestamp: bufferedLogs[0].timestamp,
4053
- level: bufferedLogs[0].level,
4054
- message: bufferedLogs[0].message?.substring(0, 100)
4055
- });
4360
+ // console.log(`[LOG-ASSOC-DEBUG] First log sample:`, {
4361
+ // timestamp: bufferedLogs[0].timestamp,
4362
+ // level: bufferedLogs[0].level,
4363
+ // message: bufferedLogs[0].message?.substring(0, 100)
4364
+ // });
4056
4365
  }
4057
4366
 
4058
4367
  const response = await fetch(`http://localhost:${port}/chromedebug/associate-logs`, {
@@ -4063,12 +4372,12 @@ async function handleFrameCaptureComplete(sessionData) {
4063
4372
  body: JSON.stringify(requestBody)
4064
4373
  });
4065
4374
 
4066
- console.log(`[LOG-ASSOC-DEBUG] Port ${port} response status: ${response.status} ${response.statusText}`);
4375
+ // console.log(`[LOG-ASSOC-DEBUG] Port ${port} response status: ${response.status} ${response.statusText}`);
4067
4376
 
4068
4377
  if (response.ok) {
4069
4378
  const result = await response.json();
4070
- console.log(`[LOG-ASSOC-DEBUG] ✅ SUCCESS on port ${port}:`, result);
4071
- console.log(`Logs associated successfully on port ${port}:`, result);
4379
+ // console.log(`[LOG-ASSOC-DEBUG] ✅ SUCCESS on port ${port}:`, result);
4380
+ // console.log(`Logs associated successfully on port ${port}:`, result);
4072
4381
  success = true;
4073
4382
 
4074
4383
  // Fetch the updated session data with logs from server
@@ -4076,14 +4385,14 @@ async function handleFrameCaptureComplete(sessionData) {
4076
4385
  const sessionResponse = await fetch(`http://localhost:${port}/chromedebug/frame-session/${sessionIdToUse}`);
4077
4386
  if (sessionResponse.ok) {
4078
4387
  const updatedSession = await sessionResponse.json();
4079
- console.log(`[LOG-ASSOC-DEBUG] Fetched updated session: ${updatedSession.frames?.length || 0} frames`);
4080
- console.log(`Fetched updated session with ${updatedSession.frames ? updatedSession.frames.length : 0} frames`);
4388
+ // console.log(`[LOG-ASSOC-DEBUG] Fetched updated session: ${updatedSession.frames?.length || 0} frames`);
4389
+ // console.log(`Fetched updated session with ${updatedSession.frames ? updatedSession.frames.length : 0} frames`);
4081
4390
 
4082
4391
  // Update Chrome local storage with the frames that now have logs
4083
4392
  const storageKey = sessionIdToUse;
4084
4393
  await chrome.storage.local.set({ [storageKey]: updatedSession });
4085
- console.log('[LOG-ASSOC-DEBUG] Updated local storage with frames containing logs');
4086
- console.log('Updated local storage with frames containing logs');
4394
+ // console.log('[LOG-ASSOC-DEBUG] Updated local storage with frames containing logs');
4395
+ // console.log('Updated local storage with frames containing logs');
4087
4396
  } else {
4088
4397
  console.error(`[LOG-ASSOC-DEBUG] Failed to fetch updated session: ${sessionResponse.status}`);
4089
4398
  console.error('Failed to fetch updated session data');
@@ -4119,15 +4428,15 @@ async function handleFrameCaptureComplete(sessionData) {
4119
4428
  attempt: retryCount + 1
4120
4429
  };
4121
4430
  console.error(`[LOG-ASSOC-DEBUG] ❌ Port ${port} exception:`, e);
4122
- console.log(`Failed to associate logs on port ${port}:`, e.message);
4431
+ // console.log(`Failed to associate logs on port ${port}:`, e.message);
4123
4432
  }
4124
4433
  }
4125
4434
 
4126
4435
  if (!success) {
4127
4436
  retryCount++;
4128
4437
  if (retryCount < maxRetries) {
4129
- console.log(`[LOG-ASSOC-DEBUG] Retrying in 1 second (attempt ${retryCount + 1}/${maxRetries})...`);
4130
- console.log(`Retrying log association (attempt ${retryCount + 1}/${maxRetries})...`);
4438
+ // console.log(`[LOG-ASSOC-DEBUG] Retrying in 1 second (attempt ${retryCount + 1}/${maxRetries})...`);
4439
+ // console.log(`Retrying log association (attempt ${retryCount + 1}/${maxRetries})...`);
4131
4440
  await new Promise(resolve => setTimeout(resolve, 1000));
4132
4441
  }
4133
4442
  }
@@ -4141,8 +4450,8 @@ async function handleFrameCaptureComplete(sessionData) {
4141
4450
  }
4142
4451
  }
4143
4452
 
4144
- console.log(`[LOG-ASSOC-DEBUG] ========== LOG ASSOCIATION COMPLETE ==========`);
4145
- console.log(`[LOG-ASSOC-DEBUG] Success: ${success}`);
4453
+ // console.log(`[LOG-ASSOC-DEBUG] ========== LOG ASSOCIATION COMPLETE ==========`);
4454
+ // console.log(`[LOG-ASSOC-DEBUG] Success: ${success}`);
4146
4455
  if (!success && lastErrorDetails) {
4147
4456
  console.error(`[LOG-ASSOC-DEBUG] Last error details:`, lastErrorDetails);
4148
4457
  }
@@ -4173,12 +4482,12 @@ async function handleFrameCaptureComplete(sessionData) {
4173
4482
  }
4174
4483
  } else if (bufferedLogs.length > 0 && serverMode === 'browser-only') {
4175
4484
  // Browser-only mode: Associate logs with frames in IndexedDB
4176
- console.log(`[Browser-Only] Associating ${bufferedLogs.length} logs with frames in IndexedDB...`);
4485
+ // console.log(`[Browser-Only] Associating ${bufferedLogs.length} logs with frames in IndexedDB...`);
4177
4486
 
4178
4487
  try {
4179
4488
  // Get all frames for this session from IndexedDB
4180
4489
  const frames = await dataBuffer.getBrowserFrames(sessionIdToUse);
4181
- console.log(`[Browser-Only] Retrieved ${frames.length} frames from IndexedDB for log association`);
4490
+ // console.log(`[Browser-Only] Retrieved ${frames.length} frames from IndexedDB for log association`);
4182
4491
 
4183
4492
  if (frames.length === 0) {
4184
4493
  console.warn('[Browser-Only] No frames found - logs cannot be associated');
@@ -4222,7 +4531,7 @@ async function handleFrameCaptureComplete(sessionData) {
4222
4531
  }
4223
4532
  }
4224
4533
 
4225
- console.log(`[Browser-Only] ✓ Associated logs with ${updatedCount} frames in IndexedDB`);
4534
+ // console.log(`[Browser-Only] ✓ Associated logs with ${updatedCount} frames in IndexedDB`);
4226
4535
  }
4227
4536
  } catch (error) {
4228
4537
  console.error('[Browser-Only] Failed to associate logs with frames:', error);
@@ -4248,18 +4557,18 @@ async function handleFrameCaptureComplete(sessionData) {
4248
4557
  // Save screen interactions to database
4249
4558
  // Skip server communication in browser-only mode
4250
4559
  if (screenInteractions.length > 0 && serverMode !== 'browser-only') {
4251
- console.log(`[Server Mode] Saving ${screenInteractions.length} screen interactions to database`);
4560
+ // console.log(`[Server Mode] Saving ${screenInteractions.length} screen interactions to database`);
4252
4561
 
4253
4562
  // Use comprehensive error handling for screen interactions save
4254
4563
  const saveScreenInteractions = async (data, context) => {
4255
4564
  // Diagnostic logging for screen interactions save
4256
- console.log('[ScreenInteractions] Preparing to save interactions:', {
4257
- sessionId: context.sessionId,
4258
- dataType: typeof data,
4259
- isArray: Array.isArray(data),
4260
- interactionCount: Array.isArray(data) ? data.length : 'N/A',
4261
- hasComplexData: Array.isArray(data) && data.some(i => i.element_html && i.element_html.length > 100)
4262
- });
4565
+ // console.log('[ScreenInteractions] Preparing to save interactions:', {
4566
+ // sessionId: context.sessionId,
4567
+ // dataType: typeof data,
4568
+ // isArray: Array.isArray(data),
4569
+ // interactionCount: Array.isArray(data) ? data.length : 'N/A',
4570
+ // hasComplexData: Array.isArray(data) && data.some(i => i.element_html && i.element_html.length > 100)
4571
+ // });
4263
4572
 
4264
4573
  let lastError = null;
4265
4574
  for (const port of CONFIG_PORTS) {
@@ -4269,7 +4578,7 @@ async function handleFrameCaptureComplete(sessionData) {
4269
4578
  try {
4270
4579
  // data is already { interactions: screenInteractions } from caller
4271
4580
  serializedBody = JSON.stringify(data);
4272
- console.log('[ScreenInteractions] JSON serialization successful, length:', serializedBody.length);
4581
+ // console.log('[ScreenInteractions] JSON serialization successful, length:', serializedBody.length);
4273
4582
  } catch (serError) {
4274
4583
  console.error('[ScreenInteractions] JSON serialization failed:', serError);
4275
4584
  throw new Error(`JSON serialization failed: ${serError.message}`);
@@ -4286,7 +4595,7 @@ async function handleFrameCaptureComplete(sessionData) {
4286
4595
  if (response.ok) {
4287
4596
  // Only log if we had to try multiple ports
4288
4597
  if (port !== CONFIG_PORTS[0]) {
4289
- console.log(`[ScreenInteractions] Connected to server on port ${port}`);
4598
+ // console.log(`[ScreenInteractions] Connected to server on port ${port}`);
4290
4599
  }
4291
4600
  return { success: true, result: { saved: true } };
4292
4601
  } else {
@@ -4310,13 +4619,13 @@ async function handleFrameCaptureComplete(sessionData) {
4310
4619
  );
4311
4620
 
4312
4621
  if (saveResult.success) {
4313
- console.log('Screen interactions saved successfully with comprehensive error handling');
4622
+ // console.log('Screen interactions saved successfully with comprehensive error handling');
4314
4623
  } else {
4315
4624
  console.error('[ErrorHandling] Failed to save screen interactions after comprehensive error handling');
4316
4625
  }
4317
4626
  } else if (screenInteractions.length > 0 && serverMode === 'browser-only') {
4318
4627
  // Browser-only mode: screen interactions not needed
4319
- console.log(`[Browser-Only] ✓ Skipping screen interactions save (browser-only mode)`);
4628
+ // console.log(`[Browser-Only] ✓ Skipping screen interactions save (browser-only mode)`);
4320
4629
  }
4321
4630
 
4322
4631
  // Stop session via session manager if available
@@ -4324,7 +4633,7 @@ async function handleFrameCaptureComplete(sessionData) {
4324
4633
  try {
4325
4634
  const stopResult = await sessionManager.stopRecording(currentSession.sessionId, currentOwnerId);
4326
4635
  if (stopResult.success) {
4327
- console.log(`Session stopped via session manager. Duration: ${stopResult.sessionDuration}ms, Frames: ${stopResult.frameCount}`);
4636
+ // console.log(`Session stopped via session manager. Duration: ${stopResult.sessionDuration}ms, Frames: ${stopResult.frameCount}`);
4328
4637
  } else {
4329
4638
  console.warn('Failed to stop session via session manager:', stopResult.error?.message);
4330
4639
  }
@@ -4376,7 +4685,7 @@ async function handleFrameCaptureComplete(sessionData) {
4376
4685
  serverPort: recordingServerPort
4377
4686
  }).catch(() => {});
4378
4687
  } else {
4379
- console.log('[Browser-Only] Skipping frameSessionComplete message - recording is in IndexedDB');
4688
+ // console.log('[Browser-Only] Skipping frameSessionComplete message - recording is in IndexedDB');
4380
4689
  }
4381
4690
 
4382
4691
  // Notify content script to hide recording indicator