@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,533 @@
1
+ // Performance Monitor for Chrome Debug Full Data Recording
2
+ // Tracks CPU usage, memory consumption, and Core Web Vitals
3
+
4
+ class PerformanceMonitor {
5
+ constructor() {
6
+ this.isMonitoring = false;
7
+ this.stats = {
8
+ cpuUsage: 0,
9
+ memoryUsage: 0,
10
+ dataVolume: 0,
11
+ eventCount: 0,
12
+ bufferSize: 0,
13
+ lastCheck: Date.now(),
14
+ // Web Vitals
15
+ lcp: null,
16
+ cls: null,
17
+ fid: null,
18
+ inp: null,
19
+ ttfb: null
20
+ };
21
+
22
+ this.thresholds = {
23
+ cpuUsage: 80, // % - throttle recording if exceeded
24
+ memoryUsage: 100 * 1024 * 1024, // 100MB
25
+ dataVolumePerSecond: 1024 * 1024, // 1MB/sec
26
+ eventCountPerSecond: 100,
27
+ // Web Vitals thresholds
28
+ lcp: 2500, // 2.5s is "good" LCP
29
+ cls: 0.1, // 0.1 is "good" CLS
30
+ inp: 200 // 200ms is "good" INP
31
+ };
32
+
33
+ this.callbacks = {
34
+ onThrottleRequired: null,
35
+ onPerformanceAlert: null,
36
+ onStatsUpdate: null
37
+ };
38
+
39
+ this.monitoringInterval = null;
40
+ this.dataBuffer = null;
41
+ this.startTime = Date.now();
42
+ this.webVitalsUnsubscribers = [];
43
+ this.performanceObserver = null;
44
+ }
45
+
46
+ init(dataBuffer, callbacks = {}) {
47
+ this.dataBuffer = dataBuffer;
48
+ this.callbacks = { ...this.callbacks, ...callbacks };
49
+
50
+ // Just prepare for monitoring, don't start yet (lazy loading)
51
+ console.log('[PerformanceMonitor] Initialized (lazy mode - not started)');
52
+ return true;
53
+ }
54
+
55
+ startMonitoring() {
56
+ if (this.isMonitoring) {
57
+ console.log('[PerformanceMonitor] Already monitoring');
58
+ return;
59
+ }
60
+
61
+ this.isMonitoring = true;
62
+ this.startTime = Date.now();
63
+
64
+ // Start Web Vitals collection
65
+ this.startWebVitalsCollection();
66
+
67
+ // Start resource monitoring
68
+ if (performance && performance.memory) {
69
+ this.startResourceMonitoring();
70
+ console.log('[PerformanceMonitor] Started with full performance tracking');
71
+ } else {
72
+ console.warn('[PerformanceMonitor] Performance API not available, using basic monitoring');
73
+ this.startBasicMonitoring();
74
+ }
75
+ }
76
+
77
+ startWebVitalsCollection() {
78
+ // Check if web-vitals library is available
79
+ if (typeof webVitals !== 'undefined') {
80
+ console.log('[PerformanceMonitor] Using web-vitals library');
81
+
82
+ // Track LCP
83
+ if (webVitals.onLCP) {
84
+ const unsubscribeLCP = webVitals.onLCP((metric) => {
85
+ this.stats.lcp = metric.value;
86
+ console.log('[Web Vitals] LCP:', metric.value);
87
+ this.checkWebVitalsThresholds('lcp', metric.value);
88
+ });
89
+ this.webVitalsUnsubscribers.push(unsubscribeLCP);
90
+ }
91
+
92
+ // Track CLS
93
+ if (webVitals.onCLS) {
94
+ const unsubscribeCLS = webVitals.onCLS((metric) => {
95
+ this.stats.cls = metric.value;
96
+ console.log('[Web Vitals] CLS:', metric.value);
97
+ this.checkWebVitalsThresholds('cls', metric.value);
98
+ });
99
+ this.webVitalsUnsubscribers.push(unsubscribeCLS);
100
+ }
101
+
102
+ // Track FID
103
+ if (webVitals.onFID) {
104
+ const unsubscribeFID = webVitals.onFID((metric) => {
105
+ this.stats.fid = metric.value;
106
+ console.log('[Web Vitals] FID:', metric.value);
107
+ });
108
+ this.webVitalsUnsubscribers.push(unsubscribeFID);
109
+ }
110
+
111
+ // Track INP
112
+ if (webVitals.onINP) {
113
+ const unsubscribeINP = webVitals.onINP((metric) => {
114
+ this.stats.inp = metric.value;
115
+ console.log('[Web Vitals] INP:', metric.value);
116
+ this.checkWebVitalsThresholds('inp', metric.value);
117
+ });
118
+ this.webVitalsUnsubscribers.push(unsubscribeINP);
119
+ }
120
+
121
+ // Track TTFB
122
+ if (webVitals.onTTFB) {
123
+ const unsubscribeTTFB = webVitals.onTTFB((metric) => {
124
+ this.stats.ttfb = metric.value;
125
+ console.log('[Web Vitals] TTFB:', metric.value);
126
+ });
127
+ this.webVitalsUnsubscribers.push(unsubscribeTTFB);
128
+ }
129
+ } else {
130
+ console.log('[PerformanceMonitor] web-vitals not available, using fallback PerformanceObserver');
131
+ this.startPerformanceObserverFallback();
132
+ }
133
+ }
134
+
135
+ startPerformanceObserverFallback() {
136
+ // Fallback implementation using PerformanceObserver
137
+ if (typeof PerformanceObserver === 'undefined') {
138
+ console.warn('[PerformanceMonitor] PerformanceObserver not available');
139
+ return;
140
+ }
141
+
142
+ try {
143
+ // Observe paint entries (includes LCP)
144
+ const paintObserver = new PerformanceObserver((list) => {
145
+ for (const entry of list.getEntries()) {
146
+ if (entry.entryType === 'largest-contentful-paint') {
147
+ this.stats.lcp = entry.startTime;
148
+ console.log('[Fallback] LCP:', entry.startTime);
149
+ }
150
+ }
151
+ });
152
+
153
+ // Try to observe paint entries
154
+ try {
155
+ paintObserver.observe({ entryTypes: ['largest-contentful-paint'] });
156
+ this.performanceObserver = paintObserver;
157
+ } catch (e) {
158
+ console.warn('[PerformanceMonitor] Could not observe LCP:', e);
159
+ }
160
+
161
+ // Note: CLS and other metrics are complex to implement correctly without web-vitals
162
+ console.warn('[PerformanceMonitor] Using fallback - some metrics may be less accurate');
163
+ } catch (error) {
164
+ console.error('[PerformanceMonitor] Failed to create PerformanceObserver:', error);
165
+ }
166
+ }
167
+
168
+ startResourceMonitoring() {
169
+ // Monitor every 2 seconds
170
+ this.monitoringInterval = setInterval(() => {
171
+ this.updateResourceStats();
172
+ this.checkResourceThresholds();
173
+ }, 2000);
174
+
175
+ // Initial update
176
+ this.updateResourceStats();
177
+ }
178
+
179
+ startBasicMonitoring() {
180
+ // Basic monitoring without performance.memory
181
+ this.monitoringInterval = setInterval(() => {
182
+ this.updateBasicStats();
183
+ this.checkBasicThresholds();
184
+ }, 5000);
185
+
186
+ // Initial update
187
+ this.updateBasicStats();
188
+ }
189
+
190
+ async updateResourceStats() {
191
+ const now = Date.now();
192
+ const timeDelta = (now - this.stats.lastCheck) / 1000; // seconds
193
+
194
+ // CPU Usage (estimated from script execution time)
195
+ const startTime = performance.now();
196
+ this.simulateWork(); // Small workload to measure responsiveness
197
+ const executionTime = performance.now() - startTime;
198
+ this.stats.cpuUsage = Math.min(100, (executionTime / 10) * 100); // Rough estimate
199
+
200
+ // Memory Usage
201
+ if (performance.memory) {
202
+ this.stats.memoryUsage = performance.memory.usedJSHeapSize;
203
+ }
204
+
205
+ // Data Buffer Stats
206
+ if (this.dataBuffer) {
207
+ const bufferStats = await this.dataBuffer.getStats();
208
+ const newEventCount = bufferStats.eventCount;
209
+ const newDataVolume = bufferStats.totalSize;
210
+
211
+ // Calculate rates
212
+ this.stats.eventRate = (newEventCount - this.stats.eventCount) / timeDelta;
213
+ this.stats.dataRate = (newDataVolume - this.stats.dataVolume) / timeDelta;
214
+
215
+ this.stats.eventCount = newEventCount;
216
+ this.stats.dataVolume = newDataVolume;
217
+ this.stats.bufferSize = bufferStats.totalSize;
218
+ this.stats.bufferUtilization = (bufferStats.totalSize / bufferStats.maxSize) * 100;
219
+ }
220
+
221
+ this.stats.lastCheck = now;
222
+
223
+ // Notify callbacks
224
+ if (this.callbacks.onStatsUpdate) {
225
+ this.callbacks.onStatsUpdate(this.getPublicStats());
226
+ }
227
+ }
228
+
229
+ updateBasicStats() {
230
+ const now = Date.now();
231
+
232
+ // Basic stats without performance.memory
233
+ if (this.dataBuffer) {
234
+ this.dataBuffer.getStats().then(bufferStats => {
235
+ this.stats.eventCount = bufferStats.eventCount;
236
+ this.stats.dataVolume = bufferStats.totalSize;
237
+ this.stats.bufferSize = bufferStats.totalSize;
238
+ this.stats.bufferUtilization = (bufferStats.totalSize / bufferStats.maxSize) * 100;
239
+
240
+ if (this.callbacks.onStatsUpdate) {
241
+ this.callbacks.onStatsUpdate(this.getPublicStats());
242
+ }
243
+ });
244
+ }
245
+
246
+ this.stats.lastCheck = now;
247
+ }
248
+
249
+ simulateWork() {
250
+ // Small computational task to measure responsiveness
251
+ let sum = 0;
252
+ for (let i = 0; i < 1000; i++) {
253
+ sum += Math.random();
254
+ }
255
+ return sum;
256
+ }
257
+
258
+ checkWebVitalsThresholds(metric, value) {
259
+ const alerts = [];
260
+
261
+ if (metric === 'lcp' && value > this.thresholds.lcp) {
262
+ alerts.push({
263
+ type: 'webvitals',
264
+ severity: 'medium',
265
+ message: `Poor LCP: ${value.toFixed(0)}ms (threshold: ${this.thresholds.lcp}ms)`,
266
+ recommendation: 'Optimize largest content paint performance'
267
+ });
268
+ }
269
+
270
+ if (metric === 'cls' && value > this.thresholds.cls) {
271
+ alerts.push({
272
+ type: 'webvitals',
273
+ severity: 'medium',
274
+ message: `Poor CLS: ${value.toFixed(3)} (threshold: ${this.thresholds.cls})`,
275
+ recommendation: 'Reduce layout shifts during page load'
276
+ });
277
+ }
278
+
279
+ if (metric === 'inp' && value > this.thresholds.inp) {
280
+ alerts.push({
281
+ type: 'webvitals',
282
+ severity: 'medium',
283
+ message: `Poor INP: ${value.toFixed(0)}ms (threshold: ${this.thresholds.inp}ms)`,
284
+ recommendation: 'Improve interaction responsiveness'
285
+ });
286
+ }
287
+
288
+ if (alerts.length > 0 && this.callbacks.onPerformanceAlert) {
289
+ this.callbacks.onPerformanceAlert(alerts);
290
+ }
291
+ }
292
+
293
+ checkResourceThresholds() {
294
+ const alerts = [];
295
+
296
+ // CPU threshold
297
+ if (this.stats.cpuUsage > this.thresholds.cpuUsage) {
298
+ alerts.push({
299
+ type: 'cpu',
300
+ severity: 'high',
301
+ message: `High CPU usage: ${this.stats.cpuUsage.toFixed(1)}%`,
302
+ recommendation: 'Consider reducing instrumentation level'
303
+ });
304
+ }
305
+
306
+ // Memory threshold
307
+ if (this.stats.memoryUsage > this.thresholds.memoryUsage) {
308
+ alerts.push({
309
+ type: 'memory',
310
+ severity: 'high',
311
+ message: `High memory usage: ${(this.stats.memoryUsage / 1024 / 1024).toFixed(1)}MB`,
312
+ recommendation: 'Buffer may need to be cleared'
313
+ });
314
+ }
315
+
316
+ // Data rate threshold
317
+ if (this.stats.dataRate > this.thresholds.dataVolumePerSecond) {
318
+ alerts.push({
319
+ type: 'dataRate',
320
+ severity: 'medium',
321
+ message: `High data rate: ${(this.stats.dataRate / 1024).toFixed(1)}KB/s`,
322
+ recommendation: 'Consider reducing capture frequency'
323
+ });
324
+ }
325
+
326
+ // Event rate threshold
327
+ if (this.stats.eventRate > this.thresholds.eventCountPerSecond) {
328
+ alerts.push({
329
+ type: 'eventRate',
330
+ severity: 'medium',
331
+ message: `High event rate: ${this.stats.eventRate.toFixed(1)} events/s`,
332
+ recommendation: 'Consider filtering events or reducing instrumentation'
333
+ });
334
+ }
335
+
336
+ // Buffer utilization
337
+ if (this.stats.bufferUtilization > 90) {
338
+ alerts.push({
339
+ type: 'buffer',
340
+ severity: 'high',
341
+ message: `Buffer nearly full: ${this.stats.bufferUtilization.toFixed(1)}%`,
342
+ recommendation: 'Force upload or clear buffer'
343
+ });
344
+ }
345
+
346
+ // Send alerts
347
+ if (alerts.length > 0) {
348
+ this.handlePerformanceAlerts(alerts);
349
+ }
350
+ }
351
+
352
+ checkBasicThresholds() {
353
+ const alerts = [];
354
+
355
+ // Only check buffer-related thresholds in basic mode
356
+ if (this.stats.bufferUtilization > 90) {
357
+ alerts.push({
358
+ type: 'buffer',
359
+ severity: 'high',
360
+ message: `Buffer nearly full: ${this.stats.bufferUtilization.toFixed(1)}%`,
361
+ recommendation: 'Force upload or clear buffer'
362
+ });
363
+ }
364
+
365
+ if (alerts.length > 0) {
366
+ this.handlePerformanceAlerts(alerts);
367
+ }
368
+ }
369
+
370
+ handlePerformanceAlerts(alerts) {
371
+ console.warn('[PerformanceMonitor] Performance alerts:', JSON.stringify(alerts, null, 2));
372
+
373
+ // Check if throttling is required
374
+ const highSeverityAlerts = alerts.filter(a => a.severity === 'high');
375
+ if (highSeverityAlerts.length > 0 && this.callbacks.onThrottleRequired) {
376
+ this.callbacks.onThrottleRequired(highSeverityAlerts);
377
+ }
378
+
379
+ // Send all alerts to callback
380
+ if (this.callbacks.onPerformanceAlert) {
381
+ this.callbacks.onPerformanceAlert(alerts);
382
+ }
383
+ }
384
+
385
+ getPublicStats() {
386
+ const uptime = (Date.now() - this.startTime) / 1000;
387
+
388
+ return {
389
+ ...this.stats,
390
+ uptime: uptime,
391
+ averageEventRate: this.stats.eventCount / uptime,
392
+ averageDataRate: this.stats.dataVolume / uptime,
393
+ memoryUsageMB: this.stats.memoryUsage ? (this.stats.memoryUsage / 1024 / 1024).toFixed(2) : 'N/A',
394
+ dataVolumeMB: (this.stats.dataVolume / 1024 / 1024).toFixed(2),
395
+ isHealthy: this.isPerformanceHealthy(),
396
+ webVitals: {
397
+ lcp: this.stats.lcp,
398
+ cls: this.stats.cls,
399
+ fid: this.stats.fid,
400
+ inp: this.stats.inp,
401
+ ttfb: this.stats.ttfb
402
+ }
403
+ };
404
+ }
405
+
406
+ isPerformanceHealthy() {
407
+ return (
408
+ this.stats.cpuUsage < this.thresholds.cpuUsage &&
409
+ this.stats.memoryUsage < this.thresholds.memoryUsage &&
410
+ this.stats.bufferUtilization < 80 &&
411
+ (this.stats.lcp === null || this.stats.lcp < this.thresholds.lcp) &&
412
+ (this.stats.cls === null || this.stats.cls < this.thresholds.cls)
413
+ );
414
+ }
415
+
416
+ recordEvent(eventType, data) {
417
+ // Record custom performance events
418
+ if (performance.mark) {
419
+ performance.mark(`chromepilot-${eventType}-${Date.now()}`);
420
+ }
421
+
422
+ // Track event-specific metrics
423
+ switch (eventType) {
424
+ case 'dom_mutation':
425
+ this.stats.domMutationCount = (this.stats.domMutationCount || 0) + 1;
426
+ break;
427
+ case 'function_call':
428
+ this.stats.functionCallCount = (this.stats.functionCallCount || 0) + 1;
429
+ break;
430
+ case 'network_request':
431
+ this.stats.networkRequestCount = (this.stats.networkRequestCount || 0) + 1;
432
+ break;
433
+ }
434
+ }
435
+
436
+ getRecommendations() {
437
+ const recommendations = [];
438
+
439
+ if (this.stats.cpuUsage > 60) {
440
+ recommendations.push({
441
+ type: 'instrumentation',
442
+ message: 'Reduce instrumentation level to improve performance',
443
+ action: 'lower_level'
444
+ });
445
+ }
446
+
447
+ if (this.stats.bufferUtilization > 70) {
448
+ recommendations.push({
449
+ type: 'buffer',
450
+ message: 'Force upload data to free buffer space',
451
+ action: 'force_upload'
452
+ });
453
+ }
454
+
455
+ if (this.stats.eventRate > 50) {
456
+ recommendations.push({
457
+ type: 'filtering',
458
+ message: 'Add event filtering to reduce data volume',
459
+ action: 'add_filters'
460
+ });
461
+ }
462
+
463
+ // Web Vitals recommendations
464
+ if (this.stats.lcp > this.thresholds.lcp) {
465
+ recommendations.push({
466
+ type: 'performance',
467
+ message: 'LCP is poor - optimize largest content paint',
468
+ action: 'optimize_lcp'
469
+ });
470
+ }
471
+
472
+ if (this.stats.cls > this.thresholds.cls) {
473
+ recommendations.push({
474
+ type: 'performance',
475
+ message: 'CLS is poor - reduce layout shifts',
476
+ action: 'reduce_cls'
477
+ });
478
+ }
479
+
480
+ return recommendations;
481
+ }
482
+
483
+ setThresholds(newThresholds) {
484
+ this.thresholds = { ...this.thresholds, ...newThresholds };
485
+ console.log('[PerformanceMonitor] Updated thresholds:', this.thresholds);
486
+ }
487
+
488
+ stopMonitoring() {
489
+ if (!this.isMonitoring) {
490
+ console.log('[PerformanceMonitor] Not currently monitoring');
491
+ return;
492
+ }
493
+
494
+ // Stop resource monitoring interval
495
+ if (this.monitoringInterval) {
496
+ clearInterval(this.monitoringInterval);
497
+ this.monitoringInterval = null;
498
+ }
499
+
500
+ // Cleanup web-vitals observers
501
+ if (this.webVitalsUnsubscribers.length > 0) {
502
+ this.webVitalsUnsubscribers.forEach(unsubscribe => {
503
+ if (typeof unsubscribe === 'function') {
504
+ unsubscribe();
505
+ }
506
+ });
507
+ this.webVitalsUnsubscribers = [];
508
+ console.log('[PerformanceMonitor] Web Vitals observers cleaned up');
509
+ }
510
+
511
+ // Cleanup PerformanceObserver fallback
512
+ if (this.performanceObserver) {
513
+ this.performanceObserver.disconnect();
514
+ this.performanceObserver = null;
515
+ console.log('[PerformanceMonitor] PerformanceObserver cleaned up');
516
+ }
517
+
518
+ this.isMonitoring = false;
519
+ console.log('[PerformanceMonitor] Stopped monitoring');
520
+ }
521
+
522
+ destroy() {
523
+ this.stopMonitoring();
524
+ this.callbacks = {};
525
+ this.dataBuffer = null;
526
+ console.log('[PerformanceMonitor] Destroyed');
527
+ }
528
+ }
529
+
530
+ // Export for use in content and background scripts
531
+ if (typeof module !== 'undefined' && module.exports) {
532
+ module.exports = PerformanceMonitor;
533
+ }