@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.
@@ -56,34 +56,42 @@ class FrameCapture {
56
56
  }, this.emergencyMaxDuration);
57
57
 
58
58
  // Update settings with passed values or apply mode-specific defaults
59
+ console.log('[FrameCapture] Received settings:', settings, 'Mode:', this.mode);
59
60
  if (this.mode === 'browser-only') {
60
61
  // Browser-only mode: ultra-low resolution and quality for storage efficiency
61
62
  this.settings = {
62
63
  frameRate: settings.frameRate || 1,
63
64
  imageQuality: settings.imageQuality || 15, // Lower quality for browser storage
64
- frameFlash: settings.frameFlash !== false
65
+ frameFlash: settings.frameFlash !== false,
66
+ videoMode: settings?.videoMode || false
65
67
  };
66
68
  } else {
67
69
  // Server mode: standard settings
68
70
  this.settings = {
69
71
  frameRate: settings.frameRate || 1,
70
72
  imageQuality: settings.imageQuality || 30,
71
- frameFlash: settings.frameFlash !== false
73
+ frameFlash: settings.frameFlash !== false,
74
+ videoMode: settings?.videoMode || false
72
75
  };
73
76
  }
77
+ console.log('[FrameCapture] Using settings:', this.settings, 'Interval:', this.settings.frameRate * 1000, 'ms');
74
78
 
75
79
  // Trigger visual feedback system start
76
80
  chrome.runtime.sendMessage({
77
81
  type: 'start-screen-capture-tracking',
78
- sessionId: this.sessionId
82
+ sessionId: this.sessionId,
83
+ tabId: this.tabId, // Include tabId for offscreen document routing
84
+ videoMode: this.settings.videoMode || false
79
85
  }).catch(() => {});
80
86
 
81
87
  // Create a synthetic first frame at scheduled start time to catch early logs
88
+ // Mark as synthetic so video exporter can skip it (1x1 placeholder can't be exported)
82
89
  const syntheticFrame = {
83
90
  timestamp: 0,
84
91
  absoluteTimestamp: this.scheduledStartTime,
85
92
  imageData: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==', // 1x1 transparent pixel
86
- logs: []
93
+ logs: [],
94
+ isSynthetic: true // Flag to identify placeholder frames for video export filtering
87
95
  };
88
96
  this.frames.push(syntheticFrame);
89
97
  this.totalFramesCaptured++;
@@ -102,8 +110,8 @@ class FrameCapture {
102
110
  mandatory: {
103
111
  chromeMediaSource: 'tab',
104
112
  chromeMediaSourceId: streamId,
105
- maxWidth: 640, // Standard resolution for server storage
106
- maxHeight: 360 // Standard resolution for server storage
113
+ maxWidth: settings.maxWidth || 640, // Default 640, allow override for video export
114
+ maxHeight: settings.maxHeight || 360 // Default 360, allow override for video export
107
115
  }
108
116
  };
109
117
 
@@ -180,7 +188,8 @@ class FrameCapture {
180
188
 
181
189
  // Trigger visual feedback for frame captured
182
190
  chrome.runtime.sendMessage({
183
- type: 'show-screen-capture-flash'
191
+ type: 'show-screen-capture-flash',
192
+ tabId: this.tabId // Include tabId for offscreen document routing
184
193
  }).catch(() => {});
185
194
 
186
195
  // Send frame immediately to keep memory usage low
@@ -266,7 +275,8 @@ class FrameCapture {
266
275
 
267
276
  // Stop visual feedback system
268
277
  chrome.runtime.sendMessage({
269
- type: 'stop-screen-capture-tracking'
278
+ type: 'stop-screen-capture-tracking',
279
+ tabId: this.tabId // Include tabId for offscreen document routing
270
280
  }).catch(() => {});
271
281
 
272
282
  // Notify completion
@@ -338,7 +348,8 @@ class FrameCapture {
338
348
 
339
349
  // Trigger visual feedback for manual snapshot
340
350
  chrome.runtime.sendMessage({
341
- type: 'show-screen-capture-flash'
351
+ type: 'show-screen-capture-flash',
352
+ tabId: this.tabId // Include tabId for offscreen document routing
342
353
  }).catch(() => {});
343
354
 
344
355
  // Flash indicator for manual snapshot
@@ -379,12 +390,22 @@ class FrameCapture {
379
390
 
380
391
  const batch = this.frames.splice(0, 5); // Send up to 5 frames at a time
381
392
 
393
+ // Filter out synthetic frames before sending to server
394
+ // Synthetic frames are 1x1 transparent pixels used only for early log capture
395
+ // They can't be exported to video and the server may reject the isSynthetic field
396
+ const realFrames = batch.filter(frame => !frame.isSynthetic);
397
+
398
+ if (realFrames.length === 0) {
399
+ // All frames in batch were synthetic, nothing to send
400
+ return;
401
+ }
402
+
382
403
  chrome.runtime.sendMessage({
383
404
  type: 'frame-batch-ready',
384
405
  target: 'background',
385
406
  data: {
386
407
  sessionId: this.sessionId,
387
- frames: batch
408
+ frames: realFrames
388
409
  }
389
410
  });
390
411
  }
@@ -0,0 +1,193 @@
1
+ /**
2
+ * Image Processor Utility (v2.7.3)
3
+ *
4
+ * Handles watermarking and image processing for Chrome Debug extension.
5
+ *
6
+ * CRITICAL DESIGN DECISION (from Second Opinion review):
7
+ * - Watermarks are applied at DISPLAY/EXPORT time, NOT storage time
8
+ * - This allows upgrade recovery (users who upgrade mid-session get clean images)
9
+ * - Storage remains clean without watermarks
10
+ */
11
+
12
+ // Watermark configuration
13
+ const WATERMARK_CONFIG = {
14
+ text: 'Chrome Debug FREE',
15
+ position: 'bottom-right',
16
+ opacity: 0.7,
17
+ fontSize: 14,
18
+ padding: 10,
19
+ backgroundColor: 'rgba(0, 0, 0, 0.7)',
20
+ textColor: '#ffffff'
21
+ };
22
+
23
+ /**
24
+ * Check if this is the PRO version
25
+ * @returns {boolean}
26
+ */
27
+ function isProVersion() {
28
+ try {
29
+ const manifest = chrome.runtime.getManifest();
30
+ return manifest.name.includes('PRO');
31
+ } catch (e) {
32
+ // If we can't access manifest, assume FREE version
33
+ return false;
34
+ }
35
+ }
36
+
37
+ /**
38
+ * Check if watermarking should be applied
39
+ * @returns {boolean}
40
+ */
41
+ function shouldWatermark() {
42
+ return !isProVersion();
43
+ }
44
+
45
+ /**
46
+ * Apply watermark to image data URL at DISPLAY or EXPORT time
47
+ * @param {string} imageDataUrl - Base64 image data URL
48
+ * @param {object} options - Watermark options (optional overrides)
49
+ * @returns {Promise<string>} - Watermarked image data URL
50
+ */
51
+ async function applyWatermark(imageDataUrl, options = {}) {
52
+ const config = {
53
+ ...WATERMARK_CONFIG,
54
+ ...options
55
+ };
56
+
57
+ return new Promise((resolve, reject) => {
58
+ const img = new Image();
59
+
60
+ img.onload = () => {
61
+ try {
62
+ const canvas = document.createElement('canvas');
63
+ canvas.width = img.width;
64
+ canvas.height = img.height;
65
+ const ctx = canvas.getContext('2d');
66
+
67
+ // Draw original image
68
+ ctx.drawImage(img, 0, 0);
69
+
70
+ // Calculate badge dimensions
71
+ ctx.font = `bold ${config.fontSize}px Arial, sans-serif`;
72
+ const textMetrics = ctx.measureText(config.text);
73
+ const textWidth = textMetrics.width;
74
+ const badgeWidth = textWidth + config.padding * 2;
75
+ const badgeHeight = config.fontSize + config.padding * 2;
76
+
77
+ // Calculate position (bottom-right corner)
78
+ let x, y;
79
+ switch (config.position) {
80
+ case 'bottom-right':
81
+ x = canvas.width - badgeWidth - 10;
82
+ y = canvas.height - badgeHeight - 10;
83
+ break;
84
+ case 'bottom-left':
85
+ x = 10;
86
+ y = canvas.height - badgeHeight - 10;
87
+ break;
88
+ case 'top-right':
89
+ x = canvas.width - badgeWidth - 10;
90
+ y = 10;
91
+ break;
92
+ case 'top-left':
93
+ x = 10;
94
+ y = 10;
95
+ break;
96
+ default:
97
+ x = canvas.width - badgeWidth - 10;
98
+ y = canvas.height - badgeHeight - 10;
99
+ }
100
+
101
+ // Draw semi-transparent badge background with rounded corners
102
+ ctx.fillStyle = config.backgroundColor;
103
+ ctx.beginPath();
104
+ roundRect(ctx, x, y, badgeWidth, badgeHeight, 5);
105
+ ctx.fill();
106
+
107
+ // Draw text
108
+ ctx.fillStyle = config.textColor;
109
+ ctx.fillText(config.text, x + config.padding, y + config.fontSize + config.padding / 2);
110
+
111
+ // Return watermarked image
112
+ resolve(canvas.toDataURL('image/jpeg', 0.92));
113
+ } catch (error) {
114
+ console.error('[ImageProcessor] Error applying watermark:', error);
115
+ // Return original image on error
116
+ resolve(imageDataUrl);
117
+ }
118
+ };
119
+
120
+ img.onerror = () => {
121
+ console.error('[ImageProcessor] Failed to load image for watermarking');
122
+ // Return original image on error
123
+ resolve(imageDataUrl);
124
+ };
125
+
126
+ img.src = imageDataUrl;
127
+ });
128
+ }
129
+
130
+ /**
131
+ * Helper function to draw rounded rectangle
132
+ */
133
+ function roundRect(ctx, x, y, width, height, radius) {
134
+ ctx.moveTo(x + radius, y);
135
+ ctx.lineTo(x + width - radius, y);
136
+ ctx.arcTo(x + width, y, x + width, y + radius, radius);
137
+ ctx.lineTo(x + width, y + height - radius);
138
+ ctx.arcTo(x + width, y + height, x + width - radius, y + height, radius);
139
+ ctx.lineTo(x + radius, y + height);
140
+ ctx.arcTo(x, y + height, x, y + height - radius, radius);
141
+ ctx.lineTo(x, y + radius);
142
+ ctx.arcTo(x, y, x + radius, y, radius);
143
+ ctx.closePath();
144
+ }
145
+
146
+ /**
147
+ * Process image for display - applies watermark if FREE version
148
+ * @param {string} imageDataUrl - Base64 image data URL
149
+ * @returns {Promise<string>} - Processed image data URL
150
+ */
151
+ async function processImageForDisplay(imageDataUrl) {
152
+ if (shouldWatermark()) {
153
+ return await applyWatermark(imageDataUrl);
154
+ }
155
+ return imageDataUrl;
156
+ }
157
+
158
+ /**
159
+ * Process image for export - applies watermark if FREE version
160
+ * @param {string} imageDataUrl - Base64 image data URL
161
+ * @returns {Promise<string>} - Processed image data URL
162
+ */
163
+ async function processImageForExport(imageDataUrl) {
164
+ if (shouldWatermark()) {
165
+ return await applyWatermark(imageDataUrl);
166
+ }
167
+ return imageDataUrl;
168
+ }
169
+
170
+ /**
171
+ * Process multiple images for display/export
172
+ * @param {string[]} imageDataUrls - Array of base64 image data URLs
173
+ * @returns {Promise<string[]>} - Array of processed image data URLs
174
+ */
175
+ async function processImagesForDisplay(imageDataUrls) {
176
+ if (shouldWatermark()) {
177
+ return await Promise.all(imageDataUrls.map(url => applyWatermark(url)));
178
+ }
179
+ return imageDataUrls;
180
+ }
181
+
182
+ // Export functions for use in other scripts
183
+ if (typeof window !== 'undefined') {
184
+ window.ChromeDebugImageProcessor = {
185
+ isProVersion,
186
+ shouldWatermark,
187
+ applyWatermark,
188
+ processImageForDisplay,
189
+ processImageForExport,
190
+ processImagesForDisplay,
191
+ WATERMARK_CONFIG
192
+ };
193
+ }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "manifest_version": 3,
3
3
  "name": "ChromeDebug MCP Assistant FREE",
4
- "version": "2.7.2",
4
+ "version": "2.7.3",
5
5
  "description": "AI-powered browser debugging with visual element selection and screen recording for developers",
6
6
  "permissions": [
7
7
  "activeTab",
@@ -151,6 +151,24 @@
151
151
  color: #f44336;
152
152
  font-weight: bold;
153
153
  }
154
+
155
+ .delete-all-btn {
156
+ transition: background 0.2s, transform 0.1s;
157
+ }
158
+
159
+ .delete-all-btn:hover {
160
+ background: #d32f2f !important;
161
+ transform: scale(1.05);
162
+ }
163
+
164
+ .delete-all-btn:active {
165
+ transform: scale(0.95);
166
+ }
167
+
168
+ .delete-all-btn:disabled {
169
+ opacity: 0.5;
170
+ cursor: not-allowed;
171
+ }
154
172
  </style>
155
173
  </head>
156
174
  <body>
@@ -171,7 +189,7 @@
171
189
  <span class="server-status disconnected" id="serverStatus"></span>
172
190
  <span id="statusText">Checking...</span>
173
191
  </div>
174
- <a href="https://github.com/dynamicupgrade/ChromeDebug" target="_blank" style="font-size: 10px; color: #2196F3; text-decoration: none;">📚 Documentation</a>
192
+ <a href="https://www.npmjs.com/package/@dynamicu/chromedebug-mcp" target="_blank" style="font-size: 10px; color: #2196F3; text-decoration: none;">📚 Documentation</a>
175
193
  </div>
176
194
  <div style="flex: 0 0 auto; width: 200px;"></div>
177
195
  </div>
@@ -283,11 +301,45 @@
283
301
  Start Workflow Recording
284
302
  <span id="workflowProBadge" style="position: absolute; top: -8px; right: -8px; background: #ff6b6b; color: white; font-size: 9px; padding: 2px 6px; border-radius: 10px; font-weight: bold; display: none;">PRO</span>
285
303
  </button>
304
+
305
+ <!-- Screenshot Recording Button -->
306
+ <button class="record-btn" id="screenshotRecordBtn" style="background: #FF9800; position: relative; padding: 8px; font-size: 11px; margin-top: 4px;">
307
+ 📸 Start Screenshot Recording
308
+ <span id="screenshotProBadge" style="position: absolute; top: -8px; right: -8px; background: #ff6b6b; color: white; font-size: 9px; padding: 2px 6px; border-radius: 10px; font-weight: bold; display: none;">PRO</span>
309
+ </button>
310
+
311
+ <!-- Screenshot Recording Settings (shown when button is clicked) -->
312
+ <div id="screenshotRecordingSettings" style="display: none; background: #fff3e0; padding: 6px; border-radius: 3px; margin-top: 4px; border: 1px solid #FF9800;">
313
+ <div style="font-size: 9px; font-weight: bold; margin-bottom: 4px; color: #E65100;">📸 Screenshot Settings</div>
314
+ <label style="font-size: 9px; display: flex; align-items: center; cursor: pointer; margin-bottom: 4px;">
315
+ <input type="checkbox" id="hideMouseCursorCheckbox" checked style="margin-right: 4px;">
316
+ Hide mouse cursor
317
+ </label>
318
+ <div style="display: flex; align-items: center; gap: 4px;">
319
+ <label style="font-size: 9px;">Countdown:</label>
320
+ <select id="screenshotCountdown" style="font-size: 9px; padding: 2px;">
321
+ <option value="0">Instant (0s)</option>
322
+ <option value="3" selected>3 seconds</option>
323
+ <option value="5">5 seconds</option>
324
+ </select>
325
+ </div>
326
+ </div>
327
+
286
328
  <button class="record-btn" id="saveRestorePointBtn" style="background: #2196F3; display: none; margin-top: 4px; padding: 6px; font-size: 10px;">📍 Save Point</button>
287
329
  <div class="recording-status" id="workflowRecordingStatus" style="font-size: 10px;"></div>
288
-
330
+ <div class="recording-status" id="screenshotRecordingStatus" style="font-size: 10px;"></div>
331
+
289
332
  <div class="recordings-list" id="workflowRecordingsList" style="margin-top: 10px; display: none;">
290
- <h4 style="margin: 0 0 6px 0; font-size: 11px;">Saved Workflow Recordings:</h4>
333
+ <div style="display: flex; align-items: center; gap: 8px; margin-bottom: 6px;">
334
+ <h4 style="margin: 0; font-size: 11px;">Saved Recordings:</h4>
335
+ <button id="deleteAllWorkflowsBtn" class="delete-all-btn" style="display: none; font-size: 8px; padding: 2px 6px; background: #f44336; color: white; border: none; border-radius: 3px; cursor: pointer; flex-shrink: 0;" title="Delete all workflow recordings">Delete All</button>
336
+ </div>
337
+ <!-- Tab System for Workflow/Screenshot filtering -->
338
+ <div id="workflowTypeTabs" style="display: flex; margin-bottom: 6px; border-bottom: 1px solid #ddd;">
339
+ <button id="tabAllWorkflows" class="workflow-tab active" style="flex: 1; padding: 4px 8px; font-size: 10px; background: #9c27b0; color: white; border: none; border-radius: 4px 4px 0 0; cursor: pointer; margin-right: 2px;">All</button>
340
+ <button id="tabWorkflowOnly" class="workflow-tab" style="flex: 1; padding: 4px 8px; font-size: 10px; background: #e0e0e0; color: #333; border: none; border-radius: 4px 4px 0 0; cursor: pointer; margin-right: 2px;">Workflow</button>
341
+ <button id="tabScreenshotsOnly" class="workflow-tab" style="flex: 1; padding: 4px 8px; font-size: 10px; background: #e0e0e0; color: #333; border: none; border-radius: 4px 4px 0 0; cursor: pointer;">📸 Screenshots</button>
342
+ </div>
291
343
  <div id="workflowRecordingsContainer" style="max-height: 300px; overflow-y: auto; overflow-x: hidden; border: 1px solid #e0e0e0; border-radius: 4px; padding: 6px;"></div>
292
344
  </div>
293
345
 
@@ -346,11 +398,28 @@
346
398
  <input type="checkbox" id="frameFlash"> Flash
347
399
  </label>
348
400
  </div>
401
+
402
+ <!-- Mouse Tracking (PRO-only) -->
403
+ <div class="mouse-tracking-settings" id="mouseTrackingSection" style="margin-top: 8px; padding-top: 8px; border-top: 1px solid #e0e0e0;">
404
+ <label style="font-size: 9px; color: #666; display: flex; align-items: center; margin-bottom: 4px;">
405
+ <input type="checkbox" id="enableMouseTracking" style="margin-right: 4px;">
406
+ Track mouse movement
407
+ </label>
408
+ <select id="mouseSampleRate" disabled style="width: 100%; padding: 2px; font-size: 9px; margin-bottom: 4px;">
409
+ <option value="50">Fast (50ms) - Smooth</option>
410
+ <option value="100" selected>Normal (100ms)</option>
411
+ <option value="200">Slow (200ms)</option>
412
+ </select>
413
+ <p style="font-size: 8px; color: #999; margin: 0; line-height: 1.2;">
414
+ Records cursor position for video playback visualization.
415
+ </p>
416
+ </div>
349
417
  </div>
350
418
  </details>
351
419
  </div>
352
420
 
353
421
  <button class="record-btn" id="recordBtn" style="padding: 8px; font-size: 11px;">Start Recording</button>
422
+ <button class="record-btn" id="startVideoRecordingBtn" style="padding: 8px; font-size: 11px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); margin-top: 4px;">🎥 Start Video Recording</button>
354
423
  <button class="record-btn" id="manualSnapshotBtn" style="background: #4CAF50; display: none; margin-top: 4px; padding: 6px; font-size: 10px;">Manual Snapshot</button>
355
424
  <div class="recording-status" id="recordingStatus" style="font-size: 10px;"></div>
356
425
  <div class="countdown-display" id="countdownDisplay" style="display: none; text-align: center; margin-top: 10px; padding: 10px; background: #f0f0f0; border-radius: 4px;">
@@ -359,7 +428,16 @@
359
428
  </div>
360
429
 
361
430
  <div class="recordings-list" id="recordingsList" style="margin-top: 10px; display: none;">
362
- <h4 style="margin: 0 0 6px 0; font-size: 11px;">Saved Recordings:</h4>
431
+ <div style="display: flex; align-items: center; gap: 8px; margin-bottom: 6px;">
432
+ <h4 style="margin: 0; font-size: 11px;">Saved Recordings:</h4>
433
+ <button id="deleteAllScreenRecordingsBtn" class="delete-all-btn" style="display: none; font-size: 8px; padding: 2px 6px; background: #f44336; color: white; border: none; border-radius: 3px; cursor: pointer; flex-shrink: 0;" title="Delete all screen recordings">Delete All</button>
434
+ </div>
435
+ <!-- Recording type tabs -->
436
+ <div id="recordingTypeTabs" style="display: flex; margin-bottom: 6px; border-bottom: 1px solid #ddd;">
437
+ <button id="tabAllRecordings" class="recording-tab active" style="flex: 1; padding: 4px 8px; font-size: 10px; background: #2196F3; color: white; border: none; border-radius: 4px 4px 0 0; cursor: pointer; margin-right: 2px;">All</button>
438
+ <button id="tabScreenRecordings" class="recording-tab" style="flex: 1; padding: 4px 8px; font-size: 10px; background: #e0e0e0; color: #333; border: none; border-radius: 4px 4px 0 0; cursor: pointer; margin-right: 2px;">📸 Screen</button>
439
+ <button id="tabVideoRecordings" class="recording-tab" style="flex: 1; padding: 4px 8px; font-size: 10px; background: #e0e0e0; color: #333; border: none; border-radius: 4px 4px 0 0; cursor: pointer;">🎥 Video</button>
440
+ </div>
363
441
  <div id="recordingsContainer" style="max-height: 300px; overflow-y: auto; overflow-x: hidden; border: 1px solid #e0e0e0; border-radius: 4px; padding: 6px;"></div>
364
442
  </div>
365
443
  </div>