@dynamicu/chromedebug-mcp 2.6.7 → 2.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +1 -1
- package/README.md +1 -1
- package/chrome-extension/background.js +611 -505
- package/chrome-extension/browser-recording-manager.js +1 -1
- package/chrome-extension/chrome-debug-logger.js +168 -0
- package/chrome-extension/console-interception-library.js +430 -0
- package/chrome-extension/content.css +16 -16
- package/chrome-extension/content.js +458 -126
- package/chrome-extension/extension-config.js +1 -1
- package/chrome-extension/license-helper.js +26 -0
- package/chrome-extension/manifest.free.json +0 -3
- package/chrome-extension/options.js +1 -1
- package/chrome-extension/popup.html +221 -191
- package/chrome-extension/popup.js +88 -379
- package/chrome-extension/pro/enhanced-capture.js +406 -0
- package/chrome-extension/pro/frame-editor.html +410 -0
- package/chrome-extension/pro/frame-editor.js +1496 -0
- package/chrome-extension/pro/function-tracker.js +843 -0
- package/chrome-extension/pro/jszip.min.js +13 -0
- package/dist/chromedebug-extension-free.zip +0 -0
- package/package.json +3 -1
- package/scripts/webpack.config.free.cjs +8 -8
- package/scripts/webpack.config.pro.cjs +2 -0
- package/src/cli.js +2 -2
- package/src/database.js +55 -7
- package/src/index.js +9 -6
- package/src/mcp/server.js +2 -2
- package/src/services/process-manager.js +10 -6
- package/src/services/process-tracker.js +10 -5
- package/src/services/profile-manager.js +17 -2
- package/src/validation/schemas.js +2 -2
- package/src/index-direct.js +0 -157
- package/src/index-modular.js +0 -219
- package/src/index-monolithic-backup.js +0 -2230
- package/src/legacy/chrome-controller-old.js +0 -1406
- package/src/legacy/index-express.js +0 -625
- package/src/legacy/index-old.js +0 -977
- package/src/legacy/routes.js +0 -260
- package/src/legacy/shared-storage.js +0 -101
|
@@ -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('
|
|
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
|
|
@@ -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;
|
|
@@ -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'
|
|
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) {
|
|
@@ -1051,96 +1347,12 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
|
|
1051
1347
|
}
|
|
1052
1348
|
});
|
|
1053
1349
|
|
|
1054
|
-
//
|
|
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
1354
|
console.log('[Background] Extension startup detected - checking for stuck sessions');
|
|
1140
1355
|
|
|
1141
|
-
// Check for PRO license auto-activation first
|
|
1142
|
-
await checkAndActivateLicense();
|
|
1143
|
-
|
|
1144
1356
|
if (sessionManager) {
|
|
1145
1357
|
try {
|
|
1146
1358
|
// Use existing recovery method from session manager
|
|
@@ -1154,12 +1366,9 @@ chrome.runtime.onStartup.addListener(async () => {
|
|
|
1154
1366
|
}
|
|
1155
1367
|
});
|
|
1156
1368
|
|
|
1157
|
-
// Extension install/update
|
|
1369
|
+
// Extension install/update
|
|
1158
1370
|
chrome.runtime.onInstalled.addListener(async (details) => {
|
|
1159
1371
|
console.log('[Background] Extension installed/updated:', details.reason);
|
|
1160
|
-
|
|
1161
|
-
// Check for PRO license auto-activation on install/update
|
|
1162
|
-
await checkAndActivateLicense();
|
|
1163
1372
|
});
|
|
1164
1373
|
|
|
1165
1374
|
// Listen for tab updates to handle restore points
|
|
@@ -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
|
|
@@ -1399,7 +1645,7 @@ let workflowRecordingTabs = new Map(); // Map of tabId to recording state
|
|
|
1399
1645
|
let workflowIncludeLogs = new Map(); // Map of tabId to includeLogsInExport setting
|
|
1400
1646
|
let workflowScreenshotSettings = new Map(); // Map of tabId to screenshot settings
|
|
1401
1647
|
let workflowSessionNames = new Map(); // Map of tabId to session name
|
|
1402
|
-
let
|
|
1648
|
+
let workflowLogBuffers = new Map(); // Map of tabId to WorkflowLogBuffer instance
|
|
1403
1649
|
|
|
1404
1650
|
//=============================================================================
|
|
1405
1651
|
// COMPREHENSIVE ERROR HANDLING SYSTEM
|
|
@@ -1875,7 +2121,7 @@ async function downloadBackupData() {
|
|
|
1875
2121
|
|
|
1876
2122
|
chrome.downloads.download({
|
|
1877
2123
|
url: url,
|
|
1878
|
-
filename: `chrome-
|
|
2124
|
+
filename: `chrome-debug-backup-${Date.now()}.json`
|
|
1879
2125
|
});
|
|
1880
2126
|
|
|
1881
2127
|
console.log('[ErrorHandling] Backup data download initiated');
|
|
@@ -2015,8 +2261,13 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
|
|
|
2015
2261
|
}
|
|
2016
2262
|
|
|
2017
2263
|
if (request.action === 'stopRecording') {
|
|
2018
|
-
stopRecording().then(() => {
|
|
2019
|
-
|
|
2264
|
+
stopRecording().then(async (result) => {
|
|
2265
|
+
// Track usage for FREE tier after successful screen recording
|
|
2266
|
+
if (result.success) {
|
|
2267
|
+
const usageResult = await incrementUsageAfterRecording();
|
|
2268
|
+
result.usage = usageResult; // Include updated usage in response
|
|
2269
|
+
}
|
|
2270
|
+
sendResponse(result); // Pass through { success: true, sessionId: ..., usage: ... }
|
|
2020
2271
|
}).catch((error) => {
|
|
2021
2272
|
console.error('Error stopping recording:', error);
|
|
2022
2273
|
sendResponse({ success: false, error: error.message });
|
|
@@ -2024,6 +2275,23 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
|
|
|
2024
2275
|
return true;
|
|
2025
2276
|
}
|
|
2026
2277
|
|
|
2278
|
+
if (request.action === 'openFrameEditor') {
|
|
2279
|
+
// Content scripts can't create tabs, so handle it here
|
|
2280
|
+
const sessionId = request.sessionId;
|
|
2281
|
+
const type = request.type; // 'workflow' or undefined for screen recordings
|
|
2282
|
+
const editorUrl = type === 'workflow'
|
|
2283
|
+
? chrome.runtime.getURL(`pro/frame-editor.html?sessionId=${sessionId}&type=workflow`)
|
|
2284
|
+
: chrome.runtime.getURL(`pro/frame-editor.html?sessionId=${sessionId}`);
|
|
2285
|
+
|
|
2286
|
+
chrome.tabs.create({ url: editorUrl }).then(() => {
|
|
2287
|
+
sendResponse({ success: true });
|
|
2288
|
+
}).catch((error) => {
|
|
2289
|
+
console.error('Error opening frame editor:', error);
|
|
2290
|
+
sendResponse({ success: false, error: error.message });
|
|
2291
|
+
});
|
|
2292
|
+
return true;
|
|
2293
|
+
}
|
|
2294
|
+
|
|
2027
2295
|
// Get quota information for browser-only mode
|
|
2028
2296
|
if (request.action === 'getQuotaInfo') {
|
|
2029
2297
|
getQuotaInfo().then((quotaInfo) => {
|
|
@@ -2217,20 +2485,6 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
|
|
|
2217
2485
|
return true;
|
|
2218
2486
|
}
|
|
2219
2487
|
|
|
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
2488
|
// Workflow recording handlers
|
|
2235
2489
|
if (request.action === 'startWorkflowRecording') {
|
|
2236
2490
|
console.log('Starting workflow recording for tab:', request.tabId);
|
|
@@ -2249,9 +2503,17 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
|
|
|
2249
2503
|
}
|
|
2250
2504
|
|
|
2251
2505
|
if (request.action === 'stopWorkflowRecording') {
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2506
|
+
const tabId = request.tabId || sender.tab?.id;
|
|
2507
|
+
console.log('Stopping workflow recording for tab:', tabId);
|
|
2508
|
+
// stopWorkflowRecording already returns { success: true, workflow: {...}, savedToServer: bool }
|
|
2509
|
+
// so we pass it through directly - don't double-wrap!
|
|
2510
|
+
stopWorkflowRecording(tabId).then(async (result) => {
|
|
2511
|
+
// Track usage for FREE tier after successful recording
|
|
2512
|
+
if (result.success) {
|
|
2513
|
+
const usageResult = await incrementUsageAfterRecording();
|
|
2514
|
+
result.usage = usageResult; // Include updated usage in response
|
|
2515
|
+
}
|
|
2516
|
+
sendResponse(result);
|
|
2255
2517
|
}).catch((error) => {
|
|
2256
2518
|
console.error('Error stopping workflow recording:', error);
|
|
2257
2519
|
sendResponse({ success: false, error: error.message });
|
|
@@ -2356,18 +2618,19 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
|
|
|
2356
2618
|
}
|
|
2357
2619
|
}
|
|
2358
2620
|
|
|
2359
|
-
// Buffer workflow console logs
|
|
2621
|
+
// Buffer workflow console logs using race-safe WorkflowLogBuffer
|
|
2360
2622
|
if (request.action === 'workflowConsoleLog' && sender.tab) {
|
|
2361
2623
|
const tabId = sender.tab.id;
|
|
2362
2624
|
if (workflowRecordingTabs.has(tabId) && workflowIncludeLogs.get(tabId)) {
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2625
|
+
const buffer = workflowLogBuffers.get(tabId);
|
|
2626
|
+
if (buffer) {
|
|
2627
|
+
// Use race-safe buffer to prevent read-modify-write race conditions
|
|
2628
|
+
buffer.addLog(request.log).catch(err => {
|
|
2629
|
+
console.error('[WorkflowLogBuffer] Error adding log:', err);
|
|
2630
|
+
});
|
|
2631
|
+
} else {
|
|
2632
|
+
console.warn(`[Workflow] No buffer found for tab ${tabId}, log dropped`);
|
|
2633
|
+
}
|
|
2371
2634
|
}
|
|
2372
2635
|
}
|
|
2373
2636
|
|
|
@@ -2742,31 +3005,10 @@ async function getQuotaInfo() {
|
|
|
2742
3005
|
|
|
2743
3006
|
async function startRecording(tabId, settings = {}) {
|
|
2744
3007
|
try {
|
|
2745
|
-
//
|
|
3008
|
+
// Detect server mode before starting
|
|
2746
3009
|
await detectServerMode();
|
|
2747
3010
|
console.log(`[Recording] Mode: ${serverMode}`);
|
|
2748
3011
|
|
|
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;
|
|
2769
|
-
|
|
2770
3012
|
// Check if session manager is available
|
|
2771
3013
|
if (!sessionManager) {
|
|
2772
3014
|
throw new Error('Session manager not initialized');
|
|
@@ -2965,16 +3207,23 @@ async function stopRecording() {
|
|
|
2965
3207
|
// Check if session manager is available
|
|
2966
3208
|
if (!sessionManager) {
|
|
2967
3209
|
console.warn('Session manager not available, stopping recording anyway');
|
|
2968
|
-
return;
|
|
3210
|
+
return { success: false, error: 'Session manager not available' };
|
|
2969
3211
|
}
|
|
2970
3212
|
|
|
2971
3213
|
// Check if currently recording via session manager
|
|
2972
3214
|
const currentlyRecording = await isCurrentlyRecordingAsync();
|
|
2973
3215
|
if (!currentlyRecording) {
|
|
2974
3216
|
console.log('No active recording session to stop');
|
|
2975
|
-
return;
|
|
3217
|
+
return { success: false, error: 'No active recording' };
|
|
2976
3218
|
}
|
|
2977
3219
|
|
|
3220
|
+
// Store sessionId before clearing it
|
|
3221
|
+
const stoppedSessionId = currentSession?.sessionId;
|
|
3222
|
+
|
|
3223
|
+
// Capture frame count and duration for the response
|
|
3224
|
+
let recordedFrameCount = 0;
|
|
3225
|
+
let recordedDuration = 0;
|
|
3226
|
+
|
|
2978
3227
|
// Stop recording with session manager
|
|
2979
3228
|
if (currentSession?.sessionId && currentOwnerId) {
|
|
2980
3229
|
const stopResult = await sessionManager.stopRecording(currentSession.sessionId, currentOwnerId);
|
|
@@ -2982,18 +3231,15 @@ async function stopRecording() {
|
|
|
2982
3231
|
console.error('Failed to stop session:', stopResult.error?.message);
|
|
2983
3232
|
// Continue with cleanup anyway
|
|
2984
3233
|
} else {
|
|
2985
|
-
|
|
3234
|
+
recordedFrameCount = stopResult.frameCount || 0;
|
|
3235
|
+
recordedDuration = stopResult.sessionDuration || 0;
|
|
3236
|
+
console.log(`Recording stopped. Duration: ${recordedDuration}ms, Frames: ${recordedFrameCount}`);
|
|
2986
3237
|
|
|
2987
3238
|
// Finalize browser-only recording if in browser-only mode
|
|
2988
3239
|
if (serverMode === 'browser-only' && browserRecordingManager) {
|
|
2989
3240
|
console.log('[Browser-Only] Finalizing browser recording in IndexedDB');
|
|
2990
3241
|
await browserRecordingManager.stopRecording(currentSession.sessionId);
|
|
2991
3242
|
}
|
|
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
3243
|
}
|
|
2998
3244
|
}
|
|
2999
3245
|
|
|
@@ -3023,6 +3269,29 @@ async function stopRecording() {
|
|
|
3023
3269
|
target: 'offscreen'
|
|
3024
3270
|
});
|
|
3025
3271
|
|
|
3272
|
+
// CRITICAL: Store recordingTabId BEFORE clearing it (for cleanup)
|
|
3273
|
+
const previousRecordingTabId = recordingTabId;
|
|
3274
|
+
console.log('[Background] previousRecordingTabId:', previousRecordingTabId, 'stoppedSessionId:', stoppedSessionId);
|
|
3275
|
+
|
|
3276
|
+
// CRITICAL FIX: Send stop-screen-capture-tracking message to recording tab WITH sessionId
|
|
3277
|
+
// so it can show the completion UI
|
|
3278
|
+
if (previousRecordingTabId && stoppedSessionId) {
|
|
3279
|
+
try {
|
|
3280
|
+
console.log('[Background] Sending recording-complete-show-ui message to tab:', previousRecordingTabId, 'with sessionId:', stoppedSessionId);
|
|
3281
|
+
await chrome.tabs.sendMessage(previousRecordingTabId, {
|
|
3282
|
+
type: 'recording-complete-show-ui',
|
|
3283
|
+
sessionId: stoppedSessionId
|
|
3284
|
+
}).catch((err) => {
|
|
3285
|
+
console.log('[Background] Could not send completion UI message to recording tab:', err);
|
|
3286
|
+
});
|
|
3287
|
+
console.log('[Background] Successfully sent recording-complete-show-ui message');
|
|
3288
|
+
} catch (error) {
|
|
3289
|
+
console.error('[Background] Failed to send completion UI message:', error);
|
|
3290
|
+
}
|
|
3291
|
+
} else {
|
|
3292
|
+
console.log('[Background] NOT sending recording-complete-show-ui - missing previousRecordingTabId or stoppedSessionId');
|
|
3293
|
+
}
|
|
3294
|
+
|
|
3026
3295
|
// CRITICAL FIX: Send stop-screen-capture-tracking message to ALL content scripts
|
|
3027
3296
|
try {
|
|
3028
3297
|
const tabs = await chrome.tabs.query({});
|
|
@@ -3043,12 +3312,22 @@ async function stopRecording() {
|
|
|
3043
3312
|
console.error('[Background] Failed to send cleanup messages to content scripts:', error);
|
|
3044
3313
|
}
|
|
3045
3314
|
|
|
3315
|
+
// CRITICAL FIX: Restore original console methods and remove event listeners
|
|
3316
|
+
if (previousRecordingTabId) {
|
|
3317
|
+
try {
|
|
3318
|
+
console.log('[Background] Stopping console log capture for tab:', previousRecordingTabId);
|
|
3319
|
+
await stopCapturingLogs(previousRecordingTabId);
|
|
3320
|
+
} catch (cleanupError) {
|
|
3321
|
+
console.error('[Background] Failed to cleanup console interceptor:', cleanupError);
|
|
3322
|
+
// Continue with stop process even if cleanup fails
|
|
3323
|
+
}
|
|
3324
|
+
}
|
|
3325
|
+
|
|
3046
3326
|
// Clear session cache
|
|
3047
3327
|
currentSession = null;
|
|
3048
3328
|
currentOwnerId = null;
|
|
3049
3329
|
|
|
3050
3330
|
// Update legacy state for backward compatibility (will be removed)
|
|
3051
|
-
const previousRecordingTabId = recordingTabId; // Store for logBuffer cleanup
|
|
3052
3331
|
isCurrentlyRecording = false;
|
|
3053
3332
|
recordingTabId = null;
|
|
3054
3333
|
currentRecordingSessionId = null;
|
|
@@ -3058,11 +3337,56 @@ async function stopRecording() {
|
|
|
3058
3337
|
logBuffer.clearTab(previousRecordingTabId);
|
|
3059
3338
|
}
|
|
3060
3339
|
|
|
3340
|
+
// CRITICAL: Clear pending frame queue to prevent infinite retry loop
|
|
3341
|
+
if (stoppedSessionId && pendingFrameQueue.has(stoppedSessionId)) {
|
|
3342
|
+
console.log(`[FrameQueue] Clearing ${pendingFrameQueue.get(stoppedSessionId).length} pending frames for stopped session ${stoppedSessionId}`);
|
|
3343
|
+
pendingFrameQueue.delete(stoppedSessionId);
|
|
3344
|
+
}
|
|
3345
|
+
|
|
3061
3346
|
console.log('Stop message sent to offscreen document');
|
|
3347
|
+
|
|
3348
|
+
// Notify popup to refresh recordings list
|
|
3349
|
+
chrome.runtime.sendMessage({
|
|
3350
|
+
action: 'recordingStopped',
|
|
3351
|
+
sessionId: stoppedSessionId
|
|
3352
|
+
}).catch(() => {
|
|
3353
|
+
// Popup might not be open, that's okay
|
|
3354
|
+
console.log('[Background] Popup not available to notify of recording stop');
|
|
3355
|
+
});
|
|
3356
|
+
|
|
3357
|
+
// Format duration as human-readable string (e.g., "1m 30s" or "45s")
|
|
3358
|
+
const formatDurationForDisplay = (ms) => {
|
|
3359
|
+
const seconds = Math.floor(ms / 1000);
|
|
3360
|
+
if (seconds < 60) {
|
|
3361
|
+
return `${seconds}s`;
|
|
3362
|
+
}
|
|
3363
|
+
const minutes = Math.floor(seconds / 60);
|
|
3364
|
+
const remainingSeconds = seconds % 60;
|
|
3365
|
+
return remainingSeconds > 0 ? `${minutes}m ${remainingSeconds}s` : `${minutes}m`;
|
|
3366
|
+
};
|
|
3367
|
+
|
|
3368
|
+
// Return success with sessionId, frameCount and duration for popup display
|
|
3369
|
+
return {
|
|
3370
|
+
success: true,
|
|
3371
|
+
sessionId: stoppedSessionId,
|
|
3372
|
+
frameCount: recordedFrameCount,
|
|
3373
|
+
duration: formatDurationForDisplay(recordedDuration)
|
|
3374
|
+
};
|
|
3062
3375
|
} catch (error) {
|
|
3063
3376
|
console.error('Error in stopRecording:', error);
|
|
3064
3377
|
// Clear session cache even on error
|
|
3065
3378
|
const errorRecordingTabId = recordingTabId; // Store for cleanup
|
|
3379
|
+
|
|
3380
|
+
// CRITICAL FIX: Clean up console interceptor even on error
|
|
3381
|
+
if (errorRecordingTabId) {
|
|
3382
|
+
try {
|
|
3383
|
+
console.log('[Background] Emergency cleanup of console interceptor for tab:', errorRecordingTabId);
|
|
3384
|
+
await stopCapturingLogs(errorRecordingTabId);
|
|
3385
|
+
} catch (cleanupError) {
|
|
3386
|
+
console.error('[Background] Failed to cleanup console interceptor during error handling:', cleanupError);
|
|
3387
|
+
}
|
|
3388
|
+
}
|
|
3389
|
+
|
|
3066
3390
|
currentSession = null;
|
|
3067
3391
|
currentOwnerId = null;
|
|
3068
3392
|
isCurrentlyRecording = false;
|
|
@@ -3081,204 +3405,16 @@ async function stopRecording() {
|
|
|
3081
3405
|
|
|
3082
3406
|
|
|
3083
3407
|
// Capture console logs from the recording tab
|
|
3408
|
+
// REFACTORED: Now uses shared console-interception-library.js
|
|
3084
3409
|
async function startCapturingLogs(tabId) {
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
|
|
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
|
-
});
|
|
3410
|
+
console.log('[Screen Recording] Starting console interception using shared library');
|
|
3411
|
+
return await self.ConsoleInterceptionLibrary.startConsoleInterception(tabId, SCREEN_RECORDING_CONSOLE_CONFIG);
|
|
3412
|
+
}
|
|
3274
3413
|
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
console.error('[Console Injection] Error details:', error.message, error.stack);
|
|
3280
|
-
return false; // Failed to inject console interceptor
|
|
3281
|
-
}
|
|
3414
|
+
// REFACTORED: Now uses shared console-interception-library.js via proper namespace
|
|
3415
|
+
async function stopCapturingLogs(tabId) {
|
|
3416
|
+
console.log('[Screen Recording] Stopping console interception using shared library');
|
|
3417
|
+
return await self.ConsoleInterceptionLibrary.stopConsoleInterception(tabId, SCREEN_RECORDING_CONSOLE_CONFIG);
|
|
3282
3418
|
}
|
|
3283
3419
|
|
|
3284
3420
|
// Workflow Recording Functions
|
|
@@ -3286,29 +3422,6 @@ async function startWorkflowRecording(tabId, includeLogsInExport, sessionName =
|
|
|
3286
3422
|
try {
|
|
3287
3423
|
console.log('Starting workflow recording for tab:', tabId);
|
|
3288
3424
|
|
|
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);
|
|
3311
|
-
|
|
3312
3425
|
// Check if this tab allows content script injection
|
|
3313
3426
|
const tab = await chrome.tabs.get(tabId);
|
|
3314
3427
|
if (!tab.url || tab.url.startsWith('chrome://') || tab.url.startsWith('chrome-extension://') || tab.url.startsWith('moz-extension://')) {
|
|
@@ -3326,107 +3439,18 @@ async function startWorkflowRecording(tabId, includeLogsInExport, sessionName =
|
|
|
3326
3439
|
const workflowId = `workflow_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
3327
3440
|
await chrome.storage.local.set({ currentWorkflowId: workflowId });
|
|
3328
3441
|
|
|
3329
|
-
// Clear any existing logs for this tab
|
|
3442
|
+
// Clear any existing logs for this tab and initialize race-safe buffer
|
|
3330
3443
|
await chrome.storage.session.set({ [`workflow_${tabId}`]: [] });
|
|
3331
|
-
|
|
3332
|
-
//
|
|
3333
|
-
|
|
3334
|
-
|
|
3335
|
-
|
|
3336
|
-
|
|
3337
|
-
|
|
3338
|
-
|
|
3339
|
-
|
|
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
|
-
});
|
|
3444
|
+
|
|
3445
|
+
// Initialize WorkflowLogBuffer for race-safe log handling
|
|
3446
|
+
if (includeLogsInExport) {
|
|
3447
|
+
const buffer = new WorkflowLogBuffer(tabId);
|
|
3448
|
+
workflowLogBuffers.set(tabId, buffer);
|
|
3449
|
+
console.log(`[Workflow] Initialized WorkflowLogBuffer for tab ${tabId}`);
|
|
3450
|
+
}
|
|
3451
|
+
|
|
3452
|
+
// REFACTORED: Use shared console interception library via proper namespace (replaces 150+ lines of duplicate code)
|
|
3453
|
+
await self.ConsoleInterceptionLibrary.startConsoleInterception(tabId, WORKFLOW_RECORDING_CONSOLE_CONFIG);
|
|
3430
3454
|
|
|
3431
3455
|
// Ensure content script is injected
|
|
3432
3456
|
try {
|
|
@@ -3444,9 +3468,11 @@ async function startWorkflowRecording(tabId, includeLogsInExport, sessionName =
|
|
|
3444
3468
|
}
|
|
3445
3469
|
|
|
3446
3470
|
// Tell the content script to start recording
|
|
3471
|
+
// Include tabId so content script can use it when stopping recording
|
|
3447
3472
|
await chrome.tabs.sendMessage(tabId, {
|
|
3448
3473
|
action: 'startWorkflowRecording',
|
|
3449
|
-
screenshotSettings: screenshotSettings
|
|
3474
|
+
screenshotSettings: screenshotSettings,
|
|
3475
|
+
tabId: tabId
|
|
3450
3476
|
});
|
|
3451
3477
|
|
|
3452
3478
|
console.log('Workflow recording started successfully');
|
|
@@ -3465,15 +3491,25 @@ async function stopWorkflowRecording(tabId) {
|
|
|
3465
3491
|
throw new Error('No workflow recording active for this tab');
|
|
3466
3492
|
}
|
|
3467
3493
|
|
|
3468
|
-
// Get workflow from content script
|
|
3494
|
+
// Get workflow from content script using a different action to avoid circular dependency
|
|
3495
|
+
// (mini menu also sends 'stopWorkflowRecording' to background, creating confusion)
|
|
3469
3496
|
const response = await chrome.tabs.sendMessage(tabId, {
|
|
3470
|
-
action: '
|
|
3497
|
+
action: 'getWorkflowData'
|
|
3471
3498
|
});
|
|
3472
|
-
|
|
3499
|
+
|
|
3473
3500
|
if (!response || !response.success) {
|
|
3474
3501
|
throw new Error('Failed to get workflow from content script');
|
|
3475
3502
|
}
|
|
3476
|
-
|
|
3503
|
+
|
|
3504
|
+
// CRITICAL FIX: Restore original console methods and remove event listeners
|
|
3505
|
+
try {
|
|
3506
|
+
console.log('[Workflow] Stopping console log capture for tab:', tabId);
|
|
3507
|
+
await stopCapturingWorkflowLogs(tabId);
|
|
3508
|
+
} catch (cleanupError) {
|
|
3509
|
+
console.error('[Workflow] Failed to cleanup console interceptor:', cleanupError);
|
|
3510
|
+
// Continue with stop process even if cleanup fails
|
|
3511
|
+
}
|
|
3512
|
+
|
|
3477
3513
|
// Handle both old format (array) and new format (object with actions and functionTraces)
|
|
3478
3514
|
let workflowData = response.workflow || [];
|
|
3479
3515
|
let workflow = Array.isArray(workflowData) ? workflowData : (workflowData.actions || []);
|
|
@@ -3494,22 +3530,38 @@ async function stopWorkflowRecording(tabId) {
|
|
|
3494
3530
|
});
|
|
3495
3531
|
});
|
|
3496
3532
|
|
|
3497
|
-
// If logs should be included, get them from session storage
|
|
3533
|
+
// If logs should be included, flush buffer and get them from session storage
|
|
3498
3534
|
if (workflowIncludeLogs.get(tabId)) {
|
|
3535
|
+
// CRITICAL: Flush any buffered logs before retrieval to ensure all logs are captured
|
|
3536
|
+
const buffer = workflowLogBuffers.get(tabId);
|
|
3537
|
+
if (buffer) {
|
|
3538
|
+
await buffer.flush();
|
|
3539
|
+
console.log(`[Workflow] Flushed buffer before retrieval, stats:`, buffer.getStats());
|
|
3540
|
+
}
|
|
3541
|
+
|
|
3499
3542
|
const result = await chrome.storage.session.get(`workflow_${tabId}`);
|
|
3500
3543
|
const logs = result[`workflow_${tabId}`] || [];
|
|
3501
3544
|
|
|
3502
|
-
// Associate logs with workflow actions
|
|
3545
|
+
// Associate logs with workflow actions using NON-OVERLAPPING windows
|
|
3546
|
+
// Each log appears in exactly ONE action to avoid duplicates
|
|
3503
3547
|
workflow = workflow.map((action, index) => {
|
|
3504
|
-
//
|
|
3548
|
+
// Calculate non-overlapping time window:
|
|
3549
|
+
// - Each action owns logs from ITS timestamp until the NEXT action's timestamp
|
|
3550
|
+
// - First action also includes 500ms before it
|
|
3551
|
+
// - Last action includes 5000ms after it
|
|
3552
|
+
const windowStart = index === 0
|
|
3553
|
+
? action.timestamp - 500 // First action: include 500ms before
|
|
3554
|
+
: action.timestamp; // Other actions: start at this action's timestamp
|
|
3555
|
+
|
|
3556
|
+
const windowEnd = index < workflow.length - 1
|
|
3557
|
+
? workflow[index + 1].timestamp // Until next action's timestamp
|
|
3558
|
+
: action.timestamp + 5000; // Last action: 5000ms after
|
|
3559
|
+
|
|
3560
|
+
// Use >= for start and < for end to ensure non-overlapping windows
|
|
3505
3561
|
const actionLogs = logs.filter(log => {
|
|
3506
|
-
|
|
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;
|
|
3562
|
+
return log.timestamp >= windowStart && log.timestamp < windowEnd;
|
|
3511
3563
|
});
|
|
3512
|
-
|
|
3564
|
+
|
|
3513
3565
|
if (actionLogs.length > 0) {
|
|
3514
3566
|
return { ...action, logs: actionLogs };
|
|
3515
3567
|
}
|
|
@@ -3521,26 +3573,44 @@ async function stopWorkflowRecording(tabId) {
|
|
|
3521
3573
|
const sessionName = workflowSessionNames.get(tabId);
|
|
3522
3574
|
const screenshotSettings = workflowScreenshotSettings.get(tabId);
|
|
3523
3575
|
const includeLogs = workflowIncludeLogs.get(tabId) || false;
|
|
3524
|
-
const userId = workflowUserIds.get(tabId); // Get userId for usage tracking
|
|
3525
3576
|
|
|
3526
3577
|
// Clean up
|
|
3527
3578
|
workflowRecordingTabs.delete(tabId);
|
|
3528
3579
|
workflowIncludeLogs.delete(tabId);
|
|
3529
3580
|
workflowScreenshotSettings.delete(tabId);
|
|
3530
3581
|
workflowSessionNames.delete(tabId);
|
|
3531
|
-
|
|
3582
|
+
|
|
3583
|
+
// Clean up WorkflowLogBuffer
|
|
3584
|
+
const bufferToCleanup = workflowLogBuffers.get(tabId);
|
|
3585
|
+
if (bufferToCleanup) {
|
|
3586
|
+
await bufferToCleanup.cleanup();
|
|
3587
|
+
workflowLogBuffers.delete(tabId);
|
|
3588
|
+
console.log(`[Workflow] Cleaned up WorkflowLogBuffer for tab ${tabId}`);
|
|
3589
|
+
}
|
|
3590
|
+
|
|
3532
3591
|
await chrome.storage.session.remove(`workflow_${tabId}`);
|
|
3533
|
-
|
|
3592
|
+
|
|
3593
|
+
// Notify popup and other extension components that recording has stopped
|
|
3594
|
+
// This is critical for popup UI sync when stopping from mini-menu
|
|
3595
|
+
await chrome.storage.local.set({
|
|
3596
|
+
workflowRecording: false,
|
|
3597
|
+
workflowStartTime: null
|
|
3598
|
+
});
|
|
3599
|
+
|
|
3534
3600
|
console.log('Workflow recording stopped, returning workflow:', workflow);
|
|
3535
3601
|
|
|
3536
3602
|
// Try to save to server
|
|
3537
3603
|
try {
|
|
3538
3604
|
const serverPorts = CONFIG_PORTS.slice(0, 5); // Use first 5 configured ports for workflow recording
|
|
3539
3605
|
let serverResult = null;
|
|
3540
|
-
|
|
3606
|
+
|
|
3541
3607
|
// Get current URL and title
|
|
3542
3608
|
const tab = await chrome.tabs.get(tabId);
|
|
3543
|
-
|
|
3609
|
+
|
|
3610
|
+
// Retrieve the workflow ID that was generated at start
|
|
3611
|
+
const storedData = await chrome.storage.local.get(['currentWorkflowId']);
|
|
3612
|
+
const sessionId = storedData.currentWorkflowId || `workflow_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
3613
|
+
console.log('[Workflow] Using sessionId for save:', sessionId);
|
|
3544
3614
|
|
|
3545
3615
|
for (const port of serverPorts) {
|
|
3546
3616
|
try {
|
|
@@ -3584,14 +3654,6 @@ async function stopWorkflowRecording(tabId) {
|
|
|
3584
3654
|
if (response.ok) {
|
|
3585
3655
|
serverResult = await response.json();
|
|
3586
3656
|
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
|
-
|
|
3595
3657
|
break;
|
|
3596
3658
|
} else {
|
|
3597
3659
|
console.log(`[Workflow] Failed on port ${port}: ${response.status} ${response.statusText}`);
|
|
@@ -3604,10 +3666,13 @@ async function stopWorkflowRecording(tabId) {
|
|
|
3604
3666
|
}
|
|
3605
3667
|
|
|
3606
3668
|
if (serverResult) {
|
|
3669
|
+
// Clean up currentWorkflowId after successful save
|
|
3670
|
+
await chrome.storage.local.remove(['currentWorkflowId']);
|
|
3671
|
+
|
|
3607
3672
|
return {
|
|
3608
3673
|
success: true,
|
|
3609
3674
|
workflow: {
|
|
3610
|
-
sessionId: sessionId,
|
|
3675
|
+
sessionId: serverResult.workflowId || serverResult.sessionId || sessionId,
|
|
3611
3676
|
url: tab.url,
|
|
3612
3677
|
title: tab.title,
|
|
3613
3678
|
actions: workflow,
|
|
@@ -3617,17 +3682,36 @@ async function stopWorkflowRecording(tabId) {
|
|
|
3617
3682
|
serverResult: serverResult
|
|
3618
3683
|
};
|
|
3619
3684
|
} else {
|
|
3685
|
+
// Server not available - clean up but return local sessionId
|
|
3686
|
+
await chrome.storage.local.remove(['currentWorkflowId']);
|
|
3687
|
+
|
|
3620
3688
|
return {
|
|
3621
3689
|
success: true,
|
|
3622
|
-
workflow:
|
|
3690
|
+
workflow: {
|
|
3691
|
+
sessionId: sessionId,
|
|
3692
|
+
url: tab.url,
|
|
3693
|
+
title: tab.title,
|
|
3694
|
+
actions: workflow,
|
|
3695
|
+
logs: []
|
|
3696
|
+
},
|
|
3623
3697
|
savedToServer: false
|
|
3624
3698
|
};
|
|
3625
3699
|
}
|
|
3626
3700
|
} catch (error) {
|
|
3627
3701
|
console.error('Error saving workflow:', error);
|
|
3702
|
+
// Clean up on error too
|
|
3703
|
+
await chrome.storage.local.remove(['currentWorkflowId']);
|
|
3704
|
+
|
|
3705
|
+
// Return structured response even on error
|
|
3628
3706
|
return {
|
|
3629
3707
|
success: true,
|
|
3630
|
-
workflow:
|
|
3708
|
+
workflow: {
|
|
3709
|
+
sessionId: sessionId,
|
|
3710
|
+
url: tab?.url || 'unknown',
|
|
3711
|
+
title: tab?.title || 'Untitled',
|
|
3712
|
+
actions: workflow,
|
|
3713
|
+
logs: []
|
|
3714
|
+
},
|
|
3631
3715
|
savedToServer: false,
|
|
3632
3716
|
error: error.message
|
|
3633
3717
|
};
|
|
@@ -3638,6 +3722,13 @@ async function stopWorkflowRecording(tabId) {
|
|
|
3638
3722
|
}
|
|
3639
3723
|
}
|
|
3640
3724
|
|
|
3725
|
+
// CRITICAL FIX: Stop workflow console log capture and restore original methods
|
|
3726
|
+
// REFACTORED: Now uses shared console-interception-library.js via proper namespace
|
|
3727
|
+
async function stopCapturingWorkflowLogs(tabId) {
|
|
3728
|
+
console.log('[Workflow Recording] Stopping console interception using shared library');
|
|
3729
|
+
return await self.ConsoleInterceptionLibrary.stopConsoleInterception(tabId, WORKFLOW_RECORDING_CONSOLE_CONFIG);
|
|
3730
|
+
}
|
|
3731
|
+
|
|
3641
3732
|
// Delete recording from server
|
|
3642
3733
|
async function deleteRecordingFromServer(recordingId, sendResponse) {
|
|
3643
3734
|
try {
|
|
@@ -3697,13 +3788,28 @@ async function retryPendingFrames(sessionId) {
|
|
|
3697
3788
|
|
|
3698
3789
|
console.log(`[FrameQueue] Retrying ${pending.length} pending frame batches for session ${sessionId}`);
|
|
3699
3790
|
|
|
3791
|
+
// Check if this is still the active recording session
|
|
3792
|
+
if (currentSession?.sessionId !== sessionId) {
|
|
3793
|
+
console.log(`[FrameQueue] Session ${sessionId} is no longer active, clearing pending frames`);
|
|
3794
|
+
pendingFrameQueue.delete(sessionId);
|
|
3795
|
+
return;
|
|
3796
|
+
}
|
|
3797
|
+
|
|
3700
3798
|
// Validate session now
|
|
3701
3799
|
if (sessionManager) {
|
|
3702
3800
|
const validationResult = await sessionManager.isSessionValid(sessionId);
|
|
3703
3801
|
if (!validationResult.valid) {
|
|
3704
3802
|
console.warn(`[FrameQueue] Session still invalid during retry: ${validationResult.reason}`);
|
|
3705
|
-
// Try again after another delay
|
|
3706
|
-
|
|
3803
|
+
// Try again after another delay (max 3 retries)
|
|
3804
|
+
const retryCount = pending[0]?.retryCount || 0;
|
|
3805
|
+
if (retryCount < 3) {
|
|
3806
|
+
// Mark all batches with retry count
|
|
3807
|
+
pending.forEach(batch => batch.retryCount = retryCount + 1);
|
|
3808
|
+
setTimeout(() => retryPendingFrames(sessionId), 200);
|
|
3809
|
+
} else {
|
|
3810
|
+
console.error(`[FrameQueue] Max retries (3) exceeded for session ${sessionId}, clearing queue`);
|
|
3811
|
+
pendingFrameQueue.delete(sessionId);
|
|
3812
|
+
}
|
|
3707
3813
|
return;
|
|
3708
3814
|
}
|
|
3709
3815
|
}
|