@dynamicu/chromedebug-mcp 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +344 -0
- package/LICENSE +21 -0
- package/README.md +250 -0
- package/chrome-extension/README.md +41 -0
- package/chrome-extension/background.js +3917 -0
- package/chrome-extension/chrome-session-manager.js +706 -0
- package/chrome-extension/content.css +181 -0
- package/chrome-extension/content.js +3022 -0
- package/chrome-extension/data-buffer.js +435 -0
- package/chrome-extension/dom-tracker.js +411 -0
- package/chrome-extension/extension-config.js +78 -0
- package/chrome-extension/firebase-client.js +278 -0
- package/chrome-extension/firebase-config.js +32 -0
- package/chrome-extension/firebase-config.module.js +22 -0
- package/chrome-extension/firebase-config.module.template.js +27 -0
- package/chrome-extension/firebase-config.template.js +36 -0
- package/chrome-extension/frame-capture.js +407 -0
- package/chrome-extension/icon128.png +1 -0
- package/chrome-extension/icon16.png +1 -0
- package/chrome-extension/icon48.png +1 -0
- package/chrome-extension/license-helper.js +181 -0
- package/chrome-extension/logger.js +23 -0
- package/chrome-extension/manifest.json +73 -0
- package/chrome-extension/network-tracker.js +510 -0
- package/chrome-extension/offscreen.html +10 -0
- package/chrome-extension/options.html +203 -0
- package/chrome-extension/options.js +282 -0
- package/chrome-extension/pako.min.js +2 -0
- package/chrome-extension/performance-monitor.js +533 -0
- package/chrome-extension/pii-redactor.js +405 -0
- package/chrome-extension/popup.html +532 -0
- package/chrome-extension/popup.js +2446 -0
- package/chrome-extension/upload-manager.js +323 -0
- package/chrome-extension/web-vitals.iife.js +1 -0
- package/config/api-keys.json +11 -0
- package/config/chrome-pilot-config.json +45 -0
- package/package.json +126 -0
- package/scripts/cleanup-processes.js +109 -0
- package/scripts/config-manager.js +280 -0
- package/scripts/generate-extension-config.js +53 -0
- package/scripts/setup-security.js +64 -0
- package/src/capture/architecture.js +426 -0
- package/src/capture/error-handling-tests.md +38 -0
- package/src/capture/error-handling-types.ts +360 -0
- package/src/capture/index.js +508 -0
- package/src/capture/interfaces.js +625 -0
- package/src/capture/memory-manager.js +713 -0
- package/src/capture/types.js +342 -0
- package/src/chrome-controller.js +2658 -0
- package/src/cli.js +19 -0
- package/src/config-loader.js +303 -0
- package/src/database.js +2178 -0
- package/src/firebase-license-manager.js +462 -0
- package/src/firebase-privacy-guard.js +397 -0
- package/src/http-server.js +1516 -0
- package/src/index-direct.js +157 -0
- package/src/index-modular.js +219 -0
- package/src/index-monolithic-backup.js +2230 -0
- package/src/index.js +305 -0
- package/src/legacy/chrome-controller-old.js +1406 -0
- package/src/legacy/index-express.js +625 -0
- package/src/legacy/index-old.js +977 -0
- package/src/legacy/routes.js +260 -0
- package/src/legacy/shared-storage.js +101 -0
- package/src/logger.js +10 -0
- package/src/mcp/handlers/chrome-tool-handler.js +306 -0
- package/src/mcp/handlers/element-tool-handler.js +51 -0
- package/src/mcp/handlers/frame-tool-handler.js +957 -0
- package/src/mcp/handlers/request-handler.js +104 -0
- package/src/mcp/handlers/workflow-tool-handler.js +636 -0
- package/src/mcp/server.js +68 -0
- package/src/mcp/tools/index.js +701 -0
- package/src/middleware/auth.js +371 -0
- package/src/middleware/security.js +267 -0
- package/src/port-discovery.js +258 -0
- package/src/routes/admin.js +182 -0
- package/src/services/browser-daemon.js +494 -0
- package/src/services/chrome-service.js +375 -0
- package/src/services/failover-manager.js +412 -0
- package/src/services/git-safety-service.js +675 -0
- package/src/services/heartbeat-manager.js +200 -0
- package/src/services/http-client.js +195 -0
- package/src/services/process-manager.js +318 -0
- package/src/services/process-tracker.js +574 -0
- package/src/services/profile-manager.js +449 -0
- package/src/services/project-manager.js +415 -0
- package/src/services/session-manager.js +497 -0
- package/src/services/session-registry.js +491 -0
- package/src/services/unified-session-manager.js +678 -0
- package/src/shared-storage-old.js +267 -0
- package/src/standalone-server.js +53 -0
- package/src/utils/extension-path.js +145 -0
- package/src/utils.js +187 -0
- package/src/validation/log-transformer.js +125 -0
- package/src/validation/schemas.js +391 -0
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
// Frame capture implementation for Chrome Debug
|
|
2
|
+
// This captures frames at 1fps for Claude compatibility
|
|
3
|
+
|
|
4
|
+
class FrameCapture {
|
|
5
|
+
constructor() {
|
|
6
|
+
this.isCapturing = false;
|
|
7
|
+
this.frames = [];
|
|
8
|
+
this.logs = [];
|
|
9
|
+
this.captureInterval = null;
|
|
10
|
+
this.sessionId = null;
|
|
11
|
+
this.startTime = null;
|
|
12
|
+
this.totalFramesCaptured = 0;
|
|
13
|
+
this.settings = {
|
|
14
|
+
frameRate: 1, // seconds between frames
|
|
15
|
+
imageQuality: 30, // JPEG quality (1-100)
|
|
16
|
+
frameFlash: true // flash indicator
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
// Lease checking properties
|
|
20
|
+
this.lastLeaseCheck = null;
|
|
21
|
+
this.leaseCheckInterval = 3000; // Check every 3 seconds (before 5s expiry)
|
|
22
|
+
this.ownerId = null; // Will be set when session starts
|
|
23
|
+
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async startCapture(streamId, tabId, sessionId, scheduledStartTime, settings = {}, ownerId = null) {
|
|
27
|
+
if (this.isCapturing) return;
|
|
28
|
+
|
|
29
|
+
// Use the passed session info for consistency
|
|
30
|
+
this.sessionId = sessionId || `frame_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
31
|
+
this.ownerId = ownerId || `owner_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
32
|
+
this.scheduledStartTime = scheduledStartTime || Date.now();
|
|
33
|
+
this.frames = [];
|
|
34
|
+
this.totalFramesCaptured = 0;
|
|
35
|
+
this.isCapturing = true;
|
|
36
|
+
this.tabId = tabId;
|
|
37
|
+
|
|
38
|
+
// Initialize lease tracking - null forces immediate first check
|
|
39
|
+
this.lastLeaseCheck = null;
|
|
40
|
+
|
|
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
|
+
};
|
|
47
|
+
|
|
48
|
+
console.log('Starting frame capture session:', this.sessionId);
|
|
49
|
+
console.log('Frame capture scheduled to start at:', new Date(scheduledStartTime));
|
|
50
|
+
console.log('Frame capture settings:', this.settings);
|
|
51
|
+
|
|
52
|
+
// Trigger visual feedback system start
|
|
53
|
+
chrome.runtime.sendMessage({
|
|
54
|
+
type: 'start-screen-capture-tracking',
|
|
55
|
+
sessionId: this.sessionId
|
|
56
|
+
}).catch(() => {
|
|
57
|
+
console.log('[FrameCapture] Could not start visual feedback tracking');
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// Create a synthetic first frame at scheduled start time to catch early logs
|
|
61
|
+
const syntheticFrame = {
|
|
62
|
+
timestamp: 0,
|
|
63
|
+
absoluteTimestamp: this.scheduledStartTime,
|
|
64
|
+
imageData: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==', // 1x1 transparent pixel
|
|
65
|
+
logs: []
|
|
66
|
+
};
|
|
67
|
+
this.frames.push(syntheticFrame);
|
|
68
|
+
this.totalFramesCaptured++;
|
|
69
|
+
console.log('Created synthetic first frame at scheduled start time');
|
|
70
|
+
|
|
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
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
audio: false
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// Create video element to capture frames from
|
|
85
|
+
const video = document.createElement('video');
|
|
86
|
+
video.srcObject = media;
|
|
87
|
+
video.play();
|
|
88
|
+
|
|
89
|
+
// Create canvas for frame extraction
|
|
90
|
+
const canvas = document.createElement('canvas');
|
|
91
|
+
const ctx = canvas.getContext('2d');
|
|
92
|
+
|
|
93
|
+
// Wait for video to be ready
|
|
94
|
+
await new Promise(resolve => {
|
|
95
|
+
video.onloadedmetadata = () => {
|
|
96
|
+
canvas.width = video.videoWidth;
|
|
97
|
+
canvas.height = video.videoHeight;
|
|
98
|
+
resolve();
|
|
99
|
+
};
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
// Monitor stream health - stop if stream ends unexpectedly
|
|
103
|
+
this.mediaStream = media;
|
|
104
|
+
media.getTracks().forEach(track => {
|
|
105
|
+
track.onended = () => {
|
|
106
|
+
console.error('[FrameCapture] Media track ended unexpectedly');
|
|
107
|
+
this.stopCapture();
|
|
108
|
+
};
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
// Wait until scheduled start time before beginning frame capture
|
|
112
|
+
const waitTime = Math.max(0, this.scheduledStartTime - Date.now());
|
|
113
|
+
console.log(`Frame capture will wait ${waitTime}ms until scheduled start time`);
|
|
114
|
+
|
|
115
|
+
setTimeout(() => {
|
|
116
|
+
console.log('Frame capture starting at scheduled time');
|
|
117
|
+
|
|
118
|
+
// Capture frames at intervals (e.g., 1 frame per second)
|
|
119
|
+
this.captureInterval = setInterval(async () => {
|
|
120
|
+
if (!this.isCapturing) return;
|
|
121
|
+
|
|
122
|
+
// Check lease validity every 5 seconds
|
|
123
|
+
if (this.lastLeaseCheck && (Date.now() - this.lastLeaseCheck) > this.leaseCheckInterval) {
|
|
124
|
+
const leaseValid = await this.checkLease();
|
|
125
|
+
if (!leaseValid) {
|
|
126
|
+
console.log('[FrameCapture] Lease expired, stopping capture');
|
|
127
|
+
this.stopCapture();
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
this.lastLeaseCheck = Date.now();
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Draw current frame to canvas
|
|
134
|
+
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
|
|
135
|
+
|
|
136
|
+
// Convert to JPEG with compression
|
|
137
|
+
canvas.toBlob(blob => {
|
|
138
|
+
if (!blob) return;
|
|
139
|
+
|
|
140
|
+
const reader = new FileReader();
|
|
141
|
+
reader.onloadend = () => {
|
|
142
|
+
const captureTimestamp = Date.now();
|
|
143
|
+
const frameData = {
|
|
144
|
+
timestamp: captureTimestamp - this.scheduledStartTime, // Relative timestamp from scheduled start
|
|
145
|
+
absoluteTimestamp: captureTimestamp, // Absolute timestamp for log association
|
|
146
|
+
imageData: reader.result,
|
|
147
|
+
logs: [], // Will be populated during post-processing
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
this.frames.push(frameData);
|
|
151
|
+
this.totalFramesCaptured++;
|
|
152
|
+
|
|
153
|
+
console.log(`Captured frame ${this.totalFramesCaptured}, size: ${blob.size} bytes`);
|
|
154
|
+
|
|
155
|
+
// Trigger visual feedback for frame captured
|
|
156
|
+
chrome.runtime.sendMessage({
|
|
157
|
+
type: 'show-screen-capture-flash'
|
|
158
|
+
}).catch(() => {
|
|
159
|
+
console.log('[FrameCapture] Could not trigger frame capture flash');
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
// Send frame immediately to keep memory usage low
|
|
163
|
+
if (this.frames.length % 5 === 0) { // Every 5 frames
|
|
164
|
+
this.sendFrameBatch().catch(error => {
|
|
165
|
+
console.error('[FrameCapture] Error sending frame batch:', error);
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
reader.readAsDataURL(blob);
|
|
170
|
+
}, 'image/jpeg', this.settings.imageQuality / 100); // Use configurable quality
|
|
171
|
+
|
|
172
|
+
// Flash indicator if enabled
|
|
173
|
+
if (this.settings.frameFlash) {
|
|
174
|
+
this.flashCaptureIndicator();
|
|
175
|
+
}
|
|
176
|
+
}, this.settings.frameRate * 1000); // Use configurable frame rate
|
|
177
|
+
|
|
178
|
+
console.log(`Frame capture interval set to ${this.settings.frameRate * 1000}ms (${this.settings.frameRate}s)`);
|
|
179
|
+
console.log(`Image quality set to ${this.settings.imageQuality}%`);
|
|
180
|
+
|
|
181
|
+
// Store the media stream for cleanup
|
|
182
|
+
this.mediaStream = media;
|
|
183
|
+
this.videoElement = video;
|
|
184
|
+
}, waitTime); // Close the setTimeout
|
|
185
|
+
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Check lease validity with background script
|
|
189
|
+
async checkLease() {
|
|
190
|
+
console.log('[FrameCapture] Lease check starting - SessionId:', this.sessionId);
|
|
191
|
+
|
|
192
|
+
try {
|
|
193
|
+
const response = await Promise.race([
|
|
194
|
+
chrome.runtime.sendMessage({
|
|
195
|
+
action: 'renewLease',
|
|
196
|
+
sessionId: this.sessionId,
|
|
197
|
+
ownerId: this.ownerId,
|
|
198
|
+
requestTime: Date.now(),
|
|
199
|
+
renewalDuration: this.leaseCheckInterval
|
|
200
|
+
}),
|
|
201
|
+
new Promise((_, reject) =>
|
|
202
|
+
setTimeout(() => reject(new Error('Lease check timeout')), 2000)
|
|
203
|
+
)
|
|
204
|
+
]);
|
|
205
|
+
|
|
206
|
+
console.log('[FrameCapture] Lease response - Success:', response?.success);
|
|
207
|
+
return response?.success === true;
|
|
208
|
+
|
|
209
|
+
} catch (error) {
|
|
210
|
+
// On communication errors, log but continue (don't stop recording)
|
|
211
|
+
// The next lease check will try again
|
|
212
|
+
console.warn('[FrameCapture] Lease check failed (will retry):', error.message);
|
|
213
|
+
return true; // CHANGED: Be permissive on errors, next check will enforce
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
stopCapture() {
|
|
218
|
+
if (!this.isCapturing) return;
|
|
219
|
+
|
|
220
|
+
this.isCapturing = false;
|
|
221
|
+
|
|
222
|
+
// Stop interval
|
|
223
|
+
if (this.captureInterval) {
|
|
224
|
+
clearInterval(this.captureInterval);
|
|
225
|
+
this.captureInterval = null;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Send remaining frames
|
|
229
|
+
if (this.frames.length > 0) {
|
|
230
|
+
this.sendFrameBatch().catch(error => {
|
|
231
|
+
console.error('[FrameCapture] Error sending final frame batch:', error);
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Clean up
|
|
236
|
+
if (this.mediaStream) {
|
|
237
|
+
this.mediaStream.getTracks().forEach(track => track.stop());
|
|
238
|
+
}
|
|
239
|
+
if (this.videoElement) {
|
|
240
|
+
this.videoElement.remove();
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Stop visual feedback system
|
|
244
|
+
chrome.runtime.sendMessage({
|
|
245
|
+
type: 'stop-screen-capture-tracking'
|
|
246
|
+
}).catch(() => {
|
|
247
|
+
console.log('[FrameCapture] Could not stop visual feedback tracking');
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
// Notify completion
|
|
251
|
+
chrome.runtime.sendMessage({
|
|
252
|
+
type: 'frame-capture-complete',
|
|
253
|
+
target: 'background',
|
|
254
|
+
data: {
|
|
255
|
+
sessionId: this.sessionId,
|
|
256
|
+
totalFrames: this.totalFramesCaptured,
|
|
257
|
+
duration: Date.now() - this.scheduledStartTime
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
console.log('Frame capture stopped');
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Flash indicator method to show when frames are captured
|
|
265
|
+
flashCaptureIndicator() {
|
|
266
|
+
// Send message through background script to content script to show flash
|
|
267
|
+
chrome.runtime.sendMessage({
|
|
268
|
+
type: 'show-frame-flash',
|
|
269
|
+
target: 'background',
|
|
270
|
+
tabId: this.tabId
|
|
271
|
+
}).catch(() => {
|
|
272
|
+
// Background script might not be available, that's ok
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Manual snapshot method for immediate frame capture
|
|
277
|
+
async takeManualSnapshot() {
|
|
278
|
+
if (!this.isCapturing) {
|
|
279
|
+
throw new Error('Cannot take manual snapshot - recording is not active');
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Check if we have the necessary elements
|
|
283
|
+
if (!this.videoElement || !this.videoElement.videoWidth) {
|
|
284
|
+
throw new Error('Video element not ready for manual snapshot');
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
console.log('Taking manual snapshot...');
|
|
288
|
+
|
|
289
|
+
// Create a temporary canvas for the manual snapshot
|
|
290
|
+
const canvas = document.createElement('canvas');
|
|
291
|
+
const ctx = canvas.getContext('2d');
|
|
292
|
+
canvas.width = this.videoElement.videoWidth;
|
|
293
|
+
canvas.height = this.videoElement.videoHeight;
|
|
294
|
+
|
|
295
|
+
// Draw current frame to canvas
|
|
296
|
+
ctx.drawImage(this.videoElement, 0, 0, canvas.width, canvas.height);
|
|
297
|
+
|
|
298
|
+
// Convert to JPEG with the same quality settings
|
|
299
|
+
return new Promise((resolve, reject) => {
|
|
300
|
+
canvas.toBlob(blob => {
|
|
301
|
+
if (!blob) {
|
|
302
|
+
reject(new Error('Failed to create manual snapshot blob'));
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
const reader = new FileReader();
|
|
307
|
+
reader.onloadend = () => {
|
|
308
|
+
const captureTimestamp = Date.now();
|
|
309
|
+
const frameData = {
|
|
310
|
+
timestamp: captureTimestamp - this.startTime,
|
|
311
|
+
absoluteTimestamp: captureTimestamp,
|
|
312
|
+
imageData: reader.result,
|
|
313
|
+
logs: [],
|
|
314
|
+
isManual: true // Flag to identify manual snapshots
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
this.frames.push(frameData);
|
|
318
|
+
this.totalFramesCaptured++;
|
|
319
|
+
|
|
320
|
+
console.log(`Manual snapshot captured: frame ${this.totalFramesCaptured}, size: ${blob.size} bytes`);
|
|
321
|
+
|
|
322
|
+
// Trigger visual feedback for manual snapshot
|
|
323
|
+
chrome.runtime.sendMessage({
|
|
324
|
+
type: 'show-screen-capture-flash'
|
|
325
|
+
}).catch(() => {
|
|
326
|
+
console.log('[FrameCapture] Could not trigger manual snapshot flash');
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
// Flash indicator for manual snapshot
|
|
330
|
+
if (this.settings.frameFlash) {
|
|
331
|
+
this.flashCaptureIndicator();
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// Send frame immediately
|
|
335
|
+
this.sendFrameBatch().catch(error => {
|
|
336
|
+
console.error('[FrameCapture] Error sending manual snapshot frame batch:', error);
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
resolve();
|
|
340
|
+
};
|
|
341
|
+
|
|
342
|
+
reader.onerror = () => {
|
|
343
|
+
reject(new Error('Failed to read manual snapshot blob'));
|
|
344
|
+
};
|
|
345
|
+
|
|
346
|
+
reader.readAsDataURL(blob);
|
|
347
|
+
}, 'image/jpeg', this.settings.imageQuality / 100);
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// Logs are no longer added in real-time
|
|
352
|
+
// They will be associated during post-processing
|
|
353
|
+
|
|
354
|
+
async sendFrameBatch() {
|
|
355
|
+
// Non-blocking lease check - warn but don't stop recording
|
|
356
|
+
// The interval check (line 113) provides the actual enforcement
|
|
357
|
+
this.checkLease().then(leaseValid => {
|
|
358
|
+
if (!leaseValid) {
|
|
359
|
+
console.warn('[FrameCapture] WARNING: Lease check failed during batch send, but continuing. Interval check will enforce if needed.');
|
|
360
|
+
}
|
|
361
|
+
}).catch(error => {
|
|
362
|
+
console.warn('[FrameCapture] Lease check error during batch send:', error.message);
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
const batch = this.frames.splice(0, 5); // Send up to 5 frames at a time
|
|
366
|
+
console.log(`[FrameCapture] Sending batch of ${batch.length} frames, ${this.frames.length} remaining`);
|
|
367
|
+
|
|
368
|
+
chrome.runtime.sendMessage({
|
|
369
|
+
type: 'frame-batch-ready',
|
|
370
|
+
target: 'background',
|
|
371
|
+
data: {
|
|
372
|
+
sessionId: this.sessionId,
|
|
373
|
+
frames: batch
|
|
374
|
+
}
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// Listen for messages
|
|
381
|
+
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
|
382
|
+
if (message.target !== 'offscreen') return;
|
|
383
|
+
|
|
384
|
+
if (message.type === 'start-frame-capture') {
|
|
385
|
+
frameCapture.startCapture(message.streamId, message.tabId, message.sessionId, message.scheduledStartTime, message.settings, message.ownerId)
|
|
386
|
+
.then(() => sendResponse({ success: true }))
|
|
387
|
+
.catch(error => {
|
|
388
|
+
console.error('Error starting frame capture:', error);
|
|
389
|
+
sendResponse({ success: false, error: error.message });
|
|
390
|
+
});
|
|
391
|
+
return true; // Keep channel open
|
|
392
|
+
} else if (message.type === 'stop-frame-capture') {
|
|
393
|
+
frameCapture.stopCapture();
|
|
394
|
+
sendResponse({ success: true });
|
|
395
|
+
} else if (message.type === 'manual-snapshot') {
|
|
396
|
+
frameCapture.takeManualSnapshot()
|
|
397
|
+
.then(() => sendResponse({ success: true }))
|
|
398
|
+
.catch(error => {
|
|
399
|
+
console.error('Error taking manual snapshot:', error);
|
|
400
|
+
sendResponse({ success: false, error: error.message });
|
|
401
|
+
});
|
|
402
|
+
return true; // Keep channel open
|
|
403
|
+
}
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
// Create instance
|
|
407
|
+
const frameCapture = new FrameCapture();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAAbwAAAG8B8aLcQwAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAADFSURBVHic7cExAQAAAMKg9U9tCj+gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4G8BDwABTdLJpgAAAABJRU5ErkJggg==
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAAbwAAAG8B8aLcQwAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAADFSURBVDiNY2AYBaNgFAyEABMDFQELuo4FDAz/GRj+/2dg+M/AwMDAwMDAwMjAyMDIwMjAyMDw/z8DAwMDw38GBgYGhv8M/xkY/jMw/P/PwPCfgYHhPwPDfwaG/wwM/xkY/jMw/Gdg+M/A8J+B4T8Dw38Ghv8MDP8ZGP4zMPxnYPjPwPCfgeE/A8N/Bob/DAz/GRj+MzD8Z2D4z8Dwn4HhPwPDfwaG/wwM/xkY/jMw/Gdg+M/A8J+B4T8Dw38GBgYAKpQVJXjaVpAAAAAASUVORK5CYII=
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAAbwAAAG8B8aLcQwAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAADFSURBVGiB7ZAxDoAgEAT3xU/4Cn/hK/yEXyCxtBALC5M9yE7CJBttmF1AQAghvg0FgAJAAaAAUAAoABQACgAFgAJAAaAAUAAoABQACgAFgAJAAaAAUAAoABQACgAFgAJAAaAAUAAoABQACgAFgAJAAaAAUAAoABQACgAFgAJAAaAAUAAoABQACgAFgAJAAaAAUAAoABQACgAFgAJAAaAAUAAoABQACgAFgAJAAaAAUAAoABQACgAFgAJAAaAAUAAoABQACgD1BzMUBT6f7DfhAAAAAElFTkSuQmCC
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* License Helper for Background Service Worker
|
|
3
|
+
* Non-module version compatible with importScripts()
|
|
4
|
+
*
|
|
5
|
+
* Privacy: Only transmits userId (anonymous UUID) and usage count
|
|
6
|
+
* NO recording data, NO PII, NO browsing history
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
// License validation and usage tracking functions
|
|
10
|
+
const LicenseHelper = {
|
|
11
|
+
cacheKey: 'chromedebug_license_cache',
|
|
12
|
+
instanceIdKey: 'chromedebug_instance_id',
|
|
13
|
+
userIdKey: 'chromedebug_user_id',
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Check if user can start a recording (license + usage limit check)
|
|
17
|
+
* @returns {Promise<{allowed: boolean, message?: string, tier?: string}>}
|
|
18
|
+
*/
|
|
19
|
+
async checkLicenseBeforeRecording() {
|
|
20
|
+
try {
|
|
21
|
+
// Get userId
|
|
22
|
+
const stored = await chrome.storage.local.get(this.userIdKey);
|
|
23
|
+
let userId = stored[this.userIdKey];
|
|
24
|
+
|
|
25
|
+
if (!userId) {
|
|
26
|
+
// First time user - allow recording, create userId
|
|
27
|
+
userId = crypto.randomUUID();
|
|
28
|
+
await chrome.storage.local.set({[this.userIdKey]: userId});
|
|
29
|
+
console.log('[License] New user created:', userId);
|
|
30
|
+
return {allowed: true, firstTime: true, userId: userId};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Check cached license status
|
|
34
|
+
const licenseStatus = await this.getCachedLicenseStatus();
|
|
35
|
+
|
|
36
|
+
if (licenseStatus.valid && licenseStatus.tier === 'pro') {
|
|
37
|
+
console.log('[License] Pro user - allowing recording');
|
|
38
|
+
return {allowed: true, tier: 'pro', userId: userId};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Free tier - check usage limit
|
|
42
|
+
const usage = await this.checkUsageLimit(userId);
|
|
43
|
+
console.log('[License] Usage check:', usage);
|
|
44
|
+
|
|
45
|
+
// Handle both API response format (withinLimit) and offline format (allowed)
|
|
46
|
+
const withinLimit = usage.withinLimit ?? usage.allowed ?? true;
|
|
47
|
+
const currentCount = usage.currentUsage ?? usage.count ?? 0;
|
|
48
|
+
const dailyLimit = usage.dailyLimit ?? usage.limit ?? 50;
|
|
49
|
+
|
|
50
|
+
if (!withinLimit) {
|
|
51
|
+
return {
|
|
52
|
+
allowed: false,
|
|
53
|
+
message: `Daily limit reached (${currentCount}/${dailyLimit}). Upgrade to Pro for unlimited recordings.`,
|
|
54
|
+
userId: userId
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return {allowed: true, tier: 'free', userId: userId};
|
|
59
|
+
} catch (error) {
|
|
60
|
+
console.error('[License] Error checking license:', error);
|
|
61
|
+
// On error, allow recording (fail-open for user experience)
|
|
62
|
+
return {allowed: true, error: error.message};
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Increment usage count AFTER recording completes
|
|
68
|
+
* @param {string} userId - User ID
|
|
69
|
+
* @returns {Promise<{success: boolean}>}
|
|
70
|
+
*/
|
|
71
|
+
async trackUsageAfterRecording(userId) {
|
|
72
|
+
try {
|
|
73
|
+
if (!userId) {
|
|
74
|
+
const stored = await chrome.storage.local.get(this.userIdKey);
|
|
75
|
+
userId = stored[this.userIdKey];
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (!userId) {
|
|
79
|
+
console.warn('[License] No userId found for usage tracking');
|
|
80
|
+
return {success: false};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Check if pro user (don't increment for pro)
|
|
84
|
+
const licenseStatus = await this.getCachedLicenseStatus();
|
|
85
|
+
if (licenseStatus.valid && licenseStatus.tier === 'pro') {
|
|
86
|
+
console.log('[License] Pro user - skipping usage increment');
|
|
87
|
+
return {success: true, tier: 'pro', skipped: true};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Increment usage for free tier
|
|
91
|
+
await this.incrementUsage(userId);
|
|
92
|
+
console.log('[License] Usage incremented for user:', userId);
|
|
93
|
+
|
|
94
|
+
return {success: true};
|
|
95
|
+
} catch (error) {
|
|
96
|
+
console.error('[License] Error tracking usage:', error);
|
|
97
|
+
return {success: false, error: error.message};
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Check usage limit via Cloud Function
|
|
103
|
+
* @param {string} userId - User ID
|
|
104
|
+
* @returns {Promise<{allowed: boolean, count?: number, limit?: number}>}
|
|
105
|
+
*/
|
|
106
|
+
async checkUsageLimit(userId) {
|
|
107
|
+
try {
|
|
108
|
+
const url = `${FUNCTIONS_URL}/checkUsageLimit`;
|
|
109
|
+
console.log('[License Helper] FUNCTIONS_URL:', FUNCTIONS_URL);
|
|
110
|
+
console.log('[License Helper] Full URL:', url);
|
|
111
|
+
const response = await fetch(url, {
|
|
112
|
+
method: 'POST',
|
|
113
|
+
headers: {'Content-Type': 'application/json'},
|
|
114
|
+
body: JSON.stringify({userId})
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
if (!response.ok) {
|
|
118
|
+
throw new Error(`HTTP ${response.status}`);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return await response.json();
|
|
122
|
+
} catch (error) {
|
|
123
|
+
console.error('[License] Failed to check usage limit:', error);
|
|
124
|
+
// Offline: allow limited usage (fail-open)
|
|
125
|
+
return {allowed: true, offline: true, limit: 5};
|
|
126
|
+
}
|
|
127
|
+
},
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Increment usage via Cloud Function
|
|
131
|
+
* @param {string} userId - User ID
|
|
132
|
+
* @returns {Promise<{success: boolean}>}
|
|
133
|
+
*/
|
|
134
|
+
async incrementUsage(userId) {
|
|
135
|
+
try {
|
|
136
|
+
const response = await fetch(`${FUNCTIONS_URL}/incrementUsage`, {
|
|
137
|
+
method: 'POST',
|
|
138
|
+
headers: {'Content-Type': 'application/json'},
|
|
139
|
+
body: JSON.stringify({userId})
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
if (!response.ok) {
|
|
143
|
+
throw new Error(`HTTP ${response.status}`);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return await response.json();
|
|
147
|
+
} catch (error) {
|
|
148
|
+
console.error('[License] Failed to increment usage:', error);
|
|
149
|
+
return {success: false, offline: true};
|
|
150
|
+
}
|
|
151
|
+
},
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Get cached license status
|
|
155
|
+
* @returns {Promise<{valid: boolean, tier?: string}>}
|
|
156
|
+
*/
|
|
157
|
+
async getCachedLicenseStatus() {
|
|
158
|
+
const result = await chrome.storage.local.get(this.cacheKey);
|
|
159
|
+
const cached = result[this.cacheKey];
|
|
160
|
+
|
|
161
|
+
if (!cached) return {valid: false};
|
|
162
|
+
|
|
163
|
+
// Check if cache is stale (>24 hours)
|
|
164
|
+
const cacheAge = Date.now() - cached.cachedAt;
|
|
165
|
+
const cacheExpired = cacheAge > (24 * 60 * 60 * 1000);
|
|
166
|
+
|
|
167
|
+
if (cacheExpired) {
|
|
168
|
+
// Check if within grace period (30 days)
|
|
169
|
+
if (Date.now() < cached.graceUntil) {
|
|
170
|
+
return {valid: cached.valid, tier: cached.tier, offline: true, gracePeriod: true};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return {valid: false, expired: true};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return {valid: cached.valid, tier: cached.tier};
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
// Make available globally for background.js
|
|
181
|
+
self.LicenseHelper = LicenseHelper;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// Simple conditional logger for Chrome extension
|
|
2
|
+
// Shows logs in development mode only
|
|
3
|
+
|
|
4
|
+
// Check if we're in development mode (can be set via extension config)
|
|
5
|
+
const isDevelopment = () => {
|
|
6
|
+
// Check if config defines development mode
|
|
7
|
+
if (typeof CHROMEDEBUG_CONFIG !== 'undefined' && CHROMEDEBUG_CONFIG.development !== undefined) {
|
|
8
|
+
return CHROMEDEBUG_CONFIG.development;
|
|
9
|
+
}
|
|
10
|
+
// Default to true for now (can be configured in extension-config.js)
|
|
11
|
+
return true;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const logger = {
|
|
15
|
+
log: (...args) => { if (isDevelopment()) console.log(...args); },
|
|
16
|
+
error: (...args) => console.error(...args), // Always show errors
|
|
17
|
+
warn: (...args) => { if (isDevelopment()) console.warn(...args); },
|
|
18
|
+
info: (...args) => { if (isDevelopment()) console.info(...args); },
|
|
19
|
+
debug: (...args) => { if (isDevelopment()) console.debug(...args); }
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
// Make available globally for the extension
|
|
23
|
+
window.logger = logger;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
{
|
|
2
|
+
"manifest_version": 3,
|
|
3
|
+
"name": "ChromeDebug MCP Assistant v2.1.1",
|
|
4
|
+
"version": "2.1.1",
|
|
5
|
+
"description": "ChromeDebug MCP visual element selector with function tracing [Build: 2025-01-20-v2.1.1]",
|
|
6
|
+
"permissions": [
|
|
7
|
+
"activeTab",
|
|
8
|
+
"scripting",
|
|
9
|
+
"tabs",
|
|
10
|
+
"storage",
|
|
11
|
+
"tabCapture",
|
|
12
|
+
"desktopCapture",
|
|
13
|
+
"notifications",
|
|
14
|
+
"offscreen"
|
|
15
|
+
],
|
|
16
|
+
"host_permissions": [
|
|
17
|
+
"https://*.cloudfunctions.net/*"
|
|
18
|
+
],
|
|
19
|
+
"action": {
|
|
20
|
+
"default_popup": "popup.html",
|
|
21
|
+
"default_icon": {
|
|
22
|
+
"16": "icon16.png",
|
|
23
|
+
"48": "icon48.png",
|
|
24
|
+
"128": "icon128.png"
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"options_page": "options.html",
|
|
28
|
+
"content_scripts": [
|
|
29
|
+
{
|
|
30
|
+
"matches": [
|
|
31
|
+
"<all_urls>"
|
|
32
|
+
],
|
|
33
|
+
"exclude_matches": [
|
|
34
|
+
"*://youtube.com/*",
|
|
35
|
+
"*://www.youtube.com/*",
|
|
36
|
+
"*://m.youtube.com/*",
|
|
37
|
+
"*://google.com/*",
|
|
38
|
+
"*://www.google.com/*",
|
|
39
|
+
"*://gmail.com/*",
|
|
40
|
+
"*://www.gmail.com/*",
|
|
41
|
+
"*://facebook.com/*",
|
|
42
|
+
"*://www.facebook.com/*",
|
|
43
|
+
"*://twitter.com/*",
|
|
44
|
+
"*://www.twitter.com/*",
|
|
45
|
+
"*://x.com/*",
|
|
46
|
+
"*://www.x.com/*",
|
|
47
|
+
"*://instagram.com/*",
|
|
48
|
+
"*://www.instagram.com/*"
|
|
49
|
+
],
|
|
50
|
+
"js": [
|
|
51
|
+
"pako.min.js",
|
|
52
|
+
"web-vitals.iife.js",
|
|
53
|
+
"pii-redactor.js",
|
|
54
|
+
"data-buffer.js",
|
|
55
|
+
"performance-monitor.js",
|
|
56
|
+
"dom-tracker.js",
|
|
57
|
+
"network-tracker.js",
|
|
58
|
+
"function-tracker.js",
|
|
59
|
+
"content.js"
|
|
60
|
+
],
|
|
61
|
+
"css": ["content.css"],
|
|
62
|
+
"run_at": "document_idle"
|
|
63
|
+
}
|
|
64
|
+
],
|
|
65
|
+
"background": {
|
|
66
|
+
"service_worker": "background.js"
|
|
67
|
+
},
|
|
68
|
+
"icons": {
|
|
69
|
+
"16": "icon16.png",
|
|
70
|
+
"48": "icon48.png",
|
|
71
|
+
"128": "icon128.png"
|
|
72
|
+
}
|
|
73
|
+
}
|