@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.
Files changed (95) hide show
  1. package/CLAUDE.md +344 -0
  2. package/LICENSE +21 -0
  3. package/README.md +250 -0
  4. package/chrome-extension/README.md +41 -0
  5. package/chrome-extension/background.js +3917 -0
  6. package/chrome-extension/chrome-session-manager.js +706 -0
  7. package/chrome-extension/content.css +181 -0
  8. package/chrome-extension/content.js +3022 -0
  9. package/chrome-extension/data-buffer.js +435 -0
  10. package/chrome-extension/dom-tracker.js +411 -0
  11. package/chrome-extension/extension-config.js +78 -0
  12. package/chrome-extension/firebase-client.js +278 -0
  13. package/chrome-extension/firebase-config.js +32 -0
  14. package/chrome-extension/firebase-config.module.js +22 -0
  15. package/chrome-extension/firebase-config.module.template.js +27 -0
  16. package/chrome-extension/firebase-config.template.js +36 -0
  17. package/chrome-extension/frame-capture.js +407 -0
  18. package/chrome-extension/icon128.png +1 -0
  19. package/chrome-extension/icon16.png +1 -0
  20. package/chrome-extension/icon48.png +1 -0
  21. package/chrome-extension/license-helper.js +181 -0
  22. package/chrome-extension/logger.js +23 -0
  23. package/chrome-extension/manifest.json +73 -0
  24. package/chrome-extension/network-tracker.js +510 -0
  25. package/chrome-extension/offscreen.html +10 -0
  26. package/chrome-extension/options.html +203 -0
  27. package/chrome-extension/options.js +282 -0
  28. package/chrome-extension/pako.min.js +2 -0
  29. package/chrome-extension/performance-monitor.js +533 -0
  30. package/chrome-extension/pii-redactor.js +405 -0
  31. package/chrome-extension/popup.html +532 -0
  32. package/chrome-extension/popup.js +2446 -0
  33. package/chrome-extension/upload-manager.js +323 -0
  34. package/chrome-extension/web-vitals.iife.js +1 -0
  35. package/config/api-keys.json +11 -0
  36. package/config/chrome-pilot-config.json +45 -0
  37. package/package.json +126 -0
  38. package/scripts/cleanup-processes.js +109 -0
  39. package/scripts/config-manager.js +280 -0
  40. package/scripts/generate-extension-config.js +53 -0
  41. package/scripts/setup-security.js +64 -0
  42. package/src/capture/architecture.js +426 -0
  43. package/src/capture/error-handling-tests.md +38 -0
  44. package/src/capture/error-handling-types.ts +360 -0
  45. package/src/capture/index.js +508 -0
  46. package/src/capture/interfaces.js +625 -0
  47. package/src/capture/memory-manager.js +713 -0
  48. package/src/capture/types.js +342 -0
  49. package/src/chrome-controller.js +2658 -0
  50. package/src/cli.js +19 -0
  51. package/src/config-loader.js +303 -0
  52. package/src/database.js +2178 -0
  53. package/src/firebase-license-manager.js +462 -0
  54. package/src/firebase-privacy-guard.js +397 -0
  55. package/src/http-server.js +1516 -0
  56. package/src/index-direct.js +157 -0
  57. package/src/index-modular.js +219 -0
  58. package/src/index-monolithic-backup.js +2230 -0
  59. package/src/index.js +305 -0
  60. package/src/legacy/chrome-controller-old.js +1406 -0
  61. package/src/legacy/index-express.js +625 -0
  62. package/src/legacy/index-old.js +977 -0
  63. package/src/legacy/routes.js +260 -0
  64. package/src/legacy/shared-storage.js +101 -0
  65. package/src/logger.js +10 -0
  66. package/src/mcp/handlers/chrome-tool-handler.js +306 -0
  67. package/src/mcp/handlers/element-tool-handler.js +51 -0
  68. package/src/mcp/handlers/frame-tool-handler.js +957 -0
  69. package/src/mcp/handlers/request-handler.js +104 -0
  70. package/src/mcp/handlers/workflow-tool-handler.js +636 -0
  71. package/src/mcp/server.js +68 -0
  72. package/src/mcp/tools/index.js +701 -0
  73. package/src/middleware/auth.js +371 -0
  74. package/src/middleware/security.js +267 -0
  75. package/src/port-discovery.js +258 -0
  76. package/src/routes/admin.js +182 -0
  77. package/src/services/browser-daemon.js +494 -0
  78. package/src/services/chrome-service.js +375 -0
  79. package/src/services/failover-manager.js +412 -0
  80. package/src/services/git-safety-service.js +675 -0
  81. package/src/services/heartbeat-manager.js +200 -0
  82. package/src/services/http-client.js +195 -0
  83. package/src/services/process-manager.js +318 -0
  84. package/src/services/process-tracker.js +574 -0
  85. package/src/services/profile-manager.js +449 -0
  86. package/src/services/project-manager.js +415 -0
  87. package/src/services/session-manager.js +497 -0
  88. package/src/services/session-registry.js +491 -0
  89. package/src/services/unified-session-manager.js +678 -0
  90. package/src/shared-storage-old.js +267 -0
  91. package/src/standalone-server.js +53 -0
  92. package/src/utils/extension-path.js +145 -0
  93. package/src/utils.js +187 -0
  94. package/src/validation/log-transformer.js +125 -0
  95. 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
+ }