@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.
- package/CLAUDE.md +25 -0
- package/chrome-extension/activation-manager.js +18 -4
- package/chrome-extension/background.js +439 -53
- package/chrome-extension/browser-recording-manager.js +256 -0
- package/chrome-extension/content.js +167 -97
- package/chrome-extension/data-buffer.js +206 -17
- package/chrome-extension/frame-capture.js +52 -15
- package/chrome-extension/manifest.free.json +3 -4
- package/chrome-extension/popup.html +109 -5
- package/chrome-extension/popup.js +597 -159
- package/config/chromedebug-config.json +101 -0
- package/dist/chromedebug-extension-free.zip +0 -0
- package/package.json +1 -1
- package/scripts/package-pro-extension.js +1 -1
- package/scripts/webpack.config.free.cjs +4 -1
- package/scripts/webpack.config.pro.cjs +4 -1
- package/src/chrome-controller.js +7 -7
- package/src/cli.js +6 -0
- package/src/database.js +6 -2
- package/src/http-server.js +3 -2
- package/src/index.js +6 -1
- package/src/services/unified-session-manager.js +22 -2
- package/src/validation/schemas.js +36 -6
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
class DataBuffer {
|
|
5
5
|
constructor() {
|
|
6
6
|
this.dbName = 'ChromePilotDataBuffer';
|
|
7
|
-
this.version =
|
|
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.
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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.
|
|
4
|
-
"version": "2.6.
|
|
5
|
-
"description": "ChromeDebug MCP visual element selector [FREE Edition] [Build: 2025-10-
|
|
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>
|