@dynamicu/chromedebug-mcp 2.6.6 → 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/activation-manager.js +18 -4
- package/chrome-extension/background.js +1044 -552
- package/chrome-extension/browser-recording-manager.js +256 -0
- 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 +617 -215
- package/chrome-extension/data-buffer.js +206 -17
- package/chrome-extension/extension-config.js +1 -1
- package/chrome-extension/frame-capture.js +52 -15
- package/chrome-extension/license-helper.js +26 -0
- package/chrome-extension/manifest.free.json +3 -6
- package/chrome-extension/options.js +1 -1
- package/chrome-extension/popup.html +315 -181
- package/chrome-extension/popup.js +673 -526
- 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/config/chromedebug-config.json +101 -0
- package/dist/chromedebug-extension-free.zip +0 -0
- package/package.json +3 -1
- package/scripts/package-pro-extension.js +1 -1
- package/scripts/webpack.config.free.cjs +11 -8
- package/scripts/webpack.config.pro.cjs +5 -0
- package/src/chrome-controller.js +7 -7
- package/src/cli.js +2 -2
- package/src/database.js +61 -9
- package/src/http-server.js +3 -2
- 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 +36 -6
- 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
|
@@ -2,29 +2,9 @@
|
|
|
2
2
|
const EXTENSION_VERSION = '2.0.4-BUILD-20250119';
|
|
3
3
|
console.log(`[popup.js] Loaded version: ${EXTENSION_VERSION}`);
|
|
4
4
|
|
|
5
|
-
//
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
let licenseClient = null;
|
|
9
|
-
let currentUserId = null;
|
|
10
|
-
|
|
11
|
-
// Load Firebase asynchronously to avoid blocking module execution
|
|
12
|
-
(async () => {
|
|
13
|
-
try {
|
|
14
|
-
const firebaseClientModule = await import('./firebase-client.js');
|
|
15
|
-
FirebaseLicenseClient = firebaseClientModule.FirebaseLicenseClient;
|
|
16
|
-
|
|
17
|
-
const firebaseConfigModule = await import('./firebase-config.public.js');
|
|
18
|
-
LEMONSQUEEZY_CHECKOUT_URL = firebaseConfigModule.LEMONSQUEEZY_CHECKOUT_URL;
|
|
19
|
-
|
|
20
|
-
// Initialize license client if Firebase loaded successfully
|
|
21
|
-
licenseClient = new FirebaseLicenseClient();
|
|
22
|
-
console.log('[popup.js] Firebase license client initialized');
|
|
23
|
-
} catch (error) {
|
|
24
|
-
console.warn('[popup.js] Firebase not available - license features disabled:', error);
|
|
25
|
-
// Extension will work without Firebase, just no license validation
|
|
26
|
-
}
|
|
27
|
-
})();
|
|
5
|
+
// Chrome Web Store URLs for upgrade flow
|
|
6
|
+
const CHROME_STORE_PRO_URL = 'https://chrome.google.com/webstore/detail/chromedebug-mcp-pro/[PRO_EXTENSION_ID]';
|
|
7
|
+
const IS_PRO_VERSION = chrome.runtime.getManifest().name.includes('PRO');
|
|
28
8
|
|
|
29
9
|
// Global variables for recording functionality
|
|
30
10
|
let isRecording = false;
|
|
@@ -46,6 +26,69 @@ let workflowStartTime = null;
|
|
|
46
26
|
// Debounce timer for screenshot quality slider
|
|
47
27
|
let screenshotQualityDebounceTimer = null;
|
|
48
28
|
|
|
29
|
+
// Toast notification function
|
|
30
|
+
function showToast(message, duration = 2000, type = 'info') {
|
|
31
|
+
// Create toast element
|
|
32
|
+
const toast = document.createElement('div');
|
|
33
|
+
toast.className = `toast toast-${type}`;
|
|
34
|
+
toast.textContent = message;
|
|
35
|
+
toast.style.cssText = `
|
|
36
|
+
position: fixed;
|
|
37
|
+
top: 20px;
|
|
38
|
+
right: 20px;
|
|
39
|
+
padding: 12px 20px;
|
|
40
|
+
background: ${type === 'error' ? '#f44336' : type === 'success' ? '#4CAF50' : '#2196F3'};
|
|
41
|
+
color: white;
|
|
42
|
+
border-radius: 4px;
|
|
43
|
+
box-shadow: 0 2px 5px rgba(0,0,0,0.3);
|
|
44
|
+
z-index: 10000;
|
|
45
|
+
font-size: 14px;
|
|
46
|
+
animation: slideInRight 0.3s ease-out;
|
|
47
|
+
`;
|
|
48
|
+
|
|
49
|
+
document.body.appendChild(toast);
|
|
50
|
+
|
|
51
|
+
// Auto remove after duration
|
|
52
|
+
setTimeout(() => {
|
|
53
|
+
toast.style.animation = 'slideOutRight 0.3s ease-in';
|
|
54
|
+
setTimeout(() => toast.remove(), 300);
|
|
55
|
+
}, duration);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Loading overlay helper functions
|
|
59
|
+
function showSectionLoading(sectionId, message = 'Loading...') {
|
|
60
|
+
console.log(`showSectionLoading called for: ${sectionId} with message: ${message}`);
|
|
61
|
+
const section = document.getElementById(sectionId);
|
|
62
|
+
if (!section) {
|
|
63
|
+
console.error(`Section not found: ${sectionId}`);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
console.log(`Section found:`, section);
|
|
67
|
+
|
|
68
|
+
// Remove any existing overlay first
|
|
69
|
+
hideSectionLoading(sectionId);
|
|
70
|
+
|
|
71
|
+
const overlay = document.createElement('div');
|
|
72
|
+
overlay.className = 'recording-section-overlay';
|
|
73
|
+
overlay.innerHTML = `
|
|
74
|
+
<div class="spinner"></div>
|
|
75
|
+
<div class="loading-text">${message}</div>
|
|
76
|
+
`;
|
|
77
|
+
overlay.setAttribute('data-loading-overlay', 'true');
|
|
78
|
+
section.appendChild(overlay);
|
|
79
|
+
console.log(`Overlay appended to section. Overlay:`, overlay);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function hideSectionLoading(sectionId) {
|
|
83
|
+
const section = document.getElementById(sectionId);
|
|
84
|
+
if (!section) return;
|
|
85
|
+
|
|
86
|
+
const overlay = section.querySelector('[data-loading-overlay="true"]');
|
|
87
|
+
if (overlay) {
|
|
88
|
+
overlay.remove();
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
49
92
|
// Workflow recording functions
|
|
50
93
|
function updateWorkflowRecordingUI() {
|
|
51
94
|
const workflowBtn = document.getElementById('workflowRecordBtn');
|
|
@@ -319,10 +362,13 @@ async function checkServerStatus() {
|
|
|
319
362
|
});
|
|
320
363
|
|
|
321
364
|
clearTimeout(timeoutId);
|
|
322
|
-
|
|
365
|
+
// Only log successful connections to reduce console noise
|
|
366
|
+
if (response.ok) {
|
|
367
|
+
console.log(`[popup.js] ✓ Connected to server on port ${port}`);
|
|
368
|
+
}
|
|
323
369
|
return response.ok;
|
|
324
370
|
} catch (error) {
|
|
325
|
-
|
|
371
|
+
// Silent failure - port discovery is expected to fail for most ports
|
|
326
372
|
return false;
|
|
327
373
|
}
|
|
328
374
|
};
|
|
@@ -333,7 +379,6 @@ async function checkServerStatus() {
|
|
|
333
379
|
if (await tryPort(cachedPort.lastSuccessfulPort, 500)) {
|
|
334
380
|
connected = true;
|
|
335
381
|
connectedPort = cachedPort.lastSuccessfulPort;
|
|
336
|
-
console.log(`[popup.js] Connected to cached port ${connectedPort}`);
|
|
337
382
|
}
|
|
338
383
|
}
|
|
339
384
|
|
|
@@ -344,7 +389,6 @@ async function checkServerStatus() {
|
|
|
344
389
|
if (await tryPort(port, 500)) {
|
|
345
390
|
connected = true;
|
|
346
391
|
connectedPort = port;
|
|
347
|
-
console.log(`[popup.js] Connected to common port ${connectedPort}`);
|
|
348
392
|
break;
|
|
349
393
|
}
|
|
350
394
|
}
|
|
@@ -356,7 +400,6 @@ async function checkServerStatus() {
|
|
|
356
400
|
if (await tryPort(port, 1000)) {
|
|
357
401
|
connected = true;
|
|
358
402
|
connectedPort = port;
|
|
359
|
-
console.log(`[popup.js] Connected to extended port ${connectedPort}`);
|
|
360
403
|
break;
|
|
361
404
|
}
|
|
362
405
|
}
|
|
@@ -368,7 +411,6 @@ async function checkServerStatus() {
|
|
|
368
411
|
if (await tryPort(port, 1000)) {
|
|
369
412
|
connected = true;
|
|
370
413
|
connectedPort = port;
|
|
371
|
-
console.log(`[popup.js] Connected to alternative port ${connectedPort}`);
|
|
372
414
|
break;
|
|
373
415
|
}
|
|
374
416
|
}
|
|
@@ -377,18 +419,13 @@ async function checkServerStatus() {
|
|
|
377
419
|
// Cache the successful port for faster connection next time
|
|
378
420
|
if (connected && connectedPort) {
|
|
379
421
|
await chrome.storage.local.set({ lastSuccessfulPort: connectedPort });
|
|
422
|
+
} else {
|
|
423
|
+
console.log('[popup.js] No server found - browser-only mode active');
|
|
380
424
|
}
|
|
381
425
|
|
|
382
426
|
if (connected) {
|
|
383
427
|
statusEl.className = 'server-status connected';
|
|
384
|
-
statusTextEl.
|
|
385
|
-
Server connected (port ${connectedPort})
|
|
386
|
-
<div style="margin-top: 4px;">
|
|
387
|
-
<a href="https://www.npmjs.com/package/@dynamicu/chromedebug-mcp" target="_blank" style="font-size: 10px; color: #666; text-decoration: none;">
|
|
388
|
-
📚 Documentation
|
|
389
|
-
</a>
|
|
390
|
-
</div>
|
|
391
|
-
`;
|
|
428
|
+
statusTextEl.textContent = `Server connected (port ${connectedPort})`;
|
|
392
429
|
} else {
|
|
393
430
|
statusEl.className = 'server-status disconnected';
|
|
394
431
|
|
|
@@ -408,9 +445,6 @@ async function checkServerStatus() {
|
|
|
408
445
|
<button id="copyInstallCmd" style="padding: 4px 8px; font-size: 11px; background: #2196F3; color: white; border: none; border-radius: 3px; cursor: pointer;">
|
|
409
446
|
📋 Copy Commands
|
|
410
447
|
</button>
|
|
411
|
-
<a href="https://www.npmjs.com/package/@dynamicu/chromedebug-mcp" target="_blank" style="font-size: 11px; color: #2196F3; text-decoration: none;">
|
|
412
|
-
📚 Full Documentation
|
|
413
|
-
</a>
|
|
414
448
|
</div>
|
|
415
449
|
`;
|
|
416
450
|
|
|
@@ -865,7 +899,7 @@ function updateRecordingsDisplay(recordings) {
|
|
|
865
899
|
if (type === 'snapshot') {
|
|
866
900
|
prompt = `Please use the get_frame_screenshot function in Chrome Debug to view the snapshot "${recordingId}"${portText}. This is a single-frame screenshot with console logs captured for debugging.`;
|
|
867
901
|
} else {
|
|
868
|
-
prompt = `Please use the
|
|
902
|
+
prompt = `Please use the chrome_debug_show_frames function in Chrome Debug to load the recording "${recordingId}"${portText}.`;
|
|
869
903
|
}
|
|
870
904
|
|
|
871
905
|
navigator.clipboard.writeText(prompt).then(() => {
|
|
@@ -902,7 +936,16 @@ function updateRecordingsDisplay(recordings) {
|
|
|
902
936
|
const recordingId = e.target.getAttribute('data-id');
|
|
903
937
|
const type = e.target.getAttribute('data-type');
|
|
904
938
|
|
|
905
|
-
//
|
|
939
|
+
// First check if server is available (for server recordings)
|
|
940
|
+
const serverStatus = await checkServerStatus();
|
|
941
|
+
|
|
942
|
+
if (!serverStatus.connected) {
|
|
943
|
+
// No server - this is a server recording that requires server to view
|
|
944
|
+
alert('This recording requires Chrome Debug server to view.\n\nStart the server with: chromedebug-mcp-server\n\nOr delete this old recording and create new browser-only recordings with Export functionality.');
|
|
945
|
+
return;
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
// Server available - check if pro version (frame editor) is available
|
|
906
949
|
try {
|
|
907
950
|
const frameEditorUrl = type === 'snapshot'
|
|
908
951
|
? chrome.runtime.getURL(`pro/frame-editor.html?sessionId=${recordingId}&type=snapshot`)
|
|
@@ -929,350 +972,71 @@ function updateRecordingsDisplay(recordings) {
|
|
|
929
972
|
|
|
930
973
|
|
|
931
974
|
|
|
932
|
-
// Initialize
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
if (!stored.chromedebug_user_id) {
|
|
941
|
-
await chrome.storage.local.set({chromedebug_user_id: currentUserId});
|
|
942
|
-
console.log('[License] Created new user ID:', currentUserId);
|
|
943
|
-
}
|
|
944
|
-
|
|
945
|
-
// Check for license activation file and pre-fill license key
|
|
946
|
-
try {
|
|
947
|
-
const activationFileUrl = chrome.runtime.getURL('license-activation.json');
|
|
948
|
-
const response = await fetch(activationFileUrl);
|
|
949
|
-
if (response.ok) {
|
|
950
|
-
const activationData = await response.json();
|
|
951
|
-
if (activationData.license_key) {
|
|
952
|
-
const licenseKeyInput = document.getElementById('license-key-input');
|
|
953
|
-
if (licenseKeyInput) {
|
|
954
|
-
licenseKeyInput.value = activationData.license_key;
|
|
955
|
-
console.log('[License UI] Pre-filled license key from activation file');
|
|
956
|
-
}
|
|
957
|
-
}
|
|
975
|
+
// Initialize tier status display for FREE version
|
|
976
|
+
function initializeTierStatus() {
|
|
977
|
+
if (IS_PRO_VERSION) {
|
|
978
|
+
// Hide license section for PRO version
|
|
979
|
+
const licenseSection = document.getElementById('license-section');
|
|
980
|
+
if (licenseSection) {
|
|
981
|
+
licenseSection.style.display = 'none';
|
|
982
|
+
console.log('[Tier] PRO version detected - hiding license section');
|
|
958
983
|
}
|
|
959
|
-
} catch (error) {
|
|
960
|
-
// Activation file doesn't exist or is inaccessible - this is normal for FREE version
|
|
961
|
-
console.log('[License UI] No activation file found - normal for FREE version');
|
|
962
|
-
}
|
|
963
|
-
|
|
964
|
-
// Check license status
|
|
965
|
-
const licenseStatus = await licenseClient.getCachedLicenseStatus();
|
|
966
|
-
console.log('[License] License status:', licenseStatus);
|
|
967
|
-
|
|
968
|
-
// Update workflow recording PRO badge visibility
|
|
969
|
-
const workflowProBadge = document.getElementById('workflowProBadge');
|
|
970
|
-
|
|
971
|
-
if (licenseStatus.valid && licenseStatus.tier === 'pro') {
|
|
972
|
-
// Show pro status
|
|
973
|
-
document.getElementById('pro-tier-status').style.display = 'block';
|
|
974
|
-
document.getElementById('free-tier-status').style.display = 'none';
|
|
975
|
-
// Hide license activation input/button when Pro license is active
|
|
976
|
-
document.getElementById('license-activation').style.display = 'none';
|
|
977
|
-
// Hide PRO badge for Pro users
|
|
978
|
-
if (workflowProBadge) workflowProBadge.style.display = 'none';
|
|
979
|
-
console.log('[License] Displaying Pro tier status');
|
|
980
984
|
} else {
|
|
981
|
-
// Show
|
|
982
|
-
const
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
// Handle both API response format (currentUsage, dailyLimit) and offline format (count, limit)
|
|
986
|
-
document.getElementById('usage-count').textContent = usage.currentUsage ?? usage.count ?? 0;
|
|
987
|
-
document.getElementById('usage-limit').textContent = usage.dailyLimit ?? usage.limit ?? 50;
|
|
988
|
-
document.getElementById('free-tier-status').style.display = 'block';
|
|
989
|
-
document.getElementById('pro-tier-status').style.display = 'none';
|
|
990
|
-
// Show license activation input/button for free tier
|
|
991
|
-
document.getElementById('license-activation').style.display = 'block';
|
|
992
|
-
// Show PRO badge for free users
|
|
993
|
-
if (workflowProBadge) workflowProBadge.style.display = 'block';
|
|
994
|
-
console.log('[License] Displaying Free tier status');
|
|
995
|
-
}
|
|
996
|
-
}
|
|
997
|
-
|
|
998
|
-
// Inline Activation Manager Functions
|
|
999
|
-
/**
|
|
1000
|
-
* Show the inline activation manager with slide-down animation
|
|
1001
|
-
*/
|
|
1002
|
-
async function showInlineActivationManager(licenseKey) {
|
|
1003
|
-
const inlineManager = document.getElementById('inline-activation-manager');
|
|
1004
|
-
const activationsList = document.getElementById('activations-list-inline');
|
|
1005
|
-
|
|
1006
|
-
// Show the container
|
|
1007
|
-
inlineManager.style.display = 'block';
|
|
1008
|
-
|
|
1009
|
-
// Trigger animation
|
|
1010
|
-
setTimeout(() => {
|
|
1011
|
-
inlineManager.style.maxHeight = '400px';
|
|
1012
|
-
}, 10);
|
|
1013
|
-
|
|
1014
|
-
// Load activations
|
|
1015
|
-
try {
|
|
1016
|
-
activationsList.innerHTML = '<div style="padding: 15px; text-align: center; color: #666;">Loading activations...</div>';
|
|
1017
|
-
|
|
1018
|
-
// Get current instance ID
|
|
1019
|
-
const stored = await chrome.storage.local.get(['chromedebug_instance_id']);
|
|
1020
|
-
const currentInstanceId = stored.chromedebug_instance_id;
|
|
985
|
+
// Show FREE tier status (no license validation)
|
|
986
|
+
const freeTierStatus = document.getElementById('free-tier-status');
|
|
987
|
+
const proTierStatus = document.getElementById('pro-tier-status');
|
|
988
|
+
const licenseActivation = document.getElementById('license-activation');
|
|
1021
989
|
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
990
|
+
if (freeTierStatus) freeTierStatus.style.display = 'block';
|
|
991
|
+
if (proTierStatus) proTierStatus.style.display = 'none';
|
|
992
|
+
if (licenseActivation) licenseActivation.style.display = 'none';
|
|
1025
993
|
|
|
1026
|
-
//
|
|
1027
|
-
|
|
994
|
+
// Fetch and display current usage count
|
|
995
|
+
updateUsageDisplay();
|
|
1028
996
|
|
|
1029
|
-
|
|
1030
|
-
console.error('[License] Error loading activations:', error);
|
|
1031
|
-
activationsList.innerHTML = `<div style="padding: 15px; text-align: center; color: #f44336;">Failed to load activations: ${error.message}</div>`;
|
|
997
|
+
console.log('[Tier] FREE version - showing usage counter');
|
|
1032
998
|
}
|
|
1033
999
|
}
|
|
1034
1000
|
|
|
1035
1001
|
/**
|
|
1036
|
-
*
|
|
1037
|
-
*/
|
|
1038
|
-
function hideInlineActivationManager() {
|
|
1039
|
-
const inlineManager = document.getElementById('inline-activation-manager');
|
|
1040
|
-
inlineManager.style.maxHeight = '0';
|
|
1041
|
-
|
|
1042
|
-
setTimeout(() => {
|
|
1043
|
-
inlineManager.style.display = 'none';
|
|
1044
|
-
}, 300);
|
|
1045
|
-
}
|
|
1046
|
-
|
|
1047
|
-
/**
|
|
1048
|
-
* Render activations in the inline manager
|
|
1002
|
+
* Fetch usage from background and update the display
|
|
1049
1003
|
*/
|
|
1050
|
-
function
|
|
1051
|
-
const container = document.getElementById('activations-list-inline');
|
|
1052
|
-
container.innerHTML = '';
|
|
1053
|
-
|
|
1054
|
-
if (activations.length === 0) {
|
|
1055
|
-
container.innerHTML = '<div style="padding: 15px; text-align: center; color: #666;">No activations found.</div>';
|
|
1056
|
-
return;
|
|
1057
|
-
}
|
|
1058
|
-
|
|
1059
|
-
activations.forEach((activation, index) => {
|
|
1060
|
-
const item = document.createElement('div');
|
|
1061
|
-
item.style.cssText = 'padding: 12px; border-bottom: 1px solid #e0e0e0; transition: background 0.2s;';
|
|
1062
|
-
item.onmouseenter = () => item.style.background = '#f9f9f9';
|
|
1063
|
-
item.onmouseleave = () => item.style.background = isCurrentDevice ? '#e8f5e9' : 'white';
|
|
1064
|
-
|
|
1065
|
-
// Check if this is the current device
|
|
1066
|
-
const isCurrentDevice = activation.name === currentInstanceId || activation.identifier === currentInstanceId;
|
|
1067
|
-
|
|
1068
|
-
if (isCurrentDevice) {
|
|
1069
|
-
item.style.background = '#e8f5e9';
|
|
1070
|
-
item.style.borderLeft = '3px solid #4caf50';
|
|
1071
|
-
}
|
|
1072
|
-
|
|
1073
|
-
// Format date
|
|
1074
|
-
const date = new Date(activation.createdAt);
|
|
1075
|
-
const formattedDate = date.toLocaleString('en-US', {
|
|
1076
|
-
month: 'short',
|
|
1077
|
-
day: 'numeric',
|
|
1078
|
-
year: 'numeric',
|
|
1079
|
-
hour: '2-digit',
|
|
1080
|
-
minute: '2-digit'
|
|
1081
|
-
});
|
|
1082
|
-
|
|
1083
|
-
// Build device info
|
|
1084
|
-
let deviceDisplay = 'Unknown Device';
|
|
1085
|
-
if (activation.deviceInfo) {
|
|
1086
|
-
deviceDisplay = activation.deviceInfo.deviceName ||
|
|
1087
|
-
`${activation.deviceInfo.platform || 'Unknown'} • ${activation.deviceInfo.browser || 'Unknown'}`;
|
|
1088
|
-
} else if (activation.name && activation.name !== currentInstanceId) {
|
|
1089
|
-
deviceDisplay = activation.name;
|
|
1090
|
-
}
|
|
1091
|
-
|
|
1092
|
-
item.innerHTML = `
|
|
1093
|
-
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;">
|
|
1094
|
-
<span style="font-weight: 500; font-size: 13px; color: #333;">Activation ${index + 1}</span>
|
|
1095
|
-
${isCurrentDevice ? '<span style="background: #4caf50; color: white; padding: 2px 6px; border-radius: 10px; font-size: 10px; font-weight: bold;">CURRENT</span>' : ''}
|
|
1096
|
-
</div>
|
|
1097
|
-
<div style="font-size: 12px; color: #666; margin-bottom: 4px;">
|
|
1098
|
-
<strong style="color: #333;">${deviceDisplay}</strong>
|
|
1099
|
-
</div>
|
|
1100
|
-
<div style="font-size: 11px; color: #999; margin-bottom: 8px;">
|
|
1101
|
-
${formattedDate}
|
|
1102
|
-
</div>
|
|
1103
|
-
<button
|
|
1104
|
-
class="deactivate-btn-inline"
|
|
1105
|
-
data-instance-id="${activation.identifier}"
|
|
1106
|
-
data-activation-number="${index + 1}"
|
|
1107
|
-
data-license-key="${licenseKey}"
|
|
1108
|
-
style="padding: 5px 10px; background: ${isCurrentDevice ? '#ccc' : '#f44336'}; color: white; border: none; border-radius: 3px; cursor: ${isCurrentDevice ? 'not-allowed' : 'pointer'}; font-size: 11px; width: 100%;"
|
|
1109
|
-
${isCurrentDevice ? 'disabled' : ''}
|
|
1110
|
-
>
|
|
1111
|
-
${isCurrentDevice ? '✓ Current Device' : 'Deactivate'}
|
|
1112
|
-
</button>
|
|
1113
|
-
`;
|
|
1114
|
-
|
|
1115
|
-
// Add click handler for deactivate button
|
|
1116
|
-
if (!isCurrentDevice) {
|
|
1117
|
-
const deactivateBtn = item.querySelector('.deactivate-btn-inline');
|
|
1118
|
-
deactivateBtn.addEventListener('click', () => {
|
|
1119
|
-
handleInlineDeactivate(activation.identifier, index + 1, licenseKey);
|
|
1120
|
-
});
|
|
1121
|
-
}
|
|
1122
|
-
|
|
1123
|
-
container.appendChild(item);
|
|
1124
|
-
});
|
|
1125
|
-
}
|
|
1126
|
-
|
|
1127
|
-
/**
|
|
1128
|
-
* Handle deactivation of an instance (inline)
|
|
1129
|
-
*/
|
|
1130
|
-
async function handleInlineDeactivate(instanceId, activationNumber, licenseKey) {
|
|
1131
|
-
const confirmed = confirm(
|
|
1132
|
-
`Are you sure you want to deactivate Activation ${activationNumber}?\n\n` +
|
|
1133
|
-
`This will free up an activation slot and automatically activate on this device.`
|
|
1134
|
-
);
|
|
1135
|
-
|
|
1136
|
-
if (!confirmed) {
|
|
1137
|
-
return;
|
|
1138
|
-
}
|
|
1139
|
-
|
|
1140
|
-
const messageDiv = document.getElementById('license-message');
|
|
1141
|
-
|
|
1004
|
+
async function updateUsageDisplay() {
|
|
1142
1005
|
try {
|
|
1143
|
-
|
|
1144
|
-
const buttons = document.querySelectorAll('.deactivate-btn-inline');
|
|
1145
|
-
buttons.forEach(btn => btn.disabled = true);
|
|
1146
|
-
|
|
1147
|
-
messageDiv.textContent = 'Deactivating...';
|
|
1148
|
-
messageDiv.style.color = '#2196F3';
|
|
1149
|
-
|
|
1150
|
-
// Deactivate the instance
|
|
1151
|
-
const result = await licenseClient.deactivateInstance(licenseKey, instanceId);
|
|
1152
|
-
console.log('[License] Deactivation result:', result);
|
|
1153
|
-
|
|
1154
|
-
if (result.deactivated) {
|
|
1155
|
-
messageDiv.textContent = 'Deactivated! Activating on this device...';
|
|
1156
|
-
messageDiv.style.color = '#4caf50';
|
|
1006
|
+
const response = await chrome.runtime.sendMessage({ action: 'getUsage' });
|
|
1157
1007
|
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
// Wait a moment, then retry activation
|
|
1162
|
-
setTimeout(async () => {
|
|
1163
|
-
// Set the license key in the input
|
|
1164
|
-
document.getElementById('license-key-input').value = licenseKey;
|
|
1008
|
+
if (response && response.success) {
|
|
1009
|
+
const usageCountEl = document.getElementById('usage-count');
|
|
1010
|
+
const usageLimitEl = document.getElementById('usage-limit');
|
|
1165
1011
|
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
}
|
|
1012
|
+
if (usageCountEl) {
|
|
1013
|
+
usageCountEl.textContent = response.count || 0;
|
|
1014
|
+
}
|
|
1015
|
+
if (usageLimitEl) {
|
|
1016
|
+
usageLimitEl.textContent = response.limit || 5;
|
|
1017
|
+
}
|
|
1169
1018
|
|
|
1170
|
-
|
|
1171
|
-
throw new Error(result.error || 'Deactivation failed');
|
|
1019
|
+
console.log(`[Usage] Display updated: ${response.count}/${response.limit}`);
|
|
1172
1020
|
}
|
|
1173
|
-
|
|
1174
1021
|
} catch (error) {
|
|
1175
|
-
console.error('[
|
|
1176
|
-
messageDiv.textContent = `Deactivation failed: ${error.message}`;
|
|
1177
|
-
messageDiv.style.color = '#f44336';
|
|
1178
|
-
|
|
1179
|
-
// Re-enable buttons
|
|
1180
|
-
const buttons = document.querySelectorAll('.deactivate-btn-inline:not([disabled])');
|
|
1181
|
-
buttons.forEach(btn => btn.disabled = false);
|
|
1022
|
+
console.error('[Usage] Failed to fetch usage:', error);
|
|
1182
1023
|
}
|
|
1183
1024
|
}
|
|
1184
1025
|
|
|
1185
|
-
//
|
|
1186
|
-
async function handleLicenseActivation() {
|
|
1187
|
-
const licenseKey = document.getElementById('license-key-input').value.trim();
|
|
1188
|
-
const messageDiv = document.getElementById('license-message');
|
|
1189
|
-
|
|
1190
|
-
if (!licenseKey) {
|
|
1191
|
-
messageDiv.textContent = 'Please enter a license key';
|
|
1192
|
-
messageDiv.style.color = '#f44336';
|
|
1193
|
-
return;
|
|
1194
|
-
}
|
|
1195
|
-
|
|
1196
|
-
messageDiv.textContent = 'Validating...';
|
|
1197
|
-
messageDiv.style.color = '#2196F3';
|
|
1198
|
-
console.log('[License] Validating license key...');
|
|
1199
|
-
|
|
1200
|
-
// Clear any existing instance ID to force fresh activation
|
|
1201
|
-
// This ensures we don't try to validate with stale/deleted instance IDs
|
|
1202
|
-
await chrome.storage.local.remove(['ls_instance_id']);
|
|
1203
|
-
console.log('[License] Cleared existing instance ID for fresh activation');
|
|
1204
|
-
|
|
1205
|
-
const result = await licenseClient.validateLicense(licenseKey);
|
|
1206
|
-
console.log('[License] Validation result:', result);
|
|
1207
|
-
console.log('[License] Error value:', result.error);
|
|
1208
|
-
console.log('[License] Error type:', typeof result.error);
|
|
1209
|
-
|
|
1210
|
-
if (result.valid) {
|
|
1211
|
-
messageDiv.textContent = 'License activated successfully!';
|
|
1212
|
-
messageDiv.style.color = '#4caf50';
|
|
1213
|
-
document.getElementById('license-key-input').value = '';
|
|
1214
|
-
await initializeLicenseUI(); // Refresh UI
|
|
1215
|
-
} else {
|
|
1216
|
-
// Check if activation limit reached
|
|
1217
|
-
console.log('[License] Checking if activation limit reached...');
|
|
1218
|
-
console.log('[License] result.error === "ACTIVATION_LIMIT_REACHED":', result.error === 'ACTIVATION_LIMIT_REACHED');
|
|
1219
|
-
console.log('[License] result.error includes "activation limit":', result.error && result.error.toLowerCase().includes('activation limit'));
|
|
1220
|
-
|
|
1221
|
-
if (result.error === 'ACTIVATION_LIMIT_REACHED' ||
|
|
1222
|
-
(result.error && result.error.toLowerCase().includes('activation limit'))) {
|
|
1223
|
-
console.log('[License] Activation limit reached, showing inline activation manager');
|
|
1224
|
-
messageDiv.textContent = 'Please deactivate an existing activation to continue';
|
|
1225
|
-
messageDiv.style.color = '#ff9800';
|
|
1226
|
-
|
|
1227
|
-
// Show inline activation manager
|
|
1228
|
-
await showInlineActivationManager(licenseKey.trim());
|
|
1229
|
-
} else {
|
|
1230
|
-
messageDiv.textContent = result.error || 'Invalid license key';
|
|
1231
|
-
messageDiv.style.color = '#f44336';
|
|
1232
|
-
}
|
|
1233
|
-
}
|
|
1234
|
-
}
|
|
1235
|
-
|
|
1236
|
-
// Upgrade button handler
|
|
1026
|
+
// Upgrade button handler - redirect to Chrome Web Store PRO version
|
|
1237
1027
|
function handleUpgradeClick() {
|
|
1238
|
-
console.log('[
|
|
1239
|
-
chrome.tabs.create({url:
|
|
1028
|
+
console.log('[Upgrade] Opening Chrome Web Store PRO version');
|
|
1029
|
+
chrome.tabs.create({url: CHROME_STORE_PRO_URL});
|
|
1240
1030
|
}
|
|
1241
1031
|
|
|
1242
|
-
// Listen for messages from activation manager
|
|
1243
|
-
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
|
1244
|
-
if (message.type === 'RETRY_ACTIVATION' && message.licenseKey) {
|
|
1245
|
-
console.log('[License] Retrying activation after deactivation');
|
|
1246
|
-
|
|
1247
|
-
// Set the license key in the input
|
|
1248
|
-
const licenseKeyInput = document.getElementById('license-key-input');
|
|
1249
|
-
if (licenseKeyInput) {
|
|
1250
|
-
licenseKeyInput.value = message.licenseKey;
|
|
1251
|
-
}
|
|
1252
|
-
|
|
1253
|
-
// Retry activation
|
|
1254
|
-
handleLicenseActivation().then(() => {
|
|
1255
|
-
console.log('[License] Retry activation complete');
|
|
1256
|
-
});
|
|
1257
|
-
}
|
|
1258
|
-
});
|
|
1259
|
-
|
|
1260
1032
|
document.addEventListener('DOMContentLoaded', () => {
|
|
1261
1033
|
console.log('DOM loaded, initializing popup');
|
|
1262
1034
|
|
|
1263
|
-
// Initialize
|
|
1264
|
-
|
|
1265
|
-
console.error('[License] Failed to initialize license UI:', error);
|
|
1266
|
-
});
|
|
1035
|
+
// Initialize tier status display
|
|
1036
|
+
initializeTierStatus();
|
|
1267
1037
|
|
|
1268
|
-
//
|
|
1269
|
-
const activateLicenseBtn = document.getElementById('activate-license-btn');
|
|
1038
|
+
// Upgrade button handler
|
|
1270
1039
|
const upgradeBtn = document.getElementById('upgrade-btn');
|
|
1271
|
-
|
|
1272
|
-
if (activateLicenseBtn) {
|
|
1273
|
-
activateLicenseBtn.addEventListener('click', handleLicenseActivation);
|
|
1274
|
-
}
|
|
1275
|
-
|
|
1276
1040
|
if (upgradeBtn) {
|
|
1277
1041
|
upgradeBtn.addEventListener('click', handleUpgradeClick);
|
|
1278
1042
|
}
|
|
@@ -1282,18 +1046,37 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
1282
1046
|
const serverStatus = await checkServerStatus();
|
|
1283
1047
|
const recordBtn = document.getElementById('recordBtn');
|
|
1284
1048
|
const workflowRecordBtn = document.getElementById('workflowRecordBtn');
|
|
1285
|
-
|
|
1049
|
+
const browserOnlyBanner = document.getElementById('browserOnlyBanner');
|
|
1050
|
+
const quotaMeter = document.getElementById('quotaMeter');
|
|
1051
|
+
|
|
1286
1052
|
if (!serverStatus.connected) {
|
|
1287
|
-
//
|
|
1288
|
-
if (
|
|
1289
|
-
|
|
1290
|
-
|
|
1053
|
+
// Show browser-only mode UI
|
|
1054
|
+
if (browserOnlyBanner) {
|
|
1055
|
+
browserOnlyBanner.style.display = 'block';
|
|
1056
|
+
}
|
|
1057
|
+
if (quotaMeter) {
|
|
1058
|
+
quotaMeter.style.display = 'block';
|
|
1059
|
+
updateQuotaUI(); // Update quota display
|
|
1291
1060
|
}
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1061
|
+
|
|
1062
|
+
// Enable recording buttons in browser-only mode
|
|
1063
|
+
if (recordBtn) {
|
|
1064
|
+
recordBtn.style.opacity = '1';
|
|
1065
|
+
recordBtn.title = 'Recording will be stored in browser storage';
|
|
1066
|
+
}
|
|
1067
|
+
if (workflowRecordBtn) {
|
|
1068
|
+
workflowRecordBtn.style.opacity = '1';
|
|
1069
|
+
workflowRecordBtn.title = 'Recording will be stored in browser storage';
|
|
1295
1070
|
}
|
|
1296
1071
|
} else {
|
|
1072
|
+
// Hide browser-only mode UI when server is connected
|
|
1073
|
+
if (browserOnlyBanner) {
|
|
1074
|
+
browserOnlyBanner.style.display = 'none';
|
|
1075
|
+
}
|
|
1076
|
+
if (quotaMeter) {
|
|
1077
|
+
quotaMeter.style.display = 'none';
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1297
1080
|
// Enable recording buttons when server is running
|
|
1298
1081
|
if (recordBtn) {
|
|
1299
1082
|
recordBtn.style.opacity = '1';
|
|
@@ -1305,11 +1088,127 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
1305
1088
|
}
|
|
1306
1089
|
}
|
|
1307
1090
|
}
|
|
1091
|
+
|
|
1092
|
+
// Update quota UI for browser-only mode
|
|
1093
|
+
async function updateQuotaUI() {
|
|
1094
|
+
try {
|
|
1095
|
+
// Request quota info from background script
|
|
1096
|
+
const response = await chrome.runtime.sendMessage({
|
|
1097
|
+
action: 'getQuotaInfo'
|
|
1098
|
+
});
|
|
1099
|
+
|
|
1100
|
+
if (response && response.quotaInfo) {
|
|
1101
|
+
const { percentage, usageMB, quotaMB, availableMB } = response.quotaInfo;
|
|
1102
|
+
|
|
1103
|
+
const quotaBar = document.getElementById('quotaBar');
|
|
1104
|
+
const quotaText = document.getElementById('quotaText');
|
|
1105
|
+
const quotaAvailable = document.getElementById('quotaAvailable');
|
|
1106
|
+
const quotaUsage = document.getElementById('quotaUsage');
|
|
1107
|
+
|
|
1108
|
+
if (quotaBar && quotaText && quotaAvailable) {
|
|
1109
|
+
const percentageValue = Math.round(percentage * 100);
|
|
1110
|
+
quotaBar.style.width = `${percentageValue}%`;
|
|
1111
|
+
|
|
1112
|
+
// Change color based on usage
|
|
1113
|
+
if (percentage > 0.8) {
|
|
1114
|
+
quotaBar.style.background = '#f44336'; // Red
|
|
1115
|
+
} else if (percentage > 0.6) {
|
|
1116
|
+
quotaBar.style.background = '#ff9800'; // Orange
|
|
1117
|
+
} else {
|
|
1118
|
+
quotaBar.style.background = '#4CAF50'; // Green
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
quotaText.textContent = `${percentageValue}% used`;
|
|
1122
|
+
quotaAvailable.textContent = `${availableMB || 0} MB available`;
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
if (quotaUsage) {
|
|
1126
|
+
quotaUsage.textContent = `${usageMB || 0} MB / ${quotaMB || 0} MB`;
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
} catch (error) {
|
|
1130
|
+
console.error('Failed to update quota UI:', error);
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1308
1133
|
|
|
1309
|
-
//
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1134
|
+
// Server polling control
|
|
1135
|
+
let serverPollingInterval = null;
|
|
1136
|
+
|
|
1137
|
+
function startPolling() {
|
|
1138
|
+
if (serverPollingInterval) {
|
|
1139
|
+
clearInterval(serverPollingInterval);
|
|
1140
|
+
}
|
|
1141
|
+
serverPollingInterval = setInterval(updateButtonStatesForServerStatus, 3000);
|
|
1142
|
+
console.log('Server polling started (every 3 seconds)');
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
function stopPolling() {
|
|
1146
|
+
if (serverPollingInterval) {
|
|
1147
|
+
clearInterval(serverPollingInterval);
|
|
1148
|
+
serverPollingInterval = null;
|
|
1149
|
+
console.log('Server polling stopped');
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
async function checkServerOnce() {
|
|
1154
|
+
console.log('Running single server check...');
|
|
1155
|
+
await updateButtonStatesForServerStatus();
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
// Initialize polling controls
|
|
1159
|
+
const pollContinuouslyCheckbox = document.getElementById('pollContinuouslyCheckbox');
|
|
1160
|
+
const retryConnectionBtn = document.getElementById('retryConnectionBtn');
|
|
1161
|
+
|
|
1162
|
+
if (pollContinuouslyCheckbox && retryConnectionBtn) {
|
|
1163
|
+
// Load saved polling preference (default: true)
|
|
1164
|
+
chrome.storage.local.get(['pollContinuously'], (result) => {
|
|
1165
|
+
const shouldPoll = result.pollContinuously !== false; // Default to true
|
|
1166
|
+
pollContinuouslyCheckbox.checked = shouldPoll;
|
|
1167
|
+
|
|
1168
|
+
// Show/hide retry button based on preference
|
|
1169
|
+
retryConnectionBtn.style.display = shouldPoll ? 'none' : 'inline-block';
|
|
1170
|
+
|
|
1171
|
+
// Initial server check
|
|
1172
|
+
updateButtonStatesForServerStatus();
|
|
1173
|
+
|
|
1174
|
+
// Start polling if enabled
|
|
1175
|
+
if (shouldPoll) {
|
|
1176
|
+
startPolling();
|
|
1177
|
+
}
|
|
1178
|
+
});
|
|
1179
|
+
|
|
1180
|
+
// Checkbox change handler
|
|
1181
|
+
pollContinuouslyCheckbox.addEventListener('change', () => {
|
|
1182
|
+
const isChecked = pollContinuouslyCheckbox.checked;
|
|
1183
|
+
|
|
1184
|
+
// Save preference
|
|
1185
|
+
chrome.storage.local.set({ pollContinuously: isChecked });
|
|
1186
|
+
|
|
1187
|
+
// Show/hide retry button
|
|
1188
|
+
retryConnectionBtn.style.display = isChecked ? 'none' : 'inline-block';
|
|
1189
|
+
|
|
1190
|
+
// Start or stop polling
|
|
1191
|
+
if (isChecked) {
|
|
1192
|
+
startPolling();
|
|
1193
|
+
} else {
|
|
1194
|
+
stopPolling();
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
console.log('Continuous polling:', isChecked ? 'enabled' : 'disabled');
|
|
1198
|
+
});
|
|
1199
|
+
|
|
1200
|
+
// Retry button click handler
|
|
1201
|
+
retryConnectionBtn.addEventListener('click', () => {
|
|
1202
|
+
showToast('Retrying connection...', 2000, 'info');
|
|
1203
|
+
checkServerOnce();
|
|
1204
|
+
});
|
|
1205
|
+
} else {
|
|
1206
|
+
// Fallback if polling controls not found - use default behavior
|
|
1207
|
+
console.warn('Polling controls not found, using default polling behavior');
|
|
1208
|
+
updateButtonStatesForServerStatus();
|
|
1209
|
+
setInterval(updateButtonStatesForServerStatus, 3000);
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1313
1212
|
// Initialize site management functionality
|
|
1314
1213
|
initializeSiteManagement();
|
|
1315
1214
|
|
|
@@ -1414,9 +1313,13 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
1414
1313
|
}
|
|
1415
1314
|
|
|
1416
1315
|
});
|
|
1417
|
-
|
|
1418
|
-
// Load saved recordings
|
|
1316
|
+
|
|
1317
|
+
// Load saved recordings (legacy)
|
|
1419
1318
|
loadRecordings();
|
|
1319
|
+
|
|
1320
|
+
// Load screen and workflow recordings with saved preferences
|
|
1321
|
+
loadScreenRecordings();
|
|
1322
|
+
loadWorkflowRecordings();
|
|
1420
1323
|
|
|
1421
1324
|
// Setup recording settings event listeners
|
|
1422
1325
|
const frameRateSelect = document.getElementById('frameRate');
|
|
@@ -1488,19 +1391,26 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
1488
1391
|
try {
|
|
1489
1392
|
// Check if server is running before starting recording
|
|
1490
1393
|
const serverStatus = await checkServerStatus();
|
|
1491
|
-
|
|
1492
|
-
|
|
1394
|
+
const isBrowserOnlyMode = !serverStatus.connected;
|
|
1395
|
+
|
|
1396
|
+
if (isBrowserOnlyMode) {
|
|
1397
|
+
console.log('Starting recording in browser-only mode');
|
|
1398
|
+
// Browser-only mode - proceed with local storage
|
|
1399
|
+
} else {
|
|
1400
|
+
console.log('Starting recording with server');
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1403
|
+
// Check FREE tier usage limit before starting
|
|
1404
|
+
const usageCheck = await chrome.runtime.sendMessage({ action: 'checkUsageLimit' });
|
|
1405
|
+
if (usageCheck && !usageCheck.allowed) {
|
|
1406
|
+
console.log('[Usage] Daily limit reached:', usageCheck);
|
|
1493
1407
|
if (recordingStatus) {
|
|
1494
|
-
recordingStatus.innerHTML = '<strong style="color: #f44336;">
|
|
1495
|
-
|
|
1408
|
+
recordingStatus.innerHTML = '<strong style="color: #f44336;">Daily limit reached</strong><br>' +
|
|
1409
|
+
`<small>${usageCheck.count}/${usageCheck.limit} recordings used today.<br>Upgrade to Pro for unlimited.</small>`;
|
|
1496
1410
|
}
|
|
1497
|
-
// Clear error message after 5 seconds
|
|
1498
|
-
setTimeout(() => {
|
|
1499
|
-
if (recordingStatus) recordingStatus.textContent = '';
|
|
1500
|
-
}, 5000);
|
|
1501
1411
|
return;
|
|
1502
1412
|
}
|
|
1503
|
-
|
|
1413
|
+
|
|
1504
1414
|
// Get the current active tab
|
|
1505
1415
|
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
|
|
1506
1416
|
console.log('Current tab:', tab);
|
|
@@ -1531,10 +1441,11 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
1531
1441
|
};
|
|
1532
1442
|
|
|
1533
1443
|
// Send message to background to start recording
|
|
1534
|
-
chrome.runtime.sendMessage({
|
|
1535
|
-
action: 'startRecording',
|
|
1444
|
+
chrome.runtime.sendMessage({
|
|
1445
|
+
action: 'startRecording',
|
|
1536
1446
|
tabId: tab.id,
|
|
1537
|
-
settings: settings
|
|
1447
|
+
settings: settings,
|
|
1448
|
+
mode: isBrowserOnlyMode ? 'browser-only' : 'server'
|
|
1538
1449
|
}, (response) => {
|
|
1539
1450
|
recordBtn.disabled = false;
|
|
1540
1451
|
|
|
@@ -1574,23 +1485,47 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
1574
1485
|
recordBtn.textContent = 'Stopping...';
|
|
1575
1486
|
recordBtn.disabled = true;
|
|
1576
1487
|
|
|
1577
|
-
chrome.runtime.sendMessage({
|
|
1488
|
+
chrome.runtime.sendMessage({
|
|
1578
1489
|
action: 'stopRecording'
|
|
1579
|
-
}, (response) => {
|
|
1490
|
+
}, async (response) => {
|
|
1580
1491
|
isStoppingRecording = false;
|
|
1581
1492
|
recordBtn.disabled = false;
|
|
1582
|
-
|
|
1493
|
+
|
|
1583
1494
|
if (chrome.runtime.lastError) {
|
|
1584
1495
|
console.error('Error stopping recording:', chrome.runtime.lastError);
|
|
1585
1496
|
recordingStatus.textContent = 'Error: ' + chrome.runtime.lastError.message;
|
|
1586
1497
|
recordBtn.textContent = 'Stop Recording';
|
|
1587
1498
|
return;
|
|
1588
1499
|
}
|
|
1589
|
-
|
|
1500
|
+
|
|
1590
1501
|
if (response && response.success) {
|
|
1591
|
-
console.log('Recording stopped successfully');
|
|
1502
|
+
console.log('Recording stopped successfully', response);
|
|
1592
1503
|
isRecording = false;
|
|
1593
1504
|
updateRecordingUI();
|
|
1505
|
+
|
|
1506
|
+
console.log('About to show loading overlay for screen-recording-section');
|
|
1507
|
+
// Show loading overlay over screen recording section
|
|
1508
|
+
showSectionLoading('screen-recording-section', 'Saving screen recording...');
|
|
1509
|
+
console.log('Loading overlay should be visible now');
|
|
1510
|
+
|
|
1511
|
+
// Use then/catch instead of async/await since we're in a callback
|
|
1512
|
+
loadScreenRecordings(true)
|
|
1513
|
+
.then(() => {
|
|
1514
|
+
console.log('Screen recordings loaded successfully');
|
|
1515
|
+
// Show success toast
|
|
1516
|
+
const frameCount = response.frameCount || 0;
|
|
1517
|
+
const duration = response.duration || '0s';
|
|
1518
|
+
showToast(`Screen recording saved! ${frameCount} frames (${duration})`, 3000, 'success');
|
|
1519
|
+
})
|
|
1520
|
+
.catch(error => {
|
|
1521
|
+
console.error('Error loading screen recordings:', error);
|
|
1522
|
+
showToast('Error loading recordings. Please refresh.', 3000, 'error');
|
|
1523
|
+
})
|
|
1524
|
+
.finally(() => {
|
|
1525
|
+
console.log('Hiding loading overlay');
|
|
1526
|
+
// Always hide loading overlay
|
|
1527
|
+
hideSectionLoading('screen-recording-section');
|
|
1528
|
+
});
|
|
1594
1529
|
} else {
|
|
1595
1530
|
recordingStatus.textContent = 'Error: ' + (response?.error || 'Failed to stop recording');
|
|
1596
1531
|
recordBtn.textContent = 'Stop Recording';
|
|
@@ -1654,26 +1589,16 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
1654
1589
|
return;
|
|
1655
1590
|
}
|
|
1656
1591
|
|
|
1657
|
-
// Check
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
if (!licenseCheck || !licenseCheck.allowed || (licenseCheck.tier && licenseCheck.tier !== 'pro')) {
|
|
1666
|
-
const message = licenseCheck?.message || 'Workflow Recording is a Pro feature. Upgrade to Pro for unlimited workflow recordings with function tracing.';
|
|
1667
|
-
|
|
1668
|
-
// Show modal
|
|
1669
|
-
if (confirm(`${message}\n\nWould you like to upgrade now?`)) {
|
|
1670
|
-
// Open upgrade page
|
|
1671
|
-
chrome.tabs.create({
|
|
1672
|
-
url: LEMONSQUEEZY_CHECKOUT_URL
|
|
1673
|
-
});
|
|
1592
|
+
// Check FREE tier usage limit before starting
|
|
1593
|
+
const usageCheck = await chrome.runtime.sendMessage({ action: 'checkUsageLimit' });
|
|
1594
|
+
if (usageCheck && !usageCheck.allowed) {
|
|
1595
|
+
console.log('[Usage] Daily limit reached:', usageCheck);
|
|
1596
|
+
const workflowStatus = document.getElementById('workflowRecordingStatus');
|
|
1597
|
+
if (workflowStatus) {
|
|
1598
|
+
workflowStatus.innerHTML = '<strong style="color: #f44336;">Daily limit reached</strong><br>' +
|
|
1599
|
+
`<small>${usageCheck.count}/${usageCheck.limit} recordings used today.<br>Upgrade to Pro for unlimited recordings.</small>`;
|
|
1674
1600
|
}
|
|
1675
|
-
|
|
1676
|
-
return; // Stop execution
|
|
1601
|
+
return;
|
|
1677
1602
|
}
|
|
1678
1603
|
|
|
1679
1604
|
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
|
|
@@ -1822,86 +1747,29 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
1822
1747
|
if (response && response.success && response.workflow) {
|
|
1823
1748
|
isWorkflowRecording = false;
|
|
1824
1749
|
// Clear state from storage
|
|
1825
|
-
chrome.storage.local.set({
|
|
1750
|
+
chrome.storage.local.set({
|
|
1826
1751
|
workflowRecording: false,
|
|
1827
1752
|
workflowStartTime: null
|
|
1828
1753
|
});
|
|
1829
1754
|
updateWorkflowRecordingUI();
|
|
1830
|
-
|
|
1831
|
-
//
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
//
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
// The response.workflow contains the full response from stopWorkflowRecording
|
|
1755
|
+
|
|
1756
|
+
// Show loading overlay over workflow recording section
|
|
1757
|
+
showSectionLoading('workflow-recording-section', 'Saving workflow recording...');
|
|
1758
|
+
|
|
1759
|
+
try {
|
|
1760
|
+
// Single source of truth - load from server
|
|
1761
|
+
await loadWorkflowRecordings(true);
|
|
1762
|
+
|
|
1763
|
+
// Show success toast - response.workflow.actions is the array
|
|
1840
1764
|
const workflowData = response.workflow;
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
${workflowData.workflow.sessionId} (workflow)
|
|
1850
|
-
</div>
|
|
1851
|
-
<div style="color: #666; font-size: 11px;">
|
|
1852
|
-
${workflowData.workflow.actions.length} actions • ${workflowData.workflow.logs.length} logs
|
|
1853
|
-
</div>
|
|
1854
|
-
</div>
|
|
1855
|
-
<div style="display: flex; gap: 4px; margin-top: 8px;">
|
|
1856
|
-
<button class="copy-id-btn" style="padding: 4px 8px; background: #2196F3; color: white; border: none; border-radius: 3px; cursor: pointer; font-size: 11px;">
|
|
1857
|
-
Copy ID
|
|
1858
|
-
</button>
|
|
1859
|
-
<button class="copy-prompt-btn" data-id="${workflowData.workflow.sessionId}" data-port="${currentServerPort || ''}" style="padding: 4px 8px; background: #4CAF50; color: white; border: none; border-radius: 3px; cursor: pointer; font-size: 11px;">
|
|
1860
|
-
Copy Prompt
|
|
1861
|
-
</button>
|
|
1862
|
-
</div>
|
|
1863
|
-
`;
|
|
1864
|
-
|
|
1865
|
-
// Add event listeners for the buttons
|
|
1866
|
-
const copyIdBtn = workflowItem.querySelector('.copy-id-btn');
|
|
1867
|
-
const copyPromptBtn = workflowItem.querySelector('.copy-prompt-btn');
|
|
1868
|
-
|
|
1869
|
-
copyIdBtn.addEventListener('click', () => {
|
|
1870
|
-
navigator.clipboard.writeText(workflowData.workflow.sessionId).then(() => {
|
|
1871
|
-
copyIdBtn.textContent = 'Copied!';
|
|
1872
|
-
copyIdBtn.style.background = '#4CAF50';
|
|
1873
|
-
setTimeout(() => {
|
|
1874
|
-
copyIdBtn.textContent = 'Copy ID';
|
|
1875
|
-
copyIdBtn.style.background = '#2196F3';
|
|
1876
|
-
}, 2000);
|
|
1877
|
-
});
|
|
1878
|
-
});
|
|
1879
|
-
|
|
1880
|
-
// copyPromptBtn is now handled by the shared event listener system with data-id and data-port attributes
|
|
1881
|
-
} else {
|
|
1882
|
-
// Fallback when server not available
|
|
1883
|
-
workflowItem = document.createElement('div');
|
|
1884
|
-
workflowItem.className = 'recording-item';
|
|
1885
|
-
workflowItem.innerHTML = `
|
|
1886
|
-
<div class="recording-info">
|
|
1887
|
-
<div style="font-weight: bold; margin-bottom: 4px; color: #ff6b6b;">
|
|
1888
|
-
Server not available - JSON downloaded
|
|
1889
|
-
</div>
|
|
1890
|
-
<div style="color: #666; font-size: 11px;">
|
|
1891
|
-
${workflowData.workflow.length} actions recorded
|
|
1892
|
-
</div>
|
|
1893
|
-
</div>
|
|
1894
|
-
`;
|
|
1895
|
-
}
|
|
1896
|
-
|
|
1897
|
-
// Add the item to the container
|
|
1898
|
-
workflowRecordingsContainer.appendChild(workflowItem);
|
|
1899
|
-
workflowRecordingsList.style.display = 'block';
|
|
1900
|
-
|
|
1901
|
-
// Reload the list to show all recordings
|
|
1902
|
-
setTimeout(() => {
|
|
1903
|
-
loadWorkflowRecordings();
|
|
1904
|
-
}, 500);
|
|
1765
|
+
const actionCount = workflowData?.actions?.length || 0;
|
|
1766
|
+
showToast(`Workflow recording saved! ${actionCount} actions recorded`, 3000, 'success');
|
|
1767
|
+
} catch (error) {
|
|
1768
|
+
console.error('Error loading workflow recordings:', error);
|
|
1769
|
+
showToast('Error loading recordings. Please refresh.', 3000, 'error');
|
|
1770
|
+
} finally {
|
|
1771
|
+
// Always hide loading overlay
|
|
1772
|
+
hideSectionLoading('workflow-recording-section');
|
|
1905
1773
|
}
|
|
1906
1774
|
}
|
|
1907
1775
|
});
|
|
@@ -2193,34 +2061,46 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
2193
2061
|
const viewScreenRecordingsLink = document.getElementById('viewScreenRecordings');
|
|
2194
2062
|
|
|
2195
2063
|
if (viewWorkflowRecordingsLink) {
|
|
2196
|
-
viewWorkflowRecordingsLink.addEventListener('click', (e) => {
|
|
2064
|
+
viewWorkflowRecordingsLink.addEventListener('click', async (e) => {
|
|
2197
2065
|
e.preventDefault();
|
|
2198
2066
|
const workflowRecordingsList = document.getElementById('workflowRecordingsList');
|
|
2199
2067
|
if (workflowRecordingsList) {
|
|
2200
2068
|
if (workflowRecordingsList.style.display === 'none' || workflowRecordingsList.style.display === '') {
|
|
2069
|
+
// Show recordings
|
|
2201
2070
|
workflowRecordingsList.style.display = 'block';
|
|
2202
|
-
loadWorkflowRecordings();
|
|
2071
|
+
await loadWorkflowRecordings();
|
|
2203
2072
|
viewWorkflowRecordingsLink.textContent = 'Hide Recordings';
|
|
2073
|
+
// Save preference
|
|
2074
|
+
await chrome.storage.local.set({ workflowRecordingsVisible: true });
|
|
2204
2075
|
} else {
|
|
2076
|
+
// Hide recordings
|
|
2205
2077
|
workflowRecordingsList.style.display = 'none';
|
|
2206
2078
|
viewWorkflowRecordingsLink.textContent = 'View Past Recordings';
|
|
2079
|
+
// Save preference
|
|
2080
|
+
await chrome.storage.local.set({ workflowRecordingsVisible: false });
|
|
2207
2081
|
}
|
|
2208
2082
|
}
|
|
2209
2083
|
});
|
|
2210
2084
|
}
|
|
2211
2085
|
|
|
2212
2086
|
if (viewScreenRecordingsLink) {
|
|
2213
|
-
viewScreenRecordingsLink.addEventListener('click', (e) => {
|
|
2087
|
+
viewScreenRecordingsLink.addEventListener('click', async (e) => {
|
|
2214
2088
|
e.preventDefault();
|
|
2215
2089
|
const recordingsList = document.getElementById('recordingsList');
|
|
2216
2090
|
if (recordingsList) {
|
|
2217
2091
|
if (recordingsList.style.display === 'none') {
|
|
2218
|
-
|
|
2092
|
+
// Show recordings
|
|
2093
|
+
await loadScreenRecordings();
|
|
2219
2094
|
recordingsList.style.display = 'block';
|
|
2220
2095
|
viewScreenRecordingsLink.textContent = 'Hide Recordings';
|
|
2096
|
+
// Save preference
|
|
2097
|
+
await chrome.storage.local.set({ screenRecordingsVisible: true });
|
|
2221
2098
|
} else {
|
|
2099
|
+
// Hide recordings
|
|
2222
2100
|
recordingsList.style.display = 'none';
|
|
2223
2101
|
viewScreenRecordingsLink.textContent = 'View Past Recordings';
|
|
2102
|
+
// Save preference
|
|
2103
|
+
await chrome.storage.local.set({ screenRecordingsVisible: false });
|
|
2224
2104
|
}
|
|
2225
2105
|
}
|
|
2226
2106
|
});
|
|
@@ -2261,16 +2141,19 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
2261
2141
|
});
|
|
2262
2142
|
|
|
2263
2143
|
// Function to load and display workflow recordings
|
|
2264
|
-
async function loadWorkflowRecordings() {
|
|
2144
|
+
async function loadWorkflowRecordings(autoShow = false) {
|
|
2265
2145
|
const workflowRecordingsList = document.getElementById('workflowRecordingsList');
|
|
2266
2146
|
const workflowRecordingsContainer = document.getElementById('workflowRecordingsContainer');
|
|
2267
|
-
|
|
2147
|
+
const viewWorkflowRecordingsLink = document.getElementById('viewWorkflowRecordings');
|
|
2148
|
+
|
|
2268
2149
|
if (!workflowRecordingsList || !workflowRecordingsContainer) {
|
|
2269
2150
|
return;
|
|
2270
2151
|
}
|
|
2271
|
-
|
|
2152
|
+
|
|
2272
2153
|
// Clear existing items
|
|
2273
2154
|
workflowRecordingsContainer.innerHTML = '';
|
|
2155
|
+
|
|
2156
|
+
let hasRecordings = false;
|
|
2274
2157
|
|
|
2275
2158
|
// Check server for workflow recordings
|
|
2276
2159
|
const ports = CONFIG_PORTS;
|
|
@@ -2283,8 +2166,9 @@ async function loadWorkflowRecordings() {
|
|
|
2283
2166
|
|
|
2284
2167
|
// Handle both data.recordings and direct array response
|
|
2285
2168
|
const recordings = data.recordings || data;
|
|
2286
|
-
|
|
2169
|
+
|
|
2287
2170
|
if (Array.isArray(recordings) && recordings.length > 0) {
|
|
2171
|
+
hasRecordings = true;
|
|
2288
2172
|
// Display each workflow recording
|
|
2289
2173
|
recordings.forEach(recording => {
|
|
2290
2174
|
const workflowItem = document.createElement('div');
|
|
@@ -2303,12 +2187,18 @@ async function loadWorkflowRecordings() {
|
|
|
2303
2187
|
${recording.screenshot_settings ? '<div style="color: #2196F3; font-size: 10px;">📸 Screenshots included</div>' : ''}
|
|
2304
2188
|
</div>
|
|
2305
2189
|
<div class="recording-buttons">
|
|
2190
|
+
<button class="copy-id-btn" data-session-id="${recording.session_id}" style="padding: 4px 8px; background: #2196F3; color: white; border: none; border-radius: 3px; cursor: pointer; font-size: 11px;">
|
|
2191
|
+
Copy ID
|
|
2192
|
+
</button>
|
|
2193
|
+
<button class="copy-prompt-btn" data-session-id="${recording.session_id}" data-name="${recording.name || ''}" style="padding: 4px 8px; background: #4CAF50; color: white; border: none; border-radius: 3px; cursor: pointer; font-size: 11px;">
|
|
2194
|
+
Copy Prompt
|
|
2195
|
+
</button>
|
|
2196
|
+
<button class="view-btn" data-session-id="${recording.session_id}" style="padding: 4px 8px; background: #9C27B0; color: white; border: none; border-radius: 3px; cursor: pointer; font-size: 11px;">
|
|
2197
|
+
View
|
|
2198
|
+
</button>
|
|
2306
2199
|
<button class="play-btn" data-session-id="${recording.session_id}" style="padding: 4px 8px; background: #4CAF50; color: white; border: none; border-radius: 3px; cursor: pointer; font-size: 11px;">
|
|
2307
2200
|
▶ Play
|
|
2308
2201
|
</button>
|
|
2309
|
-
<button class="get-prompt-btn" data-session-id="${recording.session_id}" data-name="${recording.name || ''}" style="padding: 4px 8px; background: #2196F3; color: white; border: none; border-radius: 3px; cursor: pointer; font-size: 11px;">
|
|
2310
|
-
Get Prompt
|
|
2311
|
-
</button>
|
|
2312
2202
|
<button class="delete-btn" data-recording-id="${recording.id || recording.session_id}" style="padding: 4px 8px; background: #f44336; color: white; border: none; border-radius: 3px; cursor: pointer; font-size: 11px;">
|
|
2313
2203
|
Delete
|
|
2314
2204
|
</button>
|
|
@@ -2316,17 +2206,29 @@ async function loadWorkflowRecordings() {
|
|
|
2316
2206
|
`;
|
|
2317
2207
|
|
|
2318
2208
|
// Add event listeners
|
|
2209
|
+
const copyIdBtn = workflowItem.querySelector('.copy-id-btn');
|
|
2210
|
+
const copyPromptBtn = workflowItem.querySelector('.copy-prompt-btn');
|
|
2211
|
+
const viewBtn = workflowItem.querySelector('.view-btn');
|
|
2319
2212
|
const playBtn = workflowItem.querySelector('.play-btn');
|
|
2320
|
-
const getPromptBtn = workflowItem.querySelector('.get-prompt-btn');
|
|
2321
2213
|
const deleteBtn = workflowItem.querySelector('.delete-btn');
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2214
|
+
|
|
2215
|
+
copyIdBtn.addEventListener('click', () => {
|
|
2216
|
+
navigator.clipboard.writeText(recording.session_id).then(() => {
|
|
2217
|
+
copyIdBtn.textContent = 'Copied!';
|
|
2218
|
+
copyIdBtn.style.background = '#4CAF50';
|
|
2219
|
+
setTimeout(() => {
|
|
2220
|
+
copyIdBtn.textContent = 'Copy ID';
|
|
2221
|
+
copyIdBtn.style.background = '#2196F3';
|
|
2222
|
+
}, 2000);
|
|
2223
|
+
});
|
|
2224
|
+
});
|
|
2225
|
+
|
|
2226
|
+
copyPromptBtn.addEventListener('click', () => {
|
|
2325
2227
|
const sessionId = recording.session_id;
|
|
2326
2228
|
const name = recording.name;
|
|
2327
2229
|
const port = window.CHROMEDEBUG_CONFIG?.ports?.[0] || '3001';
|
|
2328
2230
|
let prompt;
|
|
2329
|
-
|
|
2231
|
+
|
|
2330
2232
|
if (name) {
|
|
2331
2233
|
// Use name-based prompt similar to screen recording format
|
|
2332
2234
|
prompt = `Please use the get_workflow_recording function in Chrome Debug to load the workflow recording "${name}" (session: ${sessionId}) that was recorded on port ${port}.`;
|
|
@@ -2334,16 +2236,26 @@ async function loadWorkflowRecordings() {
|
|
|
2334
2236
|
// Use session ID based prompt
|
|
2335
2237
|
prompt = `Please use the get_workflow_recording function in Chrome Debug to load the workflow recording "${sessionId}" that was recorded on port ${port}.`;
|
|
2336
2238
|
}
|
|
2337
|
-
|
|
2239
|
+
|
|
2338
2240
|
navigator.clipboard.writeText(prompt).then(() => {
|
|
2339
|
-
|
|
2340
|
-
|
|
2241
|
+
copyPromptBtn.textContent = 'Copied!';
|
|
2242
|
+
const originalBg = copyPromptBtn.style.background;
|
|
2243
|
+
copyPromptBtn.style.background = '#4CAF50';
|
|
2341
2244
|
setTimeout(() => {
|
|
2342
|
-
|
|
2343
|
-
|
|
2245
|
+
copyPromptBtn.textContent = 'Copy Prompt';
|
|
2246
|
+
copyPromptBtn.style.background = originalBg;
|
|
2344
2247
|
}, 2000);
|
|
2345
2248
|
});
|
|
2346
2249
|
});
|
|
2250
|
+
|
|
2251
|
+
viewBtn.addEventListener('click', async () => {
|
|
2252
|
+
// Open frame editor in workflow mode
|
|
2253
|
+
const frameEditorUrl = chrome.runtime.getURL(`pro/frame-editor.html?sessionId=${recording.session_id}&type=workflow`);
|
|
2254
|
+
chrome.tabs.create({ url: frameEditorUrl });
|
|
2255
|
+
});
|
|
2256
|
+
|
|
2257
|
+
playBtn.addEventListener('click', () => playWorkflow(recording.session_id));
|
|
2258
|
+
|
|
2347
2259
|
deleteBtn.addEventListener('click', () => {
|
|
2348
2260
|
console.log('Delete clicked for recording:', recording);
|
|
2349
2261
|
const recordingId = recording.id || recording.session_id;
|
|
@@ -2378,6 +2290,27 @@ async function loadWorkflowRecordings() {
|
|
|
2378
2290
|
errorMsg.textContent = 'Could not connect to Chrome Debug server';
|
|
2379
2291
|
workflowRecordingsContainer.appendChild(errorMsg);
|
|
2380
2292
|
}
|
|
2293
|
+
|
|
2294
|
+
// Handle visibility based on recordings and user preference
|
|
2295
|
+
if (!hasRecordings) {
|
|
2296
|
+
workflowRecordingsList.style.display = 'none';
|
|
2297
|
+
} else {
|
|
2298
|
+
// Get user's saved preference
|
|
2299
|
+
const prefs = await chrome.storage.local.get(['workflowRecordingsVisible']);
|
|
2300
|
+
const shouldShow = autoShow || (prefs.workflowRecordingsVisible !== false); // Default to true
|
|
2301
|
+
|
|
2302
|
+
if (shouldShow) {
|
|
2303
|
+
workflowRecordingsList.style.display = 'block';
|
|
2304
|
+
if (viewWorkflowRecordingsLink) {
|
|
2305
|
+
viewWorkflowRecordingsLink.textContent = 'Hide Recordings';
|
|
2306
|
+
}
|
|
2307
|
+
} else {
|
|
2308
|
+
workflowRecordingsList.style.display = 'none';
|
|
2309
|
+
if (viewWorkflowRecordingsLink) {
|
|
2310
|
+
viewWorkflowRecordingsLink.textContent = 'View Past Recordings';
|
|
2311
|
+
}
|
|
2312
|
+
}
|
|
2313
|
+
}
|
|
2381
2314
|
}
|
|
2382
2315
|
|
|
2383
2316
|
// Function to play a workflow
|
|
@@ -2526,18 +2459,147 @@ async function deleteWorkflowRecording(recordingId) {
|
|
|
2526
2459
|
}
|
|
2527
2460
|
|
|
2528
2461
|
// Function to load and display screen recordings
|
|
2529
|
-
async function loadScreenRecordings() {
|
|
2462
|
+
async function loadScreenRecordings(autoShow = false) {
|
|
2530
2463
|
const recordingsList = document.getElementById('recordingsList');
|
|
2531
2464
|
const recordingsContainer = document.getElementById('recordingsContainer');
|
|
2532
|
-
|
|
2465
|
+
const viewScreenRecordingsLink = document.getElementById('viewScreenRecordings');
|
|
2466
|
+
|
|
2533
2467
|
if (!recordingsList || !recordingsContainer) {
|
|
2534
2468
|
return;
|
|
2535
2469
|
}
|
|
2536
|
-
|
|
2470
|
+
|
|
2537
2471
|
// Clear existing items
|
|
2538
2472
|
recordingsContainer.innerHTML = '';
|
|
2539
|
-
|
|
2540
|
-
|
|
2473
|
+
|
|
2474
|
+
let hasRecordings = false;
|
|
2475
|
+
|
|
2476
|
+
// First, load browser-only recordings from IndexedDB
|
|
2477
|
+
try {
|
|
2478
|
+
console.log('[Popup] Requesting browser-only recordings...');
|
|
2479
|
+
const browserRecordings = await chrome.runtime.sendMessage({
|
|
2480
|
+
action: 'getBrowserRecordings'
|
|
2481
|
+
});
|
|
2482
|
+
console.log('[Popup] Browser recordings received:', browserRecordings);
|
|
2483
|
+
|
|
2484
|
+
if (browserRecordings && browserRecordings.length > 0) {
|
|
2485
|
+
hasRecordings = true;
|
|
2486
|
+
recordingsList.style.display = 'block';
|
|
2487
|
+
|
|
2488
|
+
// Display each browser-only recording
|
|
2489
|
+
browserRecordings.forEach(recording => {
|
|
2490
|
+
const recordingItem = document.createElement('div');
|
|
2491
|
+
recordingItem.className = 'recording-item';
|
|
2492
|
+
|
|
2493
|
+
const date = new Date(recording.startTime);
|
|
2494
|
+
const formattedDate = date.toLocaleString();
|
|
2495
|
+
const displayName = recording.title || `Recording ${recording.sessionId}`;
|
|
2496
|
+
|
|
2497
|
+
recordingItem.innerHTML = `
|
|
2498
|
+
<div class="recording-info">
|
|
2499
|
+
<div class="recording-id" title="${recording.sessionId}">
|
|
2500
|
+
<span class="recording-id-text">🌐 ${displayName}</span>
|
|
2501
|
+
<span style="font-size: 10px; background: #ff9800; color: white; padding: 2px 6px; border-radius: 3px; margin-left: 4px;">BROWSER-ONLY</span>
|
|
2502
|
+
<span style="color: #666; font-size: 11px; margin-left: 8px;">${recording.frameCount || 0} frames • ${formattedDate}</span>
|
|
2503
|
+
</div>
|
|
2504
|
+
</div>
|
|
2505
|
+
<div class="recording-buttons">
|
|
2506
|
+
<button class="copy-id-btn" data-session-id="${recording.sessionId}" title="Copy ID">Copy ID</button>
|
|
2507
|
+
<button class="copy-prompt-btn" data-session-id="${recording.sessionId}" data-name="${recording.title || ''}" title="Copy Claude Prompt">Copy Prompt</button>
|
|
2508
|
+
<button class="view-browser-btn" data-session-id="${recording.sessionId}" title="View Screenshots">View</button>
|
|
2509
|
+
<button class="export-btn" data-session-id="${recording.sessionId}" title="Export Recording">Export</button>
|
|
2510
|
+
<button class="delete-browser-btn" data-session-id="${recording.sessionId}" title="Delete">Delete</button>
|
|
2511
|
+
</div>
|
|
2512
|
+
`;
|
|
2513
|
+
|
|
2514
|
+
// Add event listeners
|
|
2515
|
+
const copyIdBtn = recordingItem.querySelector('.copy-id-btn');
|
|
2516
|
+
const copyPromptBtn = recordingItem.querySelector('.copy-prompt-btn');
|
|
2517
|
+
const viewBtn = recordingItem.querySelector('.view-browser-btn');
|
|
2518
|
+
const exportBtn = recordingItem.querySelector('.export-btn');
|
|
2519
|
+
const deleteBtn = recordingItem.querySelector('.delete-browser-btn');
|
|
2520
|
+
|
|
2521
|
+
copyIdBtn.addEventListener('click', () => {
|
|
2522
|
+
navigator.clipboard.writeText(recording.sessionId).then(() => {
|
|
2523
|
+
copyIdBtn.textContent = 'Copied!';
|
|
2524
|
+
copyIdBtn.style.background = '#4CAF50';
|
|
2525
|
+
setTimeout(() => {
|
|
2526
|
+
copyIdBtn.textContent = 'Copy ID';
|
|
2527
|
+
copyIdBtn.style.background = '#2196F3';
|
|
2528
|
+
}, 2000);
|
|
2529
|
+
});
|
|
2530
|
+
});
|
|
2531
|
+
|
|
2532
|
+
copyPromptBtn.addEventListener('click', () => {
|
|
2533
|
+
const sessionId = recording.sessionId;
|
|
2534
|
+
const title = recording.title;
|
|
2535
|
+
let prompt;
|
|
2536
|
+
|
|
2537
|
+
if (title) {
|
|
2538
|
+
prompt = `Please analyze the browser-only screen recording "${title}" (session: ${sessionId}). This recording is stored locally in the browser.`;
|
|
2539
|
+
} else {
|
|
2540
|
+
prompt = `Please analyze the browser-only screen recording "${sessionId}". This recording is stored locally in the browser.`;
|
|
2541
|
+
}
|
|
2542
|
+
|
|
2543
|
+
navigator.clipboard.writeText(prompt).then(() => {
|
|
2544
|
+
copyPromptBtn.textContent = 'Copied!';
|
|
2545
|
+
const originalBg = copyPromptBtn.style.background;
|
|
2546
|
+
copyPromptBtn.style.background = '#4CAF50';
|
|
2547
|
+
setTimeout(() => {
|
|
2548
|
+
copyPromptBtn.textContent = 'Copy Prompt';
|
|
2549
|
+
copyPromptBtn.style.background = originalBg;
|
|
2550
|
+
}, 2000);
|
|
2551
|
+
});
|
|
2552
|
+
});
|
|
2553
|
+
|
|
2554
|
+
viewBtn.addEventListener('click', () => {
|
|
2555
|
+
// Open frame editor in browser-only mode
|
|
2556
|
+
const frameEditorUrl = chrome.runtime.getURL(`pro/frame-editor.html?sessionId=${recording.sessionId}&browserOnly=true`);
|
|
2557
|
+
chrome.tabs.create({ url: frameEditorUrl });
|
|
2558
|
+
});
|
|
2559
|
+
|
|
2560
|
+
exportBtn.addEventListener('click', async () => {
|
|
2561
|
+
try {
|
|
2562
|
+
const result = await chrome.runtime.sendMessage({
|
|
2563
|
+
action: 'exportBrowserRecording',
|
|
2564
|
+
sessionId: recording.sessionId
|
|
2565
|
+
});
|
|
2566
|
+
|
|
2567
|
+
if (result && result.success) {
|
|
2568
|
+
exportBtn.textContent = 'Exported!';
|
|
2569
|
+
exportBtn.style.background = '#4CAF50';
|
|
2570
|
+
setTimeout(() => {
|
|
2571
|
+
exportBtn.textContent = 'Export';
|
|
2572
|
+
exportBtn.style.background = '#FF9800';
|
|
2573
|
+
}, 2000);
|
|
2574
|
+
}
|
|
2575
|
+
} catch (error) {
|
|
2576
|
+
console.error('Failed to export recording:', error);
|
|
2577
|
+
alert('Failed to export recording');
|
|
2578
|
+
}
|
|
2579
|
+
});
|
|
2580
|
+
|
|
2581
|
+
deleteBtn.addEventListener('click', async () => {
|
|
2582
|
+
if (confirm('Are you sure you want to delete this browser recording?')) {
|
|
2583
|
+
try {
|
|
2584
|
+
await chrome.runtime.sendMessage({
|
|
2585
|
+
action: 'deleteBrowserRecording',
|
|
2586
|
+
sessionId: recording.sessionId
|
|
2587
|
+
});
|
|
2588
|
+
loadScreenRecordings(); // Reload list
|
|
2589
|
+
} catch (error) {
|
|
2590
|
+
console.error('Failed to delete recording:', error);
|
|
2591
|
+
}
|
|
2592
|
+
}
|
|
2593
|
+
});
|
|
2594
|
+
|
|
2595
|
+
recordingsContainer.appendChild(recordingItem);
|
|
2596
|
+
});
|
|
2597
|
+
}
|
|
2598
|
+
} catch (error) {
|
|
2599
|
+
console.error('Failed to load browser-only recordings:', error);
|
|
2600
|
+
}
|
|
2601
|
+
|
|
2602
|
+
// Then, check server for screen recordings
|
|
2541
2603
|
const ports = CONFIG_PORTS;
|
|
2542
2604
|
for (const port of ports) {
|
|
2543
2605
|
try {
|
|
@@ -2545,6 +2607,7 @@ async function loadScreenRecordings() {
|
|
|
2545
2607
|
if (response.ok) {
|
|
2546
2608
|
const data = await response.json();
|
|
2547
2609
|
if (data && data.length > 0) {
|
|
2610
|
+
hasRecordings = true;
|
|
2548
2611
|
recordingsList.style.display = 'block';
|
|
2549
2612
|
|
|
2550
2613
|
// Display each screen recording
|
|
@@ -2563,25 +2626,25 @@ async function loadScreenRecordings() {
|
|
|
2563
2626
|
|
|
2564
2627
|
recordingItem.innerHTML = `
|
|
2565
2628
|
<div class="recording-info">
|
|
2566
|
-
<div
|
|
2567
|
-
|
|
2568
|
-
|
|
2629
|
+
<div class="recording-id" title="${recording.sessionId}">
|
|
2630
|
+
<span class="recording-id-text">🎥 ${displayName}</span>
|
|
2631
|
+
<span style="color: #666; font-size: 11px; margin-left: 8px;">${recording.totalFrames} frames • ${formattedDate}</span>
|
|
2569
2632
|
</div>
|
|
2570
2633
|
</div>
|
|
2571
2634
|
<div class="recording-buttons">
|
|
2572
|
-
<button class="copy-id-btn" data-session-id="${recording.sessionId}"
|
|
2573
|
-
|
|
2574
|
-
</button>
|
|
2575
|
-
<button class="delete-btn" data-session-id="${recording.sessionId}"
|
|
2576
|
-
Delete
|
|
2577
|
-
</button>
|
|
2635
|
+
<button class="copy-id-btn" data-session-id="${recording.sessionId}" title="Copy ID">Copy ID</button>
|
|
2636
|
+
<button class="copy-prompt-btn" data-session-id="${recording.sessionId}" data-name="${recording.name || ''}" data-port="${port}" title="Copy Claude Prompt">Copy Prompt</button>
|
|
2637
|
+
<button class="view-btn" data-session-id="${recording.sessionId}" title="View Screenshots">View</button>
|
|
2638
|
+
<button class="delete-btn" data-session-id="${recording.sessionId}" title="Delete">Delete</button>
|
|
2578
2639
|
</div>
|
|
2579
2640
|
`;
|
|
2580
2641
|
|
|
2581
2642
|
// Add event listeners
|
|
2582
2643
|
const copyIdBtn = recordingItem.querySelector('.copy-id-btn');
|
|
2644
|
+
const copyPromptBtn = recordingItem.querySelector('.copy-prompt-btn');
|
|
2645
|
+
const viewBtn = recordingItem.querySelector('.view-btn');
|
|
2583
2646
|
const deleteBtn = recordingItem.querySelector('.delete-btn');
|
|
2584
|
-
|
|
2647
|
+
|
|
2585
2648
|
copyIdBtn.addEventListener('click', () => {
|
|
2586
2649
|
navigator.clipboard.writeText(recording.sessionId).then(() => {
|
|
2587
2650
|
copyIdBtn.textContent = 'Copied!';
|
|
@@ -2592,14 +2655,60 @@ async function loadScreenRecordings() {
|
|
|
2592
2655
|
}, 2000);
|
|
2593
2656
|
});
|
|
2594
2657
|
});
|
|
2595
|
-
|
|
2658
|
+
|
|
2659
|
+
copyPromptBtn.addEventListener('click', () => {
|
|
2660
|
+
const sessionId = recording.sessionId;
|
|
2661
|
+
const name = recording.name;
|
|
2662
|
+
let prompt;
|
|
2663
|
+
|
|
2664
|
+
if (name) {
|
|
2665
|
+
prompt = `Please use the chrome_debug_show_frames function in Chrome Debug to display the screen recording "${name}" (session: ${sessionId}) that was recorded on port ${port}.`;
|
|
2666
|
+
} else {
|
|
2667
|
+
prompt = `Please use the chrome_debug_show_frames function in Chrome Debug to display the screen recording "${sessionId}" that was recorded on port ${port}.`;
|
|
2668
|
+
}
|
|
2669
|
+
|
|
2670
|
+
navigator.clipboard.writeText(prompt).then(() => {
|
|
2671
|
+
copyPromptBtn.textContent = 'Copied!';
|
|
2672
|
+
const originalBg = copyPromptBtn.style.background;
|
|
2673
|
+
copyPromptBtn.style.background = '#4CAF50';
|
|
2674
|
+
setTimeout(() => {
|
|
2675
|
+
copyPromptBtn.textContent = 'Copy Prompt';
|
|
2676
|
+
copyPromptBtn.style.background = originalBg;
|
|
2677
|
+
}, 2000);
|
|
2678
|
+
});
|
|
2679
|
+
});
|
|
2680
|
+
|
|
2681
|
+
viewBtn.addEventListener('click', async () => {
|
|
2682
|
+
try {
|
|
2683
|
+
// Check if frame editor is available (Pro version)
|
|
2684
|
+
const frameEditorUrl = chrome.runtime.getURL(`pro/frame-editor.html?sessionId=${recording.sessionId}`);
|
|
2685
|
+
const response = await fetch(frameEditorUrl, { method: 'HEAD' });
|
|
2686
|
+
|
|
2687
|
+
if (response.ok) {
|
|
2688
|
+
// Pro version - open frame editor
|
|
2689
|
+
chrome.tabs.create({ url: frameEditorUrl });
|
|
2690
|
+
} else {
|
|
2691
|
+
// Free version - show summary
|
|
2692
|
+
const framesCount = recording.totalFrames || 0;
|
|
2693
|
+
const noteText = recording.name ? `\nName: ${recording.name}` : '';
|
|
2694
|
+
alert(`Screen Recording${noteText}\nSession ID: ${recording.sessionId}\n\nFrames: ${framesCount}\n\nUse "Copy ID" to share with Claude Code for detailed analysis.\n\nUpgrade to Pro for the Frame Editor with advanced viewing and editing features.`);
|
|
2695
|
+
}
|
|
2696
|
+
} catch (error) {
|
|
2697
|
+
console.error('Failed to view recording:', error);
|
|
2698
|
+
// Fallback to alert
|
|
2699
|
+
const framesCount = recording.totalFrames || 0;
|
|
2700
|
+
const noteText = recording.name ? `\nName: ${recording.name}` : '';
|
|
2701
|
+
alert(`Screen Recording${noteText}\nSession ID: ${recording.sessionId}\n\nFrames: ${framesCount}\n\nUse "Copy ID" to share with Claude Code for detailed analysis.`);
|
|
2702
|
+
}
|
|
2703
|
+
});
|
|
2704
|
+
|
|
2596
2705
|
deleteBtn.addEventListener('click', async () => {
|
|
2597
2706
|
if (confirm('Are you sure you want to delete this screen recording?')) {
|
|
2598
2707
|
try {
|
|
2599
2708
|
const deleteResponse = await fetch(`http://localhost:${port}/chromedebug/recording/${recording.sessionId}`, {
|
|
2600
2709
|
method: 'DELETE'
|
|
2601
2710
|
});
|
|
2602
|
-
|
|
2711
|
+
|
|
2603
2712
|
if (deleteResponse.ok) {
|
|
2604
2713
|
// Reload the list
|
|
2605
2714
|
loadScreenRecordings();
|
|
@@ -2612,8 +2721,6 @@ async function loadScreenRecordings() {
|
|
|
2612
2721
|
|
|
2613
2722
|
recordingsContainer.appendChild(recordingItem);
|
|
2614
2723
|
});
|
|
2615
|
-
} else {
|
|
2616
|
-
recordingsList.style.display = 'none';
|
|
2617
2724
|
}
|
|
2618
2725
|
break; // Found working server
|
|
2619
2726
|
}
|
|
@@ -2621,6 +2728,27 @@ async function loadScreenRecordings() {
|
|
|
2621
2728
|
console.error(`Failed to load screen recordings from port ${port}:`, error);
|
|
2622
2729
|
}
|
|
2623
2730
|
}
|
|
2731
|
+
|
|
2732
|
+
// Handle visibility based on recordings and user preference
|
|
2733
|
+
if (!hasRecordings) {
|
|
2734
|
+
recordingsList.style.display = 'none';
|
|
2735
|
+
} else {
|
|
2736
|
+
// Get user's saved preference
|
|
2737
|
+
const prefs = await chrome.storage.local.get(['screenRecordingsVisible']);
|
|
2738
|
+
const shouldShow = autoShow || (prefs.screenRecordingsVisible !== false); // Default to true
|
|
2739
|
+
|
|
2740
|
+
if (shouldShow) {
|
|
2741
|
+
recordingsList.style.display = 'block';
|
|
2742
|
+
if (viewScreenRecordingsLink) {
|
|
2743
|
+
viewScreenRecordingsLink.textContent = 'Hide Recordings';
|
|
2744
|
+
}
|
|
2745
|
+
} else {
|
|
2746
|
+
recordingsList.style.display = 'none';
|
|
2747
|
+
if (viewScreenRecordingsLink) {
|
|
2748
|
+
viewScreenRecordingsLink.textContent = 'View Past Recordings';
|
|
2749
|
+
}
|
|
2750
|
+
}
|
|
2751
|
+
}
|
|
2624
2752
|
}
|
|
2625
2753
|
|
|
2626
2754
|
/*
|
|
@@ -2790,6 +2918,17 @@ chrome.storage.onChanged.addListener((changes, namespace) => {
|
|
|
2790
2918
|
isWorkflowRecording = changes.workflowRecording.newValue === true;
|
|
2791
2919
|
updateWorkflowRecordingUI();
|
|
2792
2920
|
}
|
|
2921
|
+
// Update usage display when usage count changes
|
|
2922
|
+
if (changes.chromedebug_daily_usage) {
|
|
2923
|
+
const usage = changes.chromedebug_daily_usage.newValue;
|
|
2924
|
+
if (usage) {
|
|
2925
|
+
const usageCountEl = document.getElementById('usage-count');
|
|
2926
|
+
const usageLimitEl = document.getElementById('usage-limit');
|
|
2927
|
+
if (usageCountEl) usageCountEl.textContent = usage.count || 0;
|
|
2928
|
+
if (usageLimitEl) usageLimitEl.textContent = '5'; // FREE tier limit
|
|
2929
|
+
console.log(`[Usage] Storage updated: ${usage.count}/5`);
|
|
2930
|
+
}
|
|
2931
|
+
}
|
|
2793
2932
|
}
|
|
2794
2933
|
});
|
|
2795
2934
|
|
|
@@ -2828,12 +2967,20 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
|
|
|
2828
2967
|
recordingStatus.innerHTML = `<strong style="color: #4CAF50;">Recording saved!</strong><br>` +
|
|
2829
2968
|
`<small>${request.totalFrames} frames (${Math.round(request.duration / 1000)}s)</small>`;
|
|
2830
2969
|
}
|
|
2831
|
-
//
|
|
2832
|
-
|
|
2970
|
+
// Reload screen recordings list to show the new recording
|
|
2971
|
+
loadScreenRecordings(true);
|
|
2833
2972
|
// Clear status after 5 seconds
|
|
2834
2973
|
setTimeout(() => {
|
|
2835
2974
|
if (recordingStatus) recordingStatus.textContent = '';
|
|
2836
2975
|
}, 5000);
|
|
2976
|
+
} else if (request.action === 'workflowRecordingSaved') {
|
|
2977
|
+
console.log('Workflow recording saved:', request.workflowId);
|
|
2978
|
+
// Reload workflow recordings list to show the new recording
|
|
2979
|
+
loadWorkflowRecordings(true);
|
|
2980
|
+
} else if (request.action === 'recordingStopped') {
|
|
2981
|
+
console.log('Recording stopped:', request.sessionId);
|
|
2982
|
+
// Reload screen recordings list immediately
|
|
2983
|
+
loadScreenRecordings(true);
|
|
2837
2984
|
} else if (request.action === 'uploadError') {
|
|
2838
2985
|
const recordingStatus = document.getElementById('recordingStatus');
|
|
2839
2986
|
if (recordingStatus) {
|