@dynamicu/chromedebug-mcp 2.7.2 → 2.7.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +18 -0
- package/README.md +226 -16
- package/chrome-extension/background.js +547 -56
- package/chrome-extension/browser-recording-manager.js +34 -0
- package/chrome-extension/content.js +438 -32
- package/chrome-extension/firebase-config.public-sw.js +1 -1
- package/chrome-extension/firebase-config.public.js +1 -1
- package/chrome-extension/frame-capture.js +31 -10
- package/chrome-extension/image-processor.js +193 -0
- package/chrome-extension/manifest.free.json +1 -1
- package/chrome-extension/popup.html +82 -4
- package/chrome-extension/popup.js +1106 -38
- package/chrome-extension/pro/frame-editor.html +259 -6
- package/chrome-extension/pro/frame-editor.js +959 -10
- package/chrome-extension/pro/video-exporter.js +917 -0
- package/chrome-extension/pro/video-player.js +545 -0
- package/dist/chromedebug-extension-free.zip +0 -0
- package/package.json +1 -1
- package/scripts/postinstall.js +1 -1
- package/scripts/webpack.config.free.cjs +6 -0
- package/scripts/webpack.config.pro.cjs +6 -0
- package/src/chrome-controller.js +6 -6
- package/src/database.js +157 -39
- package/src/http-server.js +55 -11
- package/src/validation/schemas.js +11 -1
|
@@ -36,6 +36,7 @@ class BrowserRecordingManager {
|
|
|
36
36
|
title: metadata.title || 'Browser Recording',
|
|
37
37
|
frameCount: 0,
|
|
38
38
|
status: 'recording',
|
|
39
|
+
isVideoMode: metadata.isVideoMode || false,
|
|
39
40
|
metadata
|
|
40
41
|
};
|
|
41
42
|
|
|
@@ -248,6 +249,39 @@ class BrowserRecordingManager {
|
|
|
248
249
|
}
|
|
249
250
|
}
|
|
250
251
|
|
|
252
|
+
/**
|
|
253
|
+
* Delete all recordings and their frames
|
|
254
|
+
* @returns {Promise<{success: boolean, deletedCount: number}>}
|
|
255
|
+
*/
|
|
256
|
+
async deleteAllRecordings() {
|
|
257
|
+
try {
|
|
258
|
+
const recordings = await this.listRecordings();
|
|
259
|
+
let deletedCount = 0;
|
|
260
|
+
|
|
261
|
+
for (const recording of recordings) {
|
|
262
|
+
const result = await this.deleteRecording(recording.sessionId);
|
|
263
|
+
if (result.success) {
|
|
264
|
+
deletedCount++;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Clear any active recordings
|
|
269
|
+
this.activeRecordings.clear();
|
|
270
|
+
|
|
271
|
+
return {
|
|
272
|
+
success: true,
|
|
273
|
+
deletedCount
|
|
274
|
+
};
|
|
275
|
+
} catch (error) {
|
|
276
|
+
console.error('Failed to delete all recordings:', error);
|
|
277
|
+
return {
|
|
278
|
+
success: false,
|
|
279
|
+
deletedCount: 0,
|
|
280
|
+
error: error.message
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
251
285
|
}
|
|
252
286
|
|
|
253
287
|
// Export for use in other extension files
|
|
@@ -476,9 +476,15 @@ if (isExtensionValid()) {
|
|
|
476
476
|
sendResponse({ success: true });
|
|
477
477
|
} else if (request.action === 'recordingStarted') {
|
|
478
478
|
// Recording indicator now handled by ScreenCaptureVisualFeedback
|
|
479
|
-
|
|
479
|
+
console.log('[ChromeDebug MCP] Received recordingStarted, videoMode:', request.videoMode, 'sessionId:', request.sessionId);
|
|
480
480
|
// Start tracking screen interactions with scheduled timing
|
|
481
|
-
startScreenInteractionTracking(request.scheduledStartTime, request.sessionId);
|
|
481
|
+
startScreenInteractionTracking(request.scheduledStartTime, request.sessionId, request.videoMode || false);
|
|
482
|
+
sendResponse({ success: true });
|
|
483
|
+
} else if (request.action === 'setMouseTrackingSettings') {
|
|
484
|
+
// Update mouse tracking settings (PRO-only feature)
|
|
485
|
+
mouseTrackingEnabled = request.enabled || false;
|
|
486
|
+
mouseSampleInterval = request.sampleInterval || 100;
|
|
487
|
+
// console.log('[ChromeDebug MCP] Mouse tracking settings updated:', { mouseTrackingEnabled, mouseSampleInterval });
|
|
482
488
|
sendResponse({ success: true });
|
|
483
489
|
} else if (request.action === 'recordingStopped') {
|
|
484
490
|
// Recording indicator cleanup now handled by ScreenCaptureVisualFeedback
|
|
@@ -522,6 +528,18 @@ if (isExtensionValid()) {
|
|
|
522
528
|
sendResponse({ success: false, error: error.message });
|
|
523
529
|
});
|
|
524
530
|
return true; // Keep channel open for async response
|
|
531
|
+
} else if (request.action === 'startScreenshotRecording') {
|
|
532
|
+
// Start screenshot recording mode (v2.7.3)
|
|
533
|
+
// console.log('[ChromeDebug MCP] Starting screenshot recording with settings:', request.settings);
|
|
534
|
+
screenshotModeTabId = request.tabId;
|
|
535
|
+
screenshotModeSessionId = request.sessionId;
|
|
536
|
+
startScreenshotRecordingMode(request.settings);
|
|
537
|
+
sendResponse({ success: true });
|
|
538
|
+
} else if (request.action === 'getScreenshotData') {
|
|
539
|
+
// Called by background script to retrieve screenshot data
|
|
540
|
+
// console.log('[ChromeDebug MCP] Getting screenshot data');
|
|
541
|
+
const screenshots = stopScreenshotRecordingMode();
|
|
542
|
+
sendResponse({ success: true, screenshots: screenshots });
|
|
525
543
|
} else if (request.action === 'createRestorePoint') {
|
|
526
544
|
// Create restore point for current workflow
|
|
527
545
|
createRestorePoint(request.actionIndex || workflowActions.length).then(restorePointId => {
|
|
@@ -878,23 +896,21 @@ let workflowTabId = null; // Store tabId for use when stopping recording
|
|
|
878
896
|
let lastScreenshotTime = 0;
|
|
879
897
|
let pendingScreenshots = []; // Track pending screenshot captures
|
|
880
898
|
|
|
899
|
+
// Screenshot recording mode variables (v2.7.3)
|
|
900
|
+
let isScreenshotMode = false;
|
|
901
|
+
let screenshotModeSettings = { countdown: 3, hideCursor: true };
|
|
902
|
+
let screenshotModeActions = [];
|
|
903
|
+
let screenshotModeIndicator = null;
|
|
904
|
+
let screenshotModeTabId = null;
|
|
905
|
+
let screenshotModeSessionId = null;
|
|
906
|
+
let isCountdownActive = false; // Prevent multiple countdowns
|
|
907
|
+
|
|
881
908
|
// Async screenshot capture without blocking click recording
|
|
882
909
|
function captureScreenshotAsync(action, actionIndex) {
|
|
883
910
|
// Create the async capture promise
|
|
884
911
|
const capturePromise = (async () => {
|
|
885
912
|
try {
|
|
886
|
-
//
|
|
887
|
-
// We enforce 600ms minimum between screenshots to stay under the limit
|
|
888
|
-
const MIN_SCREENSHOT_INTERVAL = 600; // milliseconds
|
|
889
|
-
const now = Date.now();
|
|
890
|
-
const timeSinceLastScreenshot = now - lastScreenshotTime;
|
|
891
|
-
|
|
892
|
-
if (timeSinceLastScreenshot < MIN_SCREENSHOT_INTERVAL) {
|
|
893
|
-
// Wait until we can safely capture
|
|
894
|
-
const waitTime = MIN_SCREENSHOT_INTERVAL - timeSinceLastScreenshot;
|
|
895
|
-
await new Promise(resolve => setTimeout(resolve, waitTime));
|
|
896
|
-
}
|
|
897
|
-
|
|
913
|
+
// Throttling is handled centrally in background.js
|
|
898
914
|
const screenshotData = await captureWorkflowScreenshot();
|
|
899
915
|
|
|
900
916
|
if (screenshotData) {
|
|
@@ -1636,6 +1652,305 @@ async function stopWorkflowRecording() {
|
|
|
1636
1652
|
};
|
|
1637
1653
|
}
|
|
1638
1654
|
|
|
1655
|
+
// ============================================================
|
|
1656
|
+
// SCREENSHOT RECORDING MODE FUNCTIONS (v2.7.3)
|
|
1657
|
+
// Click-to-capture mode with configurable countdown
|
|
1658
|
+
// ============================================================
|
|
1659
|
+
|
|
1660
|
+
/**
|
|
1661
|
+
* Start screenshot recording mode
|
|
1662
|
+
* @param {object} settings - {countdown: number, hideCursor: boolean}
|
|
1663
|
+
*/
|
|
1664
|
+
function startScreenshotRecordingMode(settings = {}) {
|
|
1665
|
+
isScreenshotMode = true;
|
|
1666
|
+
screenshotModeSettings = {
|
|
1667
|
+
countdown: settings.countdown ?? 3,
|
|
1668
|
+
hideCursor: settings.hideCursor ?? true
|
|
1669
|
+
};
|
|
1670
|
+
screenshotModeActions = [];
|
|
1671
|
+
isCountdownActive = false;
|
|
1672
|
+
|
|
1673
|
+
// console.log('[Screenshot Mode] Starting with settings:', screenshotModeSettings);
|
|
1674
|
+
|
|
1675
|
+
// Create mode indicator
|
|
1676
|
+
createScreenshotModeIndicator();
|
|
1677
|
+
|
|
1678
|
+
// Add click listener (capture phase to intercept before page handlers)
|
|
1679
|
+
document.addEventListener('click', handleScreenshotModeClick, true);
|
|
1680
|
+
|
|
1681
|
+
// Note: Cursor hiding is now done per-capture in handleScreenshotModeClick()
|
|
1682
|
+
// This ensures the cursor is only hidden during the countdown/capture, not the entire session
|
|
1683
|
+
}
|
|
1684
|
+
|
|
1685
|
+
/**
|
|
1686
|
+
* Stop screenshot recording mode and return captured screenshots
|
|
1687
|
+
*/
|
|
1688
|
+
function stopScreenshotRecordingMode() {
|
|
1689
|
+
// console.log('[Screenshot Mode] Stopping, captured:', screenshotModeActions.length, 'screenshots');
|
|
1690
|
+
|
|
1691
|
+
isScreenshotMode = false;
|
|
1692
|
+
isCountdownActive = false;
|
|
1693
|
+
|
|
1694
|
+
// Remove click listener
|
|
1695
|
+
document.removeEventListener('click', handleScreenshotModeClick, true);
|
|
1696
|
+
|
|
1697
|
+
// Remove mode indicator
|
|
1698
|
+
removeScreenshotModeIndicator();
|
|
1699
|
+
|
|
1700
|
+
// Restore cursor
|
|
1701
|
+
document.body.classList.remove('chromedebug-hide-cursor');
|
|
1702
|
+
|
|
1703
|
+
// Reset state variables
|
|
1704
|
+
screenshotModeTabId = null;
|
|
1705
|
+
screenshotModeSessionId = null;
|
|
1706
|
+
|
|
1707
|
+
// Return captured screenshots
|
|
1708
|
+
const screenshots = screenshotModeActions.map(action => ({
|
|
1709
|
+
data: action.screenshot_data,
|
|
1710
|
+
timestamp: action.timestamp,
|
|
1711
|
+
x: action.x,
|
|
1712
|
+
y: action.y
|
|
1713
|
+
}));
|
|
1714
|
+
|
|
1715
|
+
screenshotModeActions = [];
|
|
1716
|
+
return screenshots;
|
|
1717
|
+
}
|
|
1718
|
+
|
|
1719
|
+
/**
|
|
1720
|
+
* Wait for browser repaint to complete
|
|
1721
|
+
* Uses double-requestAnimationFrame to ensure DOM changes are painted
|
|
1722
|
+
*/
|
|
1723
|
+
function waitForRepaint() {
|
|
1724
|
+
return new Promise(resolve => {
|
|
1725
|
+
requestAnimationFrame(() => {
|
|
1726
|
+
requestAnimationFrame(resolve);
|
|
1727
|
+
});
|
|
1728
|
+
});
|
|
1729
|
+
}
|
|
1730
|
+
|
|
1731
|
+
/**
|
|
1732
|
+
* Handle click event in screenshot mode
|
|
1733
|
+
*/
|
|
1734
|
+
async function handleScreenshotModeClick(event) {
|
|
1735
|
+
if (!isScreenshotMode) return;
|
|
1736
|
+
|
|
1737
|
+
// Prevent multiple countdowns at once
|
|
1738
|
+
if (isCountdownActive) {
|
|
1739
|
+
// console.log('[Screenshot Mode] Countdown already active, ignoring click');
|
|
1740
|
+
return;
|
|
1741
|
+
}
|
|
1742
|
+
|
|
1743
|
+
// CRITICAL: Capture tabId at click time (per Second Opinion review)
|
|
1744
|
+
// This ensures countdown captures the correct tab even if user switches
|
|
1745
|
+
const currentTabId = screenshotModeTabId;
|
|
1746
|
+
|
|
1747
|
+
// Get click position
|
|
1748
|
+
const clickX = event.clientX;
|
|
1749
|
+
const clickY = event.clientY;
|
|
1750
|
+
|
|
1751
|
+
// console.log('[Screenshot Mode] Click detected at:', clickX, clickY);
|
|
1752
|
+
|
|
1753
|
+
// Don't prevent default - let the click happen
|
|
1754
|
+
// The countdown gives time for page to respond
|
|
1755
|
+
|
|
1756
|
+
const countdown = screenshotModeSettings.countdown;
|
|
1757
|
+
|
|
1758
|
+
isCountdownActive = true;
|
|
1759
|
+
|
|
1760
|
+
try {
|
|
1761
|
+
// CRITICAL: Hide indicator BEFORE countdown/capture to prevent it appearing in screenshot
|
|
1762
|
+
// Using visibility:hidden (not display:none) to avoid layout reflow
|
|
1763
|
+
if (screenshotModeIndicator) {
|
|
1764
|
+
screenshotModeIndicator.style.visibility = 'hidden';
|
|
1765
|
+
}
|
|
1766
|
+
|
|
1767
|
+
// Hide cursor just before capture if setting enabled (per Second Opinion review)
|
|
1768
|
+
// Cursor is only hidden during countdown/capture, not the entire session
|
|
1769
|
+
if (screenshotModeSettings.hideCursor) {
|
|
1770
|
+
// Ensure style tag exists (lazy injection)
|
|
1771
|
+
if (!document.getElementById('chromedebug-hide-cursor-style')) {
|
|
1772
|
+
const style = document.createElement('style');
|
|
1773
|
+
style.id = 'chromedebug-hide-cursor-style';
|
|
1774
|
+
style.textContent = '.chromedebug-hide-cursor { cursor: none !important; }';
|
|
1775
|
+
document.head.appendChild(style);
|
|
1776
|
+
}
|
|
1777
|
+
document.body.classList.add('chromedebug-hide-cursor');
|
|
1778
|
+
await waitForRepaint(); // Ensure cursor is actually hidden before proceeding
|
|
1779
|
+
}
|
|
1780
|
+
|
|
1781
|
+
if (countdown > 0) {
|
|
1782
|
+
await showCountdownOverlay(countdown);
|
|
1783
|
+
// showCountdownOverlay now includes waitForRepaint after overlay removal
|
|
1784
|
+
} else {
|
|
1785
|
+
// No countdown - still need repaint delay for indicator to be hidden
|
|
1786
|
+
await waitForRepaint();
|
|
1787
|
+
}
|
|
1788
|
+
|
|
1789
|
+
// Capture screenshot via background script using captured tabId
|
|
1790
|
+
const screenshot = await captureScreenshotForMode(currentTabId);
|
|
1791
|
+
|
|
1792
|
+
if (screenshot) {
|
|
1793
|
+
screenshotModeActions.push({
|
|
1794
|
+
type: 'screenshot',
|
|
1795
|
+
timestamp: Date.now(),
|
|
1796
|
+
x: clickX,
|
|
1797
|
+
y: clickY,
|
|
1798
|
+
screenshot_data: screenshot
|
|
1799
|
+
});
|
|
1800
|
+
|
|
1801
|
+
// console.log('[Screenshot Mode] Screenshot captured, total:', screenshotModeActions.length);
|
|
1802
|
+
|
|
1803
|
+
// Update indicator count
|
|
1804
|
+
updateScreenshotModeIndicatorCount();
|
|
1805
|
+
}
|
|
1806
|
+
} catch (error) {
|
|
1807
|
+
console.error('[Screenshot Mode] Error capturing screenshot:', error);
|
|
1808
|
+
} finally {
|
|
1809
|
+
isCountdownActive = false;
|
|
1810
|
+
|
|
1811
|
+
// Restore cursor visibility after capture
|
|
1812
|
+
if (screenshotModeSettings.hideCursor) {
|
|
1813
|
+
document.body.classList.remove('chromedebug-hide-cursor');
|
|
1814
|
+
}
|
|
1815
|
+
|
|
1816
|
+
// Restore indicator visibility - check element still exists in DOM
|
|
1817
|
+
if (screenshotModeIndicator && document.body.contains(screenshotModeIndicator)) {
|
|
1818
|
+
screenshotModeIndicator.style.visibility = 'visible';
|
|
1819
|
+
}
|
|
1820
|
+
}
|
|
1821
|
+
}
|
|
1822
|
+
|
|
1823
|
+
/**
|
|
1824
|
+
* Capture screenshot through background script
|
|
1825
|
+
*/
|
|
1826
|
+
async function captureScreenshotForMode(tabId) {
|
|
1827
|
+
return new Promise((resolve, reject) => {
|
|
1828
|
+
if (!isExtensionValid()) {
|
|
1829
|
+
reject(new Error('Extension context invalidated'));
|
|
1830
|
+
return;
|
|
1831
|
+
}
|
|
1832
|
+
|
|
1833
|
+
chrome.runtime.sendMessage({
|
|
1834
|
+
action: 'captureScreenshotForMode',
|
|
1835
|
+
tabId: tabId
|
|
1836
|
+
}, (response) => {
|
|
1837
|
+
if (chrome.runtime.lastError) {
|
|
1838
|
+
reject(chrome.runtime.lastError);
|
|
1839
|
+
return;
|
|
1840
|
+
}
|
|
1841
|
+
if (response && response.success) {
|
|
1842
|
+
resolve(response.screenshot);
|
|
1843
|
+
} else {
|
|
1844
|
+
reject(new Error(response?.error || 'Failed to capture screenshot'));
|
|
1845
|
+
}
|
|
1846
|
+
});
|
|
1847
|
+
});
|
|
1848
|
+
}
|
|
1849
|
+
|
|
1850
|
+
/**
|
|
1851
|
+
* Show countdown overlay before capturing screenshot
|
|
1852
|
+
*/
|
|
1853
|
+
async function showCountdownOverlay(seconds) {
|
|
1854
|
+
const overlay = document.createElement('div');
|
|
1855
|
+
overlay.id = 'chromedebug-countdown-overlay';
|
|
1856
|
+
overlay.style.cssText = `
|
|
1857
|
+
position: fixed;
|
|
1858
|
+
top: 0; left: 0; right: 0; bottom: 0;
|
|
1859
|
+
display: flex;
|
|
1860
|
+
align-items: center;
|
|
1861
|
+
justify-content: center;
|
|
1862
|
+
z-index: 2147483647;
|
|
1863
|
+
pointer-events: none;
|
|
1864
|
+
background: rgba(0,0,0,0.3);
|
|
1865
|
+
`;
|
|
1866
|
+
|
|
1867
|
+
const circle = document.createElement('div');
|
|
1868
|
+
circle.style.cssText = `
|
|
1869
|
+
width: 200px; height: 200px;
|
|
1870
|
+
border-radius: 50%;
|
|
1871
|
+
background: rgba(0,0,0,0.8);
|
|
1872
|
+
display: flex;
|
|
1873
|
+
align-items: center;
|
|
1874
|
+
justify-content: center;
|
|
1875
|
+
color: white;
|
|
1876
|
+
font-size: 120px;
|
|
1877
|
+
font-family: Arial, sans-serif;
|
|
1878
|
+
font-weight: bold;
|
|
1879
|
+
`;
|
|
1880
|
+
|
|
1881
|
+
overlay.appendChild(circle);
|
|
1882
|
+
document.body.appendChild(overlay);
|
|
1883
|
+
|
|
1884
|
+
for (let i = seconds; i > 0; i--) {
|
|
1885
|
+
circle.textContent = i;
|
|
1886
|
+
await new Promise(r => setTimeout(r, 1000));
|
|
1887
|
+
}
|
|
1888
|
+
|
|
1889
|
+
overlay.remove();
|
|
1890
|
+
|
|
1891
|
+
// Wait for browser to complete repaint after overlay removal
|
|
1892
|
+
// This ensures the overlay doesn't appear in the captured screenshot
|
|
1893
|
+
await waitForRepaint();
|
|
1894
|
+
}
|
|
1895
|
+
|
|
1896
|
+
/**
|
|
1897
|
+
* Create screenshot mode indicator
|
|
1898
|
+
*/
|
|
1899
|
+
function createScreenshotModeIndicator() {
|
|
1900
|
+
// Remove any existing indicator
|
|
1901
|
+
removeScreenshotModeIndicator();
|
|
1902
|
+
|
|
1903
|
+
const indicator = document.createElement('div');
|
|
1904
|
+
indicator.id = 'chromedebug-screenshot-indicator';
|
|
1905
|
+
indicator.style.cssText = `
|
|
1906
|
+
position: fixed;
|
|
1907
|
+
top: 10px; right: 10px;
|
|
1908
|
+
padding: 8px 16px;
|
|
1909
|
+
background: #FF9800;
|
|
1910
|
+
color: white;
|
|
1911
|
+
border-radius: 20px;
|
|
1912
|
+
font-family: Arial, sans-serif;
|
|
1913
|
+
font-size: 14px;
|
|
1914
|
+
z-index: 2147483647;
|
|
1915
|
+
box-shadow: 0 2px 10px rgba(0,0,0,0.3);
|
|
1916
|
+
display: flex;
|
|
1917
|
+
align-items: center;
|
|
1918
|
+
gap: 8px;
|
|
1919
|
+
`;
|
|
1920
|
+
indicator.innerHTML = `
|
|
1921
|
+
<span style="font-size: 16px;">📸</span>
|
|
1922
|
+
<span>Click to capture</span>
|
|
1923
|
+
<span id="chromedebug-screenshot-count" style="background: rgba(255,255,255,0.3); padding: 2px 8px; border-radius: 10px; font-weight: bold;">0</span>
|
|
1924
|
+
`;
|
|
1925
|
+
document.body.appendChild(indicator);
|
|
1926
|
+
screenshotModeIndicator = indicator;
|
|
1927
|
+
}
|
|
1928
|
+
|
|
1929
|
+
/**
|
|
1930
|
+
* Remove screenshot mode indicator
|
|
1931
|
+
*/
|
|
1932
|
+
function removeScreenshotModeIndicator() {
|
|
1933
|
+
if (screenshotModeIndicator) {
|
|
1934
|
+
screenshotModeIndicator.remove();
|
|
1935
|
+
screenshotModeIndicator = null;
|
|
1936
|
+
}
|
|
1937
|
+
// Also try to remove by ID in case reference was lost
|
|
1938
|
+
const existing = document.getElementById('chromedebug-screenshot-indicator');
|
|
1939
|
+
if (existing) {
|
|
1940
|
+
existing.remove();
|
|
1941
|
+
}
|
|
1942
|
+
}
|
|
1943
|
+
|
|
1944
|
+
/**
|
|
1945
|
+
* Update screenshot mode indicator count
|
|
1946
|
+
*/
|
|
1947
|
+
function updateScreenshotModeIndicatorCount() {
|
|
1948
|
+
const countElement = document.getElementById('chromedebug-screenshot-count');
|
|
1949
|
+
if (countElement) {
|
|
1950
|
+
countElement.textContent = screenshotModeActions.length;
|
|
1951
|
+
}
|
|
1952
|
+
}
|
|
1953
|
+
|
|
1639
1954
|
// Capture DOM snapshot with form values and states
|
|
1640
1955
|
function captureDOMSnapshot() {
|
|
1641
1956
|
const snapshot = {
|
|
@@ -1962,6 +2277,9 @@ async function recordScreenClick(event) {
|
|
|
1962
2277
|
xpath: getXPath(element),
|
|
1963
2278
|
x: event.clientX,
|
|
1964
2279
|
y: event.clientY,
|
|
2280
|
+
viewportWidth: window.innerWidth, // For scaling during playback
|
|
2281
|
+
viewportHeight: window.innerHeight,
|
|
2282
|
+
devicePixelRatio: window.devicePixelRatio, // Critical for Retina/HiDPI display scaling
|
|
1965
2283
|
timestamp: Date.now() - recordingScheduledStartTime // Relative timestamp from scheduled start
|
|
1966
2284
|
};
|
|
1967
2285
|
|
|
@@ -2159,21 +2477,51 @@ function recordScreenMouseUp(event) {
|
|
|
2159
2477
|
screenRecordingDragStartElement = null;
|
|
2160
2478
|
}
|
|
2161
2479
|
|
|
2480
|
+
// Mouse movement tracking (PRO-only feature)
|
|
2481
|
+
function handleMouseMove(e) {
|
|
2482
|
+
const now = Date.now();
|
|
2483
|
+
if (!mouseTrackingEnabled || !isScreenRecording) return;
|
|
2484
|
+
if (now - lastMouseTime < mouseSampleInterval) return;
|
|
2485
|
+
|
|
2486
|
+
lastMouseTime = now;
|
|
2487
|
+
sendScreenInteraction({
|
|
2488
|
+
type: 'mousemove',
|
|
2489
|
+
x: e.clientX,
|
|
2490
|
+
y: e.clientY,
|
|
2491
|
+
pageX: e.pageX,
|
|
2492
|
+
pageY: e.pageY,
|
|
2493
|
+
viewportWidth: window.innerWidth,
|
|
2494
|
+
viewportHeight: window.innerHeight,
|
|
2495
|
+
devicePixelRatio: window.devicePixelRatio, // Critical for Retina/HiDPI display scaling
|
|
2496
|
+
timestamp: now - recordingScheduledStartTime
|
|
2497
|
+
});
|
|
2498
|
+
}
|
|
2499
|
+
|
|
2500
|
+
function startMouseTracking() {
|
|
2501
|
+
if (!mouseTrackingEnabled) return;
|
|
2502
|
+
document.addEventListener('mousemove', handleMouseMove, { passive: true });
|
|
2503
|
+
}
|
|
2504
|
+
|
|
2505
|
+
function stopMouseTracking() {
|
|
2506
|
+
document.removeEventListener('mousemove', handleMouseMove);
|
|
2507
|
+
lastMouseTime = 0;
|
|
2508
|
+
}
|
|
2509
|
+
|
|
2162
2510
|
// Global variables for synchronized timing
|
|
2163
2511
|
let recordingScheduledStartTime = null;
|
|
2164
2512
|
let recordingSessionId = null;
|
|
2165
2513
|
|
|
2166
2514
|
// Start/stop screen interaction tracking
|
|
2167
|
-
function startScreenInteractionTracking(scheduledStartTime, sessionId) {
|
|
2515
|
+
function startScreenInteractionTracking(scheduledStartTime, sessionId, videoMode = false) {
|
|
2168
2516
|
// Store the scheduled start time and session ID for timestamp calculations
|
|
2169
2517
|
recordingScheduledStartTime = scheduledStartTime;
|
|
2170
2518
|
recordingSessionId = sessionId;
|
|
2171
2519
|
|
|
2172
2520
|
// console.log('[ChromePilot] Screen interaction tracking scheduled to start at:', new Date(scheduledStartTime));
|
|
2173
2521
|
|
|
2174
|
-
// Trigger visual feedback start
|
|
2522
|
+
// Trigger visual feedback start (pass videoMode for hidden indicator)
|
|
2175
2523
|
if (window.screenCaptureVisualFeedback) {
|
|
2176
|
-
window.screenCaptureVisualFeedback.handleStartScreenCapture({ sessionId, scheduledStartTime });
|
|
2524
|
+
window.screenCaptureVisualFeedback.handleStartScreenCapture({ sessionId, scheduledStartTime, videoMode });
|
|
2177
2525
|
}
|
|
2178
2526
|
|
|
2179
2527
|
// Wait until the scheduled start time before activating tracking
|
|
@@ -2190,6 +2538,9 @@ function startScreenInteractionTracking(scheduledStartTime, sessionId) {
|
|
|
2190
2538
|
document.addEventListener('mousedown', recordScreenMouseDown, true);
|
|
2191
2539
|
document.addEventListener('mouseup', recordScreenMouseUp, true);
|
|
2192
2540
|
|
|
2541
|
+
// Start mouse tracking if enabled (PRO-only feature)
|
|
2542
|
+
startMouseTracking();
|
|
2543
|
+
|
|
2193
2544
|
// console.log('[ChromePilot] Screen interaction tracking started at scheduled time');
|
|
2194
2545
|
}, waitTime);
|
|
2195
2546
|
}
|
|
@@ -2197,6 +2548,9 @@ function startScreenInteractionTracking(scheduledStartTime, sessionId) {
|
|
|
2197
2548
|
function stopScreenInteractionTracking() {
|
|
2198
2549
|
isScreenRecording = false;
|
|
2199
2550
|
|
|
2551
|
+
// Stop mouse tracking (PRO-only feature)
|
|
2552
|
+
stopMouseTracking();
|
|
2553
|
+
|
|
2200
2554
|
// Array of cleanup operations for systematic execution
|
|
2201
2555
|
const cleanupOperations = [
|
|
2202
2556
|
() => document.removeEventListener('click', recordScreenClick, true),
|
|
@@ -2302,6 +2656,11 @@ let screenRecordingDragStartElement = null;
|
|
|
2302
2656
|
let screenRecordingDragStartX = 0;
|
|
2303
2657
|
let screenRecordingDragStartY = 0;
|
|
2304
2658
|
|
|
2659
|
+
// Mouse movement tracking (PRO-only feature)
|
|
2660
|
+
let mouseTrackingEnabled = false;
|
|
2661
|
+
let mouseSampleInterval = 100; // User-selectable: 50, 100, or 200ms
|
|
2662
|
+
let lastMouseTime = 0;
|
|
2663
|
+
|
|
2305
2664
|
// Full data recording system components
|
|
2306
2665
|
let fullDataRecording = null;
|
|
2307
2666
|
let dataBuffer = null;
|
|
@@ -2937,11 +3296,17 @@ class ScreenCaptureVisualFeedback {
|
|
|
2937
3296
|
}
|
|
2938
3297
|
|
|
2939
3298
|
handleStartScreenCapture(message) {
|
|
2940
|
-
//
|
|
3299
|
+
// If we're already in video mode recording and got a redundant start message,
|
|
3300
|
+
// ignore it to prevent re-showing the indicator
|
|
3301
|
+
if (this.isRecording && this.isVideoMode) {
|
|
3302
|
+
console.log('[Video Mode Debug] Already in video mode recording, ignoring redundant start message');
|
|
3303
|
+
return;
|
|
3304
|
+
}
|
|
3305
|
+
|
|
2941
3306
|
this.isRecording = true;
|
|
2942
3307
|
this.actionCount = 0;
|
|
2943
|
-
this.
|
|
2944
|
-
|
|
3308
|
+
this.isVideoMode = message.videoMode || false;
|
|
3309
|
+
this.showRecordingIndicator(this.isVideoMode);
|
|
2945
3310
|
}
|
|
2946
3311
|
|
|
2947
3312
|
handleScreenCaptureInteraction(interaction) {
|
|
@@ -3023,7 +3388,15 @@ class ScreenCaptureVisualFeedback {
|
|
|
3023
3388
|
// Action counter is now part of unified indicator, handled by hideRecordingIndicator
|
|
3024
3389
|
}
|
|
3025
3390
|
|
|
3026
|
-
showRecordingIndicator() {
|
|
3391
|
+
showRecordingIndicator(videoMode = false) {
|
|
3392
|
+
// Store video mode for use in countdown sequence
|
|
3393
|
+
this.isVideoMode = videoMode;
|
|
3394
|
+
console.log('[Video Mode Debug] showRecordingIndicator called with videoMode:', videoMode);
|
|
3395
|
+
|
|
3396
|
+
// VIDEO MODE: Show countdown but hide the control menu after
|
|
3397
|
+
// The countdown (3, 2, 1) will show so user knows when recording starts
|
|
3398
|
+
// After countdown, the indicator is removed (handled in startCountdownSequence)
|
|
3399
|
+
|
|
3027
3400
|
// CRITICAL FIX: Clear any existing countdown interval FIRST
|
|
3028
3401
|
if (this.countdownInterval) {
|
|
3029
3402
|
// console.log('[ScreenCapture] Clearing existing countdown interval');
|
|
@@ -3119,22 +3492,39 @@ class ScreenCaptureVisualFeedback {
|
|
|
3119
3492
|
startCountdownSequence() {
|
|
3120
3493
|
let count = 3;
|
|
3121
3494
|
|
|
3495
|
+
// Video mode: simpler countdown without action badge
|
|
3496
|
+
const getCountdownHTML = (num) => {
|
|
3497
|
+
if (this.isVideoMode) {
|
|
3498
|
+
return `
|
|
3499
|
+
<span style="color: #FF9800; font-size: 20px; animation: pulse 1.5s infinite;">🎥</span>
|
|
3500
|
+
<span>Video recording in ${num}...</span>
|
|
3501
|
+
`;
|
|
3502
|
+
}
|
|
3503
|
+
return `
|
|
3504
|
+
<span style="color: #FF9800; font-size: 20px; animation: pulse 1.5s infinite;">●</span>
|
|
3505
|
+
<span>Starting in ${num}...</span>
|
|
3506
|
+
<span class="action-count-badge" style="background: rgba(255,255,255,0.2); padding: 2px 8px; border-radius: 12px; font-size: 12px;">0 actions</span>
|
|
3507
|
+
`;
|
|
3508
|
+
};
|
|
3509
|
+
|
|
3122
3510
|
// Initial countdown display
|
|
3123
|
-
this.recordingIndicatorElement.innerHTML =
|
|
3124
|
-
<span style="color: #FF9800; font-size: 20px; animation: pulse 1.5s infinite;">●</span>
|
|
3125
|
-
<span>Starting in ${count}...</span>
|
|
3126
|
-
<span class="action-count-badge" style="background: rgba(255,255,255,0.2); padding: 2px 8px; border-radius: 12px; font-size: 12px;">0 actions</span>
|
|
3127
|
-
`;
|
|
3511
|
+
this.recordingIndicatorElement.innerHTML = getCountdownHTML(count);
|
|
3128
3512
|
|
|
3129
3513
|
this.countdownInterval = setInterval(() => {
|
|
3130
3514
|
count--;
|
|
3515
|
+
|
|
3516
|
+
// VIDEO MODE: Hide indicator at count=1 (before final frame) to ensure it's not captured
|
|
3517
|
+
// Use visibility+opacity for immediate hiding without layout reflow
|
|
3518
|
+
if (count === 1 && this.isVideoMode && this.recordingIndicatorElement) {
|
|
3519
|
+
this.recordingIndicatorElement.style.visibility = 'hidden';
|
|
3520
|
+
this.recordingIndicatorElement.style.opacity = '0';
|
|
3521
|
+
}
|
|
3522
|
+
|
|
3131
3523
|
if (count > 0) {
|
|
3132
|
-
// Update countdown
|
|
3133
|
-
this.
|
|
3134
|
-
|
|
3135
|
-
|
|
3136
|
-
<span class="action-count-badge" style="background: rgba(255,255,255,0.2); padding: 2px 8px; border-radius: 12px; font-size: 12px;">0 actions</span>
|
|
3137
|
-
`;
|
|
3524
|
+
// Update countdown (but already hidden for video mode at count=1)
|
|
3525
|
+
if (!this.isVideoMode || count > 1) {
|
|
3526
|
+
this.recordingIndicatorElement.innerHTML = getCountdownHTML(count);
|
|
3527
|
+
}
|
|
3138
3528
|
} else {
|
|
3139
3529
|
// Countdown complete! Notify background to start inactivity monitoring NOW
|
|
3140
3530
|
if (isExtensionValid()) {
|
|
@@ -3143,6 +3533,22 @@ class ScreenCaptureVisualFeedback {
|
|
|
3143
3533
|
});
|
|
3144
3534
|
}
|
|
3145
3535
|
|
|
3536
|
+
// VIDEO MODE: Hide indicator completely after countdown
|
|
3537
|
+
// User must stop recording via popup (opens from browser extension icon)
|
|
3538
|
+
console.log('[Video Mode Debug] Countdown complete. isVideoMode:', this.isVideoMode);
|
|
3539
|
+
if (this.isVideoMode) {
|
|
3540
|
+
console.log('[Video Mode Debug] HIDING mini controller for video mode');
|
|
3541
|
+
clearInterval(this.countdownInterval);
|
|
3542
|
+
this.countdownInterval = null;
|
|
3543
|
+
// Remove indicator element completely
|
|
3544
|
+
if (this.recordingIndicatorElement && this.recordingIndicatorElement.parentNode) {
|
|
3545
|
+
this.recordingIndicatorElement.parentNode.removeChild(this.recordingIndicatorElement);
|
|
3546
|
+
this.recordingIndicatorElement = null;
|
|
3547
|
+
console.log('[Video Mode Debug] Mini controller REMOVED from DOM');
|
|
3548
|
+
}
|
|
3549
|
+
return; // Exit early, no need to show recording UI
|
|
3550
|
+
}
|
|
3551
|
+
|
|
3146
3552
|
// Switch to recording display with two-row layout
|
|
3147
3553
|
this.recordingIndicatorElement.style.pointerEvents = 'auto'; // Enable clicking
|
|
3148
3554
|
this.recordingIndicatorElement.innerHTML = `
|
|
@@ -19,7 +19,7 @@ const FIREBASE_CONFIG = {
|
|
|
19
19
|
const FUNCTIONS_URL = `https://us-central1-${FIREBASE_CONFIG.projectId}.cloudfunctions.net`;
|
|
20
20
|
|
|
21
21
|
// LemonSqueezy checkout URL (Production)
|
|
22
|
-
const LEMONSQUEEZY_CHECKOUT_URL = 'https://chromedebug.com/buy/996773cb-682b-430f-b9e3-9ce2130bd967';
|
|
22
|
+
const LEMONSQUEEZY_CHECKOUT_URL = 'https://chromedebug.com/checkout/buy/996773cb-682b-430f-b9e3-9ce2130bd967';
|
|
23
23
|
|
|
24
24
|
// Make available globally for service worker context
|
|
25
25
|
if (typeof self !== 'undefined') {
|
|
@@ -24,7 +24,7 @@ const FIREBASE_CONFIG = {
|
|
|
24
24
|
const FUNCTIONS_URL = `https://us-central1-${FIREBASE_CONFIG.projectId}.cloudfunctions.net`;
|
|
25
25
|
|
|
26
26
|
// LemonSqueezy checkout URL (Production)
|
|
27
|
-
const LEMONSQUEEZY_CHECKOUT_URL = 'https://chromedebug.com/buy/996773cb-682b-430f-b9e3-9ce2130bd967';
|
|
27
|
+
const LEMONSQUEEZY_CHECKOUT_URL = 'https://chromedebug.com/checkout/buy/996773cb-682b-430f-b9e3-9ce2130bd967';
|
|
28
28
|
|
|
29
29
|
// Export for ES6 modules (popup.js, firebase-client.js)
|
|
30
30
|
export { FIREBASE_CONFIG, FUNCTIONS_URL, LEMONSQUEEZY_CHECKOUT_URL };
|