UNPKG

frontend-performance-monitor

Version:

This is browser performance and error monitoring tool

153 lines (152 loc) 5.8 kB
/** * FrontendMonitor class for monitoring web performance and errors. */ export class FrontendMonitor { constructor(reportUrl) { this.isMonitoring = false; this.clsValue = 0; this.reportUrl = reportUrl; this.performanceTiming = performance.timing; } // Initialize monitoring init() { if (this.isMonitoring) return; this.isMonitoring = true; this.setupPerformanceObserver(); this.captureErrors(); window.addEventListener('load', () => this.reportPerformance()); } // Performance metrics monitoring setupPerformanceObserver() { if (!('PerformanceObserver' in window)) return; // Monitor FP/FCP new PerformanceObserver(list => { for (const entry of list.getEntriesByName('first-paint')) { this.report({ type: 'performance', data: { ...this.getBasicTiming(), fp: Math.round(entry.startTime) } }); } for (const entry of list.getEntriesByName('first-contentful-paint')) { this.report({ type: 'performance', data: { ...this.getBasicTiming(), fcp: Math.round(entry.startTime) } }); } }).observe({ type: 'paint', buffered: true }); // Monitor LCP new PerformanceObserver(list => { const entries = list.getEntries(); if (entries.length > 0) { const lastEntry = entries[entries.length - 1]; const lcp = lastEntry.renderTime || lastEntry.loadTime; this.report({ type: 'performance', data: { ...this.getBasicTiming(), lcp: Math.round(lcp) } }); } }).observe({ type: 'largest-contentful-paint', buffered: true }); // Monitor CLS new PerformanceObserver(list => { const entries = list.getEntries(); for (const entry of entries) { if (!entry.hadRecentInput) { this.clsValue += entry.value; } } this.report({ type: 'performance', data: { ...this.getBasicTiming(), cls: this.clsValue } }); }).observe({ type: 'layout-shift', buffered: true }); } // Calculate basic performance metrics getBasicTiming() { const t = this.performanceTiming; return { dns: t.domainLookupEnd - t.domainLookupStart, tcp: t.connectEnd - t.connectStart, ttfb: t.responseStart - t.requestStart, domParse: t.domComplete - t.domInteractive, resources: t.loadEventStart - t.domContentLoadedEventEnd, domReady: t.domContentLoadedEventEnd - t.navigationStart, interactive: t.domInteractive - t.navigationStart, load: t.loadEventEnd - t.navigationStart }; } // Error monitoring captureErrors() { // JavaScript runtime errors window.addEventListener('error', event => { var _a; this.report({ type: 'error', data: { message: event.message, filename: event.filename || window.location.href, lineno: event.lineno || 0, colno: event.colno || 0, stack: (_a = event.error) === null || _a === void 0 ? void 0 : _a.stack, type: 'js', timestamp: Date.now() } }); }, true); // Resource loading errors window.addEventListener('error', event => { const target = event.target; if (target && (target.tagName === 'LINK' || target.tagName === 'SCRIPT' || target.tagName === 'IMG')) { this.report({ type: 'error', data: { message: `Resource load error: ${target.src || target.href}`, filename: target.src || target.href || '', lineno: 0, colno: 0, type: 'resource', timestamp: Date.now() } }); } }, true); // Uncaught promise rejections window.addEventListener('unhandledrejection', event => { var _a, _b; this.report({ type: 'error', data: { message: ((_a = event.reason) === null || _a === void 0 ? void 0 : _a.message) || 'Unhandled promise rejection', filename: window.location.href, lineno: 0, colno: 0, stack: (_b = event.reason) === null || _b === void 0 ? void 0 : _b.stack, type: 'promise', timestamp: Date.now() } }); }); } // Report core performance metrics reportPerformance() { this.report({ type: 'performance', data: this.getBasicTiming() }); } // Data reporting method report(data) { if (navigator.sendBeacon) { const blob = new Blob([JSON.stringify(data)], { type: 'application/json' }); navigator.sendBeacon(this.reportUrl, blob); } else { // Fallback using XMLHttpRequest const xhr = new XMLHttpRequest(); xhr.open('POST', this.reportUrl, true); xhr.setRequestHeader('Content-Type', 'application/json'); xhr.send(JSON.stringify(data)); } } }