UNPKG

web-see-monitor

Version:

前端监控 SDK,包含性能监控、错误监控和用户行为监控

287 lines (279 loc) 9.37 kB
'use strict'; class Monitor { constructor(config) { var _a, _b, _c, _d; this.plugins = new Map(); this.cache = []; this.timer = null; this.MAX_CACHE_LENGTH = 10; this.REPORT_INTERVAL = 5000; // 5秒 this.config = { ...config, enable: (_a = config.enable) !== null && _a !== void 0 ? _a : true, sampling: (_b = config.sampling) !== null && _b !== void 0 ? _b : 1, maxCache: (_c = config.maxCache) !== null && _c !== void 0 ? _c : this.MAX_CACHE_LENGTH, reportInterval: (_d = config.reportInterval) !== null && _d !== void 0 ? _d : this.REPORT_INTERVAL, }; this.init(); } init() { if (!this.config.enable) return; // 初始化插件 if (this.config.plugins) { this.config.plugins.forEach(Plugin => { const instance = new Plugin(); instance.setReport(this.addCache.bind(this)); instance.init(); this.plugins.set(instance.name, instance); }); } // 页面卸载前发送剩余数据 window.addEventListener('unload', () => { this.report(true); }); } destroy() { this.plugins.forEach(plugin => plugin.destroy()); this.plugins.clear(); if (this.timer) { clearTimeout(this.timer); this.timer = null; } } addCache(data) { // 采样 if (Math.random() > this.config.sampling) return; this.cache.push({ ...data, timestamp: Date.now(), }); if (this.cache.length >= this.config.maxCache) { this.report(); } else if (!this.timer) { this.timer = setTimeout(() => this.report(), this.config.reportInterval); } } report(isUnload = false) { if (this.cache.length === 0) return; const data = this.cache.slice(); this.cache = []; if (this.timer) { clearTimeout(this.timer); this.timer = null; } const reportData = { appId: this.config.appId, userId: this.config.userId, userAgent: navigator.userAgent, data }; // 页面卸载前使用 sendBeacon if (isUnload && navigator.sendBeacon) { navigator.sendBeacon(this.config.reportUrl, JSON.stringify(reportData)); return; } // 普通上报使用 fetch fetch(this.config.reportUrl, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(reportData), keepalive: true }).catch(error => { console.error('Report error:', error); // 上报失败,重新加入缓存 this.cache.push(...data); }); } } class BasePlugin { constructor() { this.config = {}; } setConfig(config) { this.config = { ...this.config, ...config }; } setReport(report) { this.report = report; } } class PerformancePlugin extends BasePlugin { constructor() { super(...arguments); this.name = 'performance'; } init() { this.monitorPageLoad(); this.monitorResourceLoad(); } destroy() { var _a; (_a = this.performanceObserver) === null || _a === void 0 ? void 0 : _a.disconnect(); } monitorPageLoad() { window.addEventListener('load', () => { setTimeout(() => { var _a; const performance = window.performance; if (!performance) return; const timing = performance.timing; const data = { type: 'performance', subType: 'page-load', dnsTime: timing.domainLookupEnd - timing.domainLookupStart, tcpTime: timing.connectEnd - timing.connectStart, whiteScreenTime: timing.domLoading - timing.navigationStart, domReadyTime: timing.domContentLoadedEventEnd - timing.navigationStart, loadTime: timing.loadEventEnd - timing.navigationStart, }; (_a = this.report) === null || _a === void 0 ? void 0 : _a.call(this, data); }, 0); }); } monitorResourceLoad() { this.performanceObserver = new PerformanceObserver((list) => { const entries = list.getEntries(); entries.forEach((entry) => { var _a; const data = { type: 'performance', subType: 'resource', name: entry.name, initiatorType: entry.initiatorType, duration: entry.duration, transferSize: entry.transferSize, }; (_a = this.report) === null || _a === void 0 ? void 0 : _a.call(this, data); }); }); this.performanceObserver.observe({ entryTypes: ['resource'] }); } } class ErrorPlugin extends BasePlugin { constructor() { super(); this.name = 'error'; this.errorHandler = this.handleError.bind(this); this.rejectionHandler = this.handleRejection.bind(this); } init() { window.addEventListener('error', this.errorHandler, true); window.addEventListener('unhandledrejection', this.rejectionHandler); } destroy() { if (this.errorHandler) { window.removeEventListener('error', this.errorHandler, true); } if (this.rejectionHandler) { window.removeEventListener('unhandledrejection', this.rejectionHandler); } } shouldIgnoreError(error) { const ignoreErrors = (this.config.ignoreErrors || []); const errorMessage = error instanceof Error ? error.message : error; return ignoreErrors.some(pattern => pattern.test(errorMessage)); } handleError(event) { var _a, _b; if (this.shouldIgnoreError(event.error || event.message)) return; const data = { type: 'error', subType: 'javascript', message: event.message, filename: event.filename, position: `${event.lineno}:${event.colno}`, stack: (_a = event.error) === null || _a === void 0 ? void 0 : _a.stack, }; (_b = this.report) === null || _b === void 0 ? void 0 : _b.call(this, data); } handleRejection(event) { var _a; const error = event.reason; if (this.shouldIgnoreError(error)) return; const data = { type: 'error', subType: 'promise', message: (error === null || error === void 0 ? void 0 : error.message) || error, stack: error === null || error === void 0 ? void 0 : error.stack, }; (_a = this.report) === null || _a === void 0 ? void 0 : _a.call(this, data); } } function debounce(fn, delay) { let timer = null; return function (...args) { if (timer) clearTimeout(timer); timer = setTimeout(() => { fn.apply(this, args); timer = null; }, delay); }; } class BehaviorPlugin extends BasePlugin { constructor() { super(); this.name = 'behavior'; this.clickHandler = debounce(this.handleClick.bind(this), 300); } init() { this.monitorPV(); document.addEventListener('click', this.clickHandler, true); } destroy() { if (this.clickHandler) { document.removeEventListener('click', this.clickHandler, true); } } monitorPV() { var _a; const data = { type: 'behavior', subType: 'pv', url: window.location.href, referer: document.referrer, }; (_a = this.report) === null || _a === void 0 ? void 0 : _a.call(this, data); } handleClick(event) { var _a; const target = event.target; const data = { type: 'behavior', subType: 'click', path: this.getElementPath(target), }; (_a = this.report) === null || _a === void 0 ? void 0 : _a.call(this, data); } getElementPath(element) { if (!element || !element.tagName) return ''; const path = []; let currentElement = element; while (currentElement) { let selector = currentElement.tagName.toLowerCase(); if (currentElement.id) { selector += `#${currentElement.id}`; } else if (currentElement.className) { selector += `.${currentElement.className.split(' ').join('.')}`; } path.unshift(selector); currentElement = currentElement.parentElement; } return path.join(' > '); } } exports.BehaviorPlugin = BehaviorPlugin; exports.ErrorPlugin = ErrorPlugin; exports.Monitor = Monitor; exports.PerformancePlugin = PerformancePlugin; //# sourceMappingURL=index.cjs.map