@dynamicu/chromedebug-mcp 2.6.4 → 2.6.7

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.
@@ -4,7 +4,7 @@
4
4
  class DataBuffer {
5
5
  constructor() {
6
6
  this.dbName = 'ChromePilotDataBuffer';
7
- this.version = 1;
7
+ this.version = 2; // Incremented for new object stores
8
8
  this.db = null;
9
9
  this.maxBufferSize = 100 * 1024 * 1024; // 100MB default
10
10
  this.currentSize = 0;
@@ -31,30 +31,48 @@ class DataBuffer {
31
31
 
32
32
  request.onupgradeneeded = (event) => {
33
33
  const db = event.target.result;
34
-
34
+
35
35
  // Create object stores if they don't exist
36
36
  if (!db.objectStoreNames.contains('events')) {
37
- const eventsStore = db.createObjectStore('events', {
38
- keyPath: 'id',
39
- autoIncrement: true
37
+ const eventsStore = db.createObjectStore('events', {
38
+ keyPath: 'id',
39
+ autoIncrement: true
40
40
  });
41
41
  eventsStore.createIndex('timestamp', 'timestamp', { unique: false });
42
42
  eventsStore.createIndex('type', 'type', { unique: false });
43
43
  eventsStore.createIndex('recording_id', 'recording_id', { unique: false });
44
44
  }
45
-
45
+
46
46
  if (!db.objectStoreNames.contains('batches')) {
47
- const batchesStore = db.createObjectStore('batches', {
48
- keyPath: 'id',
49
- autoIncrement: true
47
+ const batchesStore = db.createObjectStore('batches', {
48
+ keyPath: 'id',
49
+ autoIncrement: true
50
50
  });
51
51
  batchesStore.createIndex('timestamp', 'timestamp', { unique: false });
52
52
  batchesStore.createIndex('status', 'status', { unique: false });
53
53
  }
54
-
54
+
55
55
  if (!db.objectStoreNames.contains('metadata')) {
56
56
  db.createObjectStore('metadata', { keyPath: 'key' });
57
57
  }
58
+
59
+ // New object stores for browser-only recording mode
60
+ if (!db.objectStoreNames.contains('browser_recordings')) {
61
+ const recordingsStore = db.createObjectStore('browser_recordings', {
62
+ keyPath: 'sessionId'
63
+ });
64
+ recordingsStore.createIndex('timestamp', 'startTime', { unique: false });
65
+ recordingsStore.createIndex('status', 'status', { unique: false });
66
+ }
67
+
68
+ if (!db.objectStoreNames.contains('browser_frames')) {
69
+ const framesStore = db.createObjectStore('browser_frames', {
70
+ keyPath: 'id',
71
+ autoIncrement: true
72
+ });
73
+ framesStore.createIndex('sessionId', 'sessionId', { unique: false });
74
+ framesStore.createIndex('frameIndex', 'frameIndex', { unique: false });
75
+ }
58
76
  };
59
77
  });
60
78
  }
@@ -390,11 +408,11 @@ class DataBuffer {
390
408
 
391
409
  async getStats() {
392
410
  if (!this.db) await this.init();
393
-
411
+
394
412
  const transaction = this.db.transaction(['events', 'batches'], 'readonly');
395
413
  const eventsStore = transaction.objectStore('events');
396
414
  const batchesStore = transaction.objectStore('batches');
397
-
415
+
398
416
  return new Promise((resolve) => {
399
417
  let stats = {
400
418
  eventCount: 0,
@@ -404,29 +422,200 @@ class DataBuffer {
404
422
  totalSize: this.currentSize,
405
423
  maxSize: this.maxBufferSize
406
424
  };
407
-
425
+
408
426
  eventsStore.count().onsuccess = (e) => {
409
427
  stats.eventCount = e.target.result;
410
428
  };
411
-
429
+
412
430
  batchesStore.count().onsuccess = (e) => {
413
431
  stats.batchCount = e.target.result;
414
432
  };
415
-
433
+
416
434
  const pendingIndex = batchesStore.index('status');
417
435
  pendingIndex.count(IDBKeyRange.only('pending')).onsuccess = (e) => {
418
436
  stats.pendingBatches = e.target.result;
419
437
  };
420
-
438
+
421
439
  pendingIndex.count(IDBKeyRange.only('uploaded')).onsuccess = (e) => {
422
440
  stats.uploadedBatches = e.target.result;
423
441
  };
424
-
442
+
425
443
  transaction.oncomplete = () => {
426
444
  resolve(stats);
427
445
  };
428
446
  });
429
447
  }
448
+
449
+ // Browser-only recording methods
450
+ async addBrowserRecording(recording) {
451
+ if (!this.db) await this.init();
452
+ const transaction = this.db.transaction(['browser_recordings'], 'readwrite');
453
+ const store = transaction.objectStore('browser_recordings');
454
+
455
+ return new Promise((resolve, reject) => {
456
+ const request = store.add(recording);
457
+ request.onsuccess = () => resolve(request.result);
458
+ request.onerror = () => reject(request.error);
459
+ });
460
+ }
461
+
462
+ async updateBrowserRecording(sessionId, updates) {
463
+ if (!this.db) await this.init();
464
+ const transaction = this.db.transaction(['browser_recordings'], 'readwrite');
465
+ const store = transaction.objectStore('browser_recordings');
466
+
467
+ return new Promise((resolve, reject) => {
468
+ const getRequest = store.get(sessionId);
469
+ getRequest.onsuccess = () => {
470
+ const recording = getRequest.result;
471
+ if (recording) {
472
+ Object.assign(recording, updates);
473
+ const putRequest = store.put(recording);
474
+ putRequest.onsuccess = () => resolve();
475
+ putRequest.onerror = () => reject(putRequest.error);
476
+ } else {
477
+ reject(new Error('Recording not found'));
478
+ }
479
+ };
480
+ getRequest.onerror = () => reject(getRequest.error);
481
+ });
482
+ }
483
+
484
+ async getBrowserRecording(sessionId) {
485
+ if (!this.db) await this.init();
486
+ const transaction = this.db.transaction(['browser_recordings'], 'readonly');
487
+ const store = transaction.objectStore('browser_recordings');
488
+
489
+ return new Promise((resolve, reject) => {
490
+ const request = store.get(sessionId);
491
+ request.onsuccess = () => resolve(request.result);
492
+ request.onerror = () => reject(request.error);
493
+ });
494
+ }
495
+
496
+ async getAllBrowserRecordings() {
497
+ if (!this.db) await this.init();
498
+ const transaction = this.db.transaction(['browser_recordings'], 'readonly');
499
+ const store = transaction.objectStore('browser_recordings');
500
+
501
+ return new Promise((resolve, reject) => {
502
+ const request = store.getAll();
503
+ request.onsuccess = () => resolve(request.result || []);
504
+ request.onerror = () => reject(request.error);
505
+ });
506
+ }
507
+
508
+ async deleteBrowserRecording(sessionId) {
509
+ if (!this.db) await this.init();
510
+
511
+ return new Promise((resolve, reject) => {
512
+ const transaction = this.db.transaction(['browser_recordings'], 'readwrite');
513
+ const store = transaction.objectStore('browser_recordings');
514
+
515
+ // Delete the recording
516
+ const deleteRequest = store.delete(sessionId);
517
+ deleteRequest.onerror = () => reject(deleteRequest.error);
518
+
519
+ // Wait for transaction to COMPLETE (commit to disk), not just request success
520
+ // This prevents race condition where add() happens before delete() commits
521
+ transaction.oncomplete = () => resolve();
522
+ transaction.onerror = () => reject(transaction.error);
523
+ });
524
+ }
525
+
526
+ async addBrowserFrame(frame) {
527
+ if (!this.db) await this.init();
528
+ const transaction = this.db.transaction(['browser_frames'], 'readwrite');
529
+ const store = transaction.objectStore('browser_frames');
530
+
531
+ return new Promise((resolve, reject) => {
532
+ const request = store.add(frame);
533
+ request.onsuccess = () => resolve(request.result);
534
+ request.onerror = () => reject(request.error);
535
+ });
536
+ }
537
+
538
+ async getBrowserFrames(sessionId) {
539
+ if (!this.db) await this.init();
540
+ const transaction = this.db.transaction(['browser_frames'], 'readonly');
541
+ const store = transaction.objectStore('browser_frames');
542
+ const index = store.index('sessionId');
543
+
544
+ return new Promise((resolve, reject) => {
545
+ const request = index.getAll(IDBKeyRange.only(sessionId));
546
+ request.onsuccess = () => resolve(request.result || []);
547
+ request.onerror = () => reject(request.error);
548
+ });
549
+ }
550
+
551
+ async getBrowserFrameCount(sessionId) {
552
+ if (!this.db) await this.init();
553
+ const transaction = this.db.transaction(['browser_frames'], 'readonly');
554
+ const store = transaction.objectStore('browser_frames');
555
+ const index = store.index('sessionId');
556
+
557
+ return new Promise((resolve, reject) => {
558
+ const request = index.count(IDBKeyRange.only(sessionId));
559
+ request.onsuccess = () => resolve(request.result);
560
+ request.onerror = () => reject(request.error);
561
+ });
562
+ }
563
+
564
+ async updateBrowserFrame(frameId, updates) {
565
+ if (!this.db) await this.init();
566
+
567
+ return new Promise((resolve, reject) => {
568
+ const transaction = this.db.transaction(['browser_frames'], 'readwrite');
569
+ const store = transaction.objectStore('browser_frames');
570
+
571
+ // Get the existing frame
572
+ const getRequest = store.get(frameId);
573
+ getRequest.onsuccess = () => {
574
+ const frame = getRequest.result;
575
+ if (!frame) {
576
+ reject(new Error(`Frame ${frameId} not found`));
577
+ return;
578
+ }
579
+
580
+ // Apply updates
581
+ Object.assign(frame, updates);
582
+
583
+ // Put the updated frame back
584
+ const putRequest = store.put(frame);
585
+ putRequest.onerror = () => reject(putRequest.error);
586
+ };
587
+ getRequest.onerror = () => reject(getRequest.error);
588
+
589
+ // Wait for transaction to complete
590
+ transaction.oncomplete = () => resolve();
591
+ transaction.onerror = () => reject(transaction.error);
592
+ });
593
+ }
594
+
595
+ async deleteBrowserFrames(sessionId) {
596
+ if (!this.db) await this.init();
597
+ const transaction = this.db.transaction(['browser_frames'], 'readwrite');
598
+ const store = transaction.objectStore('browser_frames');
599
+ const index = store.index('sessionId');
600
+
601
+ return new Promise((resolve, reject) => {
602
+ let deletedCount = 0;
603
+ const request = index.openCursor(IDBKeyRange.only(sessionId));
604
+
605
+ request.onsuccess = (event) => {
606
+ const cursor = event.target.result;
607
+ if (cursor) {
608
+ store.delete(cursor.primaryKey);
609
+ deletedCount++;
610
+ cursor.continue();
611
+ } else {
612
+ resolve(deletedCount);
613
+ }
614
+ };
615
+
616
+ request.onerror = () => reject(request.error);
617
+ });
618
+ }
430
619
  }
431
620
 
432
621
  // Export for use in background script
@@ -10,6 +10,7 @@ class FrameCapture {
10
10
  this.sessionId = null;
11
11
  this.startTime = null;
12
12
  this.totalFramesCaptured = 0;
13
+ this.mode = 'server'; // 'server' or 'browser-only'
13
14
  this.settings = {
14
15
  frameRate: 1, // seconds between frames
15
16
  imageQuality: 30, // JPEG quality (1-100)
@@ -23,6 +24,16 @@ class FrameCapture {
23
24
 
24
25
  }
25
26
 
27
+ setMode(mode) {
28
+ if (mode !== 'server' && mode !== 'browser-only') {
29
+ console.warn('[FrameCapture] Invalid mode:', mode, '- defaulting to server');
30
+ this.mode = 'server';
31
+ } else {
32
+ this.mode = mode;
33
+ console.log('[FrameCapture] Mode set to:', this.mode);
34
+ }
35
+ }
36
+
26
37
  async startCapture(streamId, tabId, sessionId, scheduledStartTime, settings = {}, ownerId = null) {
27
38
  if (this.isCapturing) return;
28
39
 
@@ -38,12 +49,22 @@ class FrameCapture {
38
49
  // Initialize lease tracking - null forces immediate first check
39
50
  this.lastLeaseCheck = null;
40
51
 
41
- // Update settings with passed values
42
- this.settings = {
43
- frameRate: settings.frameRate || 1,
44
- imageQuality: settings.imageQuality || 30,
45
- frameFlash: settings.frameFlash !== false
46
- };
52
+ // Update settings with passed values or apply mode-specific defaults
53
+ if (this.mode === 'browser-only') {
54
+ // Browser-only mode: ultra-low resolution and quality for storage efficiency
55
+ this.settings = {
56
+ frameRate: settings.frameRate || 1,
57
+ imageQuality: settings.imageQuality || 15, // Lower quality for browser storage
58
+ frameFlash: settings.frameFlash !== false
59
+ };
60
+ } else {
61
+ // Server mode: standard settings
62
+ this.settings = {
63
+ frameRate: settings.frameRate || 1,
64
+ imageQuality: settings.imageQuality || 30,
65
+ frameFlash: settings.frameFlash !== false
66
+ };
67
+ }
47
68
 
48
69
  console.log('Starting frame capture session:', this.sessionId);
49
70
  console.log('Frame capture scheduled to start at:', new Date(scheduledStartTime));
@@ -68,16 +89,27 @@ class FrameCapture {
68
89
  this.totalFramesCaptured++;
69
90
  console.log('Created synthetic first frame at scheduled start time');
70
91
 
71
- // Get the media stream with very low resolution optimized for Claude reading
72
- const media = await navigator.mediaDevices.getUserMedia({
73
- video: {
74
- mandatory: {
75
- chromeMediaSource: 'tab',
76
- chromeMediaSourceId: streamId,
77
- maxWidth: 640, // Reduced from 960 for smaller files
78
- maxHeight: 360 // Reduced from 540 for smaller files
92
+ // Get the media stream with resolution based on mode
93
+ const videoConstraints = this.mode === 'browser-only'
94
+ ? {
95
+ mandatory: {
96
+ chromeMediaSource: 'tab',
97
+ chromeMediaSourceId: streamId,
98
+ maxWidth: 480, // Ultra-low resolution for browser storage
99
+ maxHeight: 270 // Ultra-low resolution for browser storage
100
+ }
79
101
  }
80
- },
102
+ : {
103
+ mandatory: {
104
+ chromeMediaSource: 'tab',
105
+ chromeMediaSourceId: streamId,
106
+ maxWidth: 640, // Standard resolution for server storage
107
+ maxHeight: 360 // Standard resolution for server storage
108
+ }
109
+ };
110
+
111
+ const media = await navigator.mediaDevices.getUserMedia({
112
+ video: videoConstraints,
81
113
  audio: false
82
114
  });
83
115
 
@@ -382,6 +414,11 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
382
414
  if (message.target !== 'offscreen') return;
383
415
 
384
416
  if (message.type === 'start-frame-capture') {
417
+ // Set mode before starting capture
418
+ if (message.mode) {
419
+ frameCapture.setMode(message.mode);
420
+ }
421
+
385
422
  frameCapture.startCapture(message.streamId, message.tabId, message.sessionId, message.scheduledStartTime, message.settings, message.ownerId)
386
423
  .then(() => sendResponse({ success: true }))
387
424
  .catch(error => {
@@ -1,15 +1,14 @@
1
1
  {
2
2
  "manifest_version": 3,
3
- "name": "ChromeDebug MCP Assistant FREE v2.6.0",
4
- "version": "2.6.0",
5
- "description": "ChromeDebug MCP visual element selector [FREE Edition] [Build: 2025-10-24-v2.6.0]",
3
+ "name": "ChromeDebug MCP Assistant FREE v2.6.7",
4
+ "version": "2.6.7",
5
+ "description": "ChromeDebug MCP visual element selector [FREE Edition] [Build: 2025-11-10-v2.6.7]",
6
6
  "permissions": [
7
7
  "activeTab",
8
8
  "scripting",
9
9
  "tabs",
10
10
  "storage",
11
11
  "tabCapture",
12
- "desktopCapture",
13
12
  "notifications",
14
13
  "offscreen"
15
14
  ],
@@ -110,7 +110,29 @@
110
110
  50% { transform: scale(1.1); }
111
111
  100% { transform: scale(1); }
112
112
  }
113
-
113
+
114
+ @keyframes slideInRight {
115
+ from {
116
+ transform: translateX(100%);
117
+ opacity: 0;
118
+ }
119
+ to {
120
+ transform: translateX(0);
121
+ opacity: 1;
122
+ }
123
+ }
124
+
125
+ @keyframes slideOutRight {
126
+ from {
127
+ transform: translateX(0);
128
+ opacity: 1;
129
+ }
130
+ to {
131
+ transform: translateX(100%);
132
+ opacity: 0;
133
+ }
134
+ }
135
+
114
136
  .countdown-timer {
115
137
  animation: countdown 1s ease-in-out;
116
138
  }
@@ -183,7 +205,19 @@
183
205
  <span class="server-status disconnected" id="serverStatus"></span>
184
206
  <span id="statusText">Checking server...</span>
185
207
  </div>
186
-
208
+
209
+ <div id="pollingControls" style="margin: 10px 0; padding: 8px; background: #f5f5f5; border-radius: 4px; border: 1px solid #e0e0e0;">
210
+ <div style="display: flex; align-items: center; gap: 10px;">
211
+ <label style="display: flex; align-items: center; cursor: pointer; font-size: 13px; flex: 1;">
212
+ <input type="checkbox" id="pollContinuouslyCheckbox" checked style="margin-right: 6px; cursor: pointer;">
213
+ <span>Poll Continuously (every 3s)</span>
214
+ </label>
215
+ <button id="retryConnectionBtn" style="display: none; padding: 4px 12px; font-size: 12px; background: #2196F3; color: white; border: none; border-radius: 3px; cursor: pointer; white-space: nowrap;">
216
+ Retry Connection
217
+ </button>
218
+ </div>
219
+ </div>
220
+
187
221
  <div class="instructions">
188
222
  <strong>Chrome Debug Assistant:</strong>
189
223
  <p>Use the recording features below to capture screen recordings, workflow recordings, or debug data for your applications.</p>
@@ -221,7 +255,7 @@
221
255
  </div>
222
256
  </div>
223
257
 
224
- <div class="recording-section" style="margin-top: 20px; padding-top: 20px; border-top: 1px solid #e0e0e0;">
258
+ <div class="recording-section" id="workflow-recording-section" style="margin-top: 20px; padding-top: 20px; border-top: 1px solid #e0e0e0; position: relative;">
225
259
  <div style="display: flex; justify-content: space-between; align-items: center; margin: 0 0 10px 0;">
226
260
  <h3 style="margin: 0; font-size: 16px;">Workflow Recording</h3>
227
261
  <a href="#" id="viewWorkflowRecordings" style="font-size: 12px; color: #2196F3; text-decoration: none;">View Past Recordings</a>
@@ -289,7 +323,7 @@
289
323
  </div>
290
324
  </div>
291
325
 
292
- <div class="recording-section" style="margin-top: 20px; padding-top: 20px; border-top: 1px solid #e0e0e0;">
326
+ <div class="recording-section" id="screen-recording-section" style="margin-top: 20px; padding-top: 20px; border-top: 1px solid #e0e0e0; position: relative;">
293
327
  <div style="display: flex; justify-content: space-between; align-items: center; margin: 0 0 10px 0;">
294
328
  <h3 style="margin: 0; font-size: 16px;">Screen Recording</h3>
295
329
  <a href="#" id="viewScreenRecordings" style="font-size: 12px; color: #2196F3; text-decoration: none;">View Past Recordings</a>
@@ -395,7 +429,40 @@
395
429
  </div>
396
430
  -->
397
431
  </div>
398
-
432
+
433
+ <!-- Browser-Only Mode Banner (shown when no server detected) -->
434
+ <div id="browserOnlyBanner" style="display: none; margin-top: 20px; padding: 12px; background: #fff3cd; border: 1px solid #ffc107; border-radius: 4px;">
435
+ <div style="font-size: 13px; font-weight: 500; color: #856404; margin-bottom: 8px;">
436
+ ⚠️ Browser-Only Mode
437
+ </div>
438
+ <div style="font-size: 12px; color: #856404; margin-bottom: 10px;">
439
+ No Chrome Debug server detected. Recordings are stored locally in your browser and may be lost when clearing browsing data.
440
+ </div>
441
+ <div style="font-size: 11px; color: #856404; margin-bottom: 10px;">
442
+ <strong>Storage Available:</strong> <span id="quotaUsage">Checking...</span>
443
+ </div>
444
+ <div style="margin-top: 10px;">
445
+ <button id="exportRecordingBtn" style="padding: 6px 12px; background: #28a745; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 12px; width: 100%; margin-bottom: 5px; display: none;">
446
+ 💾 Export Recording
447
+ </button>
448
+ <a href="https://chrome-debug.com/upgrade" target="_blank" style="display: block; padding: 6px 12px; background: #2196F3; color: white; border: none; border-radius: 4px; text-align: center; font-size: 12px; text-decoration: none;">
449
+ 🚀 Get Full Version with Server
450
+ </a>
451
+ </div>
452
+ </div>
453
+
454
+ <!-- Storage Quota Meter (shown in browser-only mode) -->
455
+ <div id="quotaMeter" style="display: none; margin-top: 15px; padding: 10px; background: #f9f9f9; border-radius: 4px; border: 1px solid #e0e0e0;">
456
+ <div style="font-size: 13px; font-weight: 500; margin-bottom: 8px;">Browser Storage</div>
457
+ <div style="width: 100%; background: #e0e0e0; border-radius: 8px; height: 20px; overflow: hidden;">
458
+ <div id="quotaBar" style="height: 100%; background: #4CAF50; width: 0%; transition: width 0.3s, background 0.3s;"></div>
459
+ </div>
460
+ <div style="font-size: 11px; color: #666; margin-top: 5px; display: flex; justify-content: space-between;">
461
+ <span id="quotaText">0% used</span>
462
+ <span id="quotaAvailable">0 MB available</span>
463
+ </div>
464
+ </div>
465
+
399
466
  <style>
400
467
  .recording-item {
401
468
  background: #f5f5f5;
@@ -538,6 +605,43 @@
538
605
  border-color: #f44336 !important;
539
606
  color: #c62828;
540
607
  }
608
+
609
+ /* Loading overlay for recording sections */
610
+ .recording-section-overlay {
611
+ position: absolute;
612
+ top: 0;
613
+ left: 0;
614
+ right: 0;
615
+ bottom: 0;
616
+ background: rgba(255, 255, 255, 0.95);
617
+ display: flex;
618
+ flex-direction: column;
619
+ align-items: center;
620
+ justify-content: center;
621
+ z-index: 1000;
622
+ border-radius: 8px;
623
+ }
624
+
625
+ .recording-section-overlay .spinner {
626
+ width: 40px;
627
+ height: 40px;
628
+ border: 4px solid #f3f3f3;
629
+ border-top: 4px solid #3498db;
630
+ border-radius: 50%;
631
+ animation: spin 1s linear infinite;
632
+ }
633
+
634
+ @keyframes spin {
635
+ 0% { transform: rotate(0deg); }
636
+ 100% { transform: rotate(360deg); }
637
+ }
638
+
639
+ .recording-section-overlay .loading-text {
640
+ margin-top: 16px;
641
+ color: #333;
642
+ font-size: 14px;
643
+ font-weight: 500;
644
+ }
541
645
  </style>
542
646
 
543
647
  <script src="disable-react-devtools.js"></script>