perfor-monitor
Version:
性能监控工具库
3 lines (2 loc) • 25.2 kB
JavaScript
"use strict";var e=require("react");class t{constructor(){this.worker=null,this.metrics={},this.radarPlugin=null,window.Worker&&(this.worker=new Worker("performance-worker.js"),this.worker.onmessage=this.handleWorkerMessage.bind(this))}use(e){return this.radarPlugin=e,this}handleWorkerMessage(e){const{type:t,data:s}=e.data;this.metrics[t]=s,requestIdleCallback(()=>{this.radarPlugin?.report({[t]:s})})}postToWorker(e){this.worker?.postMessage(e)}destroy(){this.worker?.terminate(),this.stopMonitoring()}}const s={interval:3e3,lastInterval:performance.now()};class i{constructor(e={}){this.panel=null,this.isExpanded=!0,this.metrics={fps:0,jank:{small:0,medium:0,large:0}},this.panel=null,this.isExpanded=!0,this.panelInfo={title:e.title||"Page",left:e.left||10,width:e.width||200,height:e.height||200,isExpanded:e.isExpanded||!0},this.initPanel(),this.metrics={fps:0,jank:{small:0,medium:0,large:0}}}initPanel(){this.panel=document.createElement("div"),this.panel.id="perf-panel",this.panel.style.cssText="\n position: fixed;\n top: 10px;\n left: 10px;\n background: rgba(0,0,0,0.7);\n color: white;\n border-radius: 4px;\n padding: 2px;\n z-index: 9999;\n backdrop-filter: blur(4px);\n box-shadow: 0 4px 12px rgba(0,0,0,0.2);\n user-select: none;\n width: 200px;\n transition: transform 0.3s;\n ";const e=document.createElement("div");e.style.cssText="\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding-bottom: 2px;\n border-bottom: 1px solid rgba(255,255,255,0.2);\n cursor: move;\n ",e.innerHTML=`\n <span>📊 Jank Frame</span>\n <span>${this.panelInfo?.title||"Page"}</span>\n <button id="toggle-panel" style="background:none; border:none; color:white; font-size:18px;">\n ${this.isExpanded?"−":"+"}\n </button>\n `,this.panel.appendChild(e);const t=document.createElement("div");t.id="perf-content",t.style.cssText="\n padding: 2px;\n cursor: move;\n display: flex;\n justify-content: space-between;\n align-items: center;\n border-bottom: 1px solid rgba(255,255,255,0.2);\n ",t.style.display=this.isExpanded?"block":"none",this.panel.appendChild(t),document.body.appendChild(this.panel),this.bindEvents()}bindEvents(){this.panel?.querySelector("#toggle-panel")?.addEventListener("click",()=>{this.isExpanded=!this.isExpanded;const e=this.panel?.querySelector("#toggle-panel"),t=this.panel?.querySelector("#perf-content");e&&(e.textContent=this.isExpanded?"−":"+"),t&&(t.style.display=this.isExpanded?"block":"none")});let e={x:0,y:0};const t=t=>{this.panel&&(this.panel.style.left=t.clientX-e.x+"px",this.panel.style.top=t.clientY-e.y+"px")};this.panel?.querySelector("div")?.addEventListener("mousedown",s=>{e={x:s.clientX-(this.panel?.offsetLeft||0),y:s.clientY-(this.panel?.offsetTop||0)},document.addEventListener("mousemove",t)}),document.addEventListener("mouseup",()=>{document.removeEventListener("mousemove",t)})}updateMetrics(e){this.metrics=e,this.render()}render(){const e=this.panel?.querySelector("#perf-content");if(performance.now(),s.lastInterval,s.interval,s.lastInterval=performance.now(),!e)return;const{fps:t,jank:i}=this.metrics,n=t>55?"#4CAF50":t>45?"#FFC107":"#F44336",r="\n font-size: 10px;\n padding:2px 8px;\n border-radius:12px;\n ";e.innerHTML=`\n <div style="margin-bottom: 8px;">\n <div>FPS: <span style="color:${n}">${t}</span></div>\n </div>\n <div>\n <div style="display: flex; gap: 6px; margin-bottom: 8px;">\n <span style="background:#FF9800; ${r}">small: ${i.small}</span>\n <span style="background:#FF5722; ${r}">middle: ${i.medium}</span>\n <span style="background:#D32F2F; ${r}">large: ${i.large}</span>\n </div>\n </div>\n `}destroy(){this.panel&&document.body.contains(this.panel)&&document.body.removeChild(this.panel)}}const n={small:0,medium:0,large:0};class r extends t{constructor(e={}){super(),this.frameTimes=[],this.jankCounts=n,this.performanceObserver=null,this.updateTimer=null,this.eventEntries=[],this.rafId=null,this.frameTimes=[],this.jankCounts=n,this.deviceRefreshRate=e.deviceRefreshRate||60,this.frame=e.frame||1e3/this.deviceRefreshRate||16.67,this.minJankThreshold=e.minJankThreshold||50,this.largeJankThreshold=e.largeJankThreshold||100,this.updateInterval=e.updateInterval||1e3}startMonitoring(){"undefined"!=typeof PerformanceObserver&&PerformanceObserver?.supportedEntryTypes?.includes?.("event")?(this.startEventTimingMonitor(),console.log("PerformanceObserver 初始化成功, start monitor"),this.startFrameRateMonitor()):(console.warn("PerformanceObserver 初始化失败,回退到传统监测"),this.startLegacyMonitor())}startEventTimingMonitor(){try{this.performanceObserver=new PerformanceObserver(e=>{const t=e.getEntries();this.eventEntries.push(...t),t.forEach(e=>{const t=e.processingStart-e.startTime,s=e.processingEnd-e.processingStart;t>this.minJankThreshold&&this.jankCounts.small++,t>this.largeJankThreshold&&this.jankCounts.large++,s>this.minJankThreshold&&s<=this.largeJankThreshold&&this.jankCounts.medium++})}),this.performanceObserver.observe({type:"event",buffered:!0}),this.updateTimer=window.setInterval(()=>{this.updateMetrics()},this.updateInterval)}catch(e){console.warn("PerformanceObserver初始化失败,回退到传统监测:",e),this.startLegacyMonitor()}}startFrameRateMonitor(){let e=performance.now();const t=s=>{const i=s-e;this.frameTimes.push(i),this.frameTimes.length>60&&this.frameTimes.shift(),e=s,this.rafId=requestAnimationFrame(t)};this.rafId=requestAnimationFrame(t)}startLegacyMonitor(){let e=performance.now(),t=0;const s=i=>{const n=i-e;this.frameTimes.push(n),this.frameTimes.length>60&&this.frameTimes.shift();((e,t)=>{const s=t?.slice(-3)?.reduce?.((e,t)=>e+t,0)/3;return e>Math.max(83,2*s)})(n,this.frameTimes.slice(-3))&&this.jankCounts.small++,n>125&&this.jankCounts.large++,i-t>this.updateInterval&&(this.updateMetrics(),t=i),e=i,this.rafId=requestAnimationFrame(s)};this.rafId=requestAnimationFrame(s)}updateMetrics(){const e=this.frameTimes.length>0?Math.round(1e3/(this.frameTimes.reduce((e,t)=>e+t,0)/this.frameTimes.length)):this.deviceRefreshRate;let t=0;this.frameTimes.forEach(e=>{const s=e-this.frame;t+=s>0?s:0});const s=this.frameTimes.length>0?t/(this.frameTimes.length*this.frame):0,i=this.frameTimes.length>0?this.frameTimes?.filter(e=>e>100)?.length/this.frameTimes.length:0;let r=0,o=0;if(this.eventEntries.length>0){const e=this.eventEntries.map(e=>e.processingStart-e.startTime);r=e.reduce((e,t)=>e+t,0)/e.length,o=Math.max(...e),this.eventEntries=[]}const a={fps:e,jank:{small:this.jankCounts.small,medium:this.jankCounts.medium,large:this.jankCounts.large,stutterRate:s,severeJankRate:i},frameTimes:[...this.frameTimes],eventTiming:{avgDelay:r,maxDelay:o}};this.onUpdate&&this.onUpdate(a),this.postToWorker({type:"calculateJank",data:{fps:e,jankCounts:this.jankCounts,stutterRate:s,severeJankRate:i,eventTiming:{avgDelay:r,maxDelay:o}}}),this.jankCounts={...n}}stopMonitoring(){this.performanceObserver&&(this.performanceObserver.disconnect(),this.performanceObserver=null),null!==this.updateTimer&&(window.clearInterval(this.updateTimer),this.updateTimer=null),null!==this.rafId&&(cancelAnimationFrame(this.rafId),this.rafId=null),this.frameTimes=[],this.jankCounts=n,this.eventEntries=[]}}exports.PanelJank=i,exports.PanelJankPerformance=class{constructor(){this.isExpanded=!0,this.isDragging=!1,this.dragOffset={x:0,y:0},this.metrics={fps:0,jank:{small:0,medium:0,large:0,stutterRate:0,severeJankRate:0},frameTimes:[]},this.createPanel(),this.enableDrag()}createPanel(){this.panel=document.createElement("div"),this.panel.style.cssText="\n position: fixed;\n top: 20px;\n left: 20px;\n background: rgba(0,0,0,0.7);\n color: white;\n border-radius: 8px;\n font-family: monospace;\n z-index: 10000;\n backdrop-filter: blur(4px);\n box-shadow: 0 4px 12px rgba(0,0,0,0.2);\n user-select: none;\n ";const e=document.createElement("div");e.style.cssText="\n padding: 12px;\n cursor: move;\n display: flex;\n justify-content: space-between;\n align-items: center;\n border-bottom: 1px solid rgba(255,255,255,0.2);\n ";const t=document.createElement("div");t.textContent="📊 Performance Metrics";const s=document.createElement("div");s.textContent=this.isExpanded?"−":"+",s.style.cursor="pointer",s.style.fontSize="18px",e.appendChild(t),e.appendChild(s),this.panel.appendChild(e),this.content=document.createElement("div"),this.content.id="metrics-content",this.content.style.padding="12px",this.panel.appendChild(this.content),document.body.appendChild(this.panel),s.addEventListener("click",()=>{this.isExpanded=!this.isExpanded,s.textContent=this.isExpanded?"−":"+",this.content.style.display=this.isExpanded?"block":"none"})}enableDrag(){const e=this.panel.querySelector("div");e?.addEventListener("mousedown",e=>{this.isDragging=!0,this.dragOffset.x=e.clientX-this.panel.offsetLeft,this.dragOffset.y=e.clientY-this.panel.offsetTop}),document.addEventListener("mousemove",e=>{this.isDragging&&(this.panel.style.left=e.clientX-this.dragOffset.x+"px",this.panel.style.top=e.clientY-this.dragOffset.y+"px")}),document.addEventListener("mouseup",()=>this.isDragging=!1)}updateMetrics(e){this.metrics={...e,fps:0,frameTimes:this.metrics.frameTimes?.slice?.(-60),jank:{small:e.small,medium:e.medium,large:e.large,stutterRate:e.stutterRate,severeJankRate:e.severeJankRate}},this.render()}render(){if(!this.isExpanded)return;const{fps:e,jank:t,frameTimes:s}=this.metrics,i=e>55?"#00ff00":e>45?"#ffff00":"#ff0000";this.content.innerHTML=`\n <div style="margin-bottom: 10px;">\n <div>FPS: <span style="color:${i}">${e}</span></div>\n <div style="font-size: 12px; color: #aaa;">${s.length} frames</div>\n </div>\n\n <div style="margin-bottom: 10px;">\n <div style="display: flex; gap: 8px; margin-bottom: 8px;">\n <span style="background:#ffcc00; color:black; padding:2px 8px; border-radius:12px;">小卡顿: ${t.small}</span>\n <span style="background:#ff9900; color:black; padding:2px 8px; border-radius:12px;">中卡顿: ${t.medium}</span>\n <span style="background:#ff3300; color:black; padding:2px 8px; border-radius:12px;">大卡顿: ${t.large}</span>\n </div>\n <div>卡顿率: ${(100*t.stutterRate).toFixed(1)}%</div>\n </div>\n\n <div style="height: 60px; margin-top: 10px;">\n <canvas id="frame-time-chart" width="200" height="60"></canvas>\n </div>\n `,this.drawFrameTimeChart()}drawFrameTimeChart(){const e=this.content.querySelector("#frame-time-chart"),t=e.getContext("2d"),{width:s,height:i}=e;t.clearRect(0,0,s,i),t.strokeStyle="rgba(255,255,255,0.3)",t.beginPath();const n=.833*i;t.moveTo(0,n),t.lineTo(s,n),t.stroke(),t.strokeStyle="#4dabf7",t.beginPath(),this.metrics.frameTimes?.forEach?.((e,n)=>{const r=n/this.metrics.frameTimes?.length*s,o=i*(1-Math.min(e,100)/100);0===n?t.moveTo(r,o):t.lineTo(r,o)}),t.stroke()}},exports.PerformanceBase=t,exports.PerformanceJankStutter=r,exports.PerformanceMonitor=class{constructor(e={}){this.isStarted=!1,this.isReported=!1,this.isStopped=!1,this.observers=[],this.reportTimer=null,this.lcpEntries=[],this.inpEntries=[],this.clsValue=0;const t={FCP:2e3,LCP:2500,TTI:5e3,FID:100,INP:200,CLS:.1},s=500;this.config={warnings:{...t,...e.warnings},reportUrl:e.reportUrl||"",appId:e.appId||"WEB_APP",debug:e.debug||!1,isDev:e.isDev||!1,maxTime:e.maxTime||s,deviceType:e.deviceType||"auto",networkType:e.networkType||"unknown",pageInfo:e.pageInfo||{pageUrl:window.location.href,pageTitle:document.title,routeId:"initial"}},this.metrics={DNS:0,TCP:0,SSL:0,TTFB:0,FCP:0,FMP:0,DCL:0,LCP:0,TTI:0,FID:0,INP:0,CLS:0,networkType:"unknown",deviceType:"desktop"}}detectDeviceType(){return"auto"!==this.config.deviceType?this.config.deviceType:/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)?"mobile":"desktop"}detectNetworkType(){const e=navigator?.connection;if(!e)return"unknown";return["slow-2g","2g","3g","4g","5g"].includes(e.effectiveType)?e.effectiveType:e.type||"unknown"}getDeviceMemory(){return navigator?.deviceMemory}getCPUCores(){return navigator?.hardwareConcurrency}initPerfTiming(){if(!("timing"in performance))return;const e=performance.timing;this.metrics={...this.metrics,DNS:e.domainLookupEnd-e.domainLookupStart,TCP:e.connectEnd-e.connectStart,SSL:e.secureConnectionStart?e.connectEnd-e.secureConnectionStart:0,TTFB:e.responseStart-e.requestStart,DCL:e.domContentLoadedEventStart-e.navigationStart}}logDebug(e){this.config.debug&&console.debug(`[PerformanceMonitor] ${e}`)}start(){if(this.isStarted)return void this.logDebug("Monitoring already started");this.isStarted=!0,this.logDebug("Performance monitoring started"),this.metrics.deviceType=this.detectDeviceType(),this.metrics.networkType=this.detectNetworkType(),this.metrics.memory=this.getDeviceMemory(),this.metrics.cpuCores=this.getCPUCores(),this.initPerfTiming();const e=new PerformanceObserver(t=>{const s=t.getEntriesByName("first-contentful-paint");s.length>0&&(this.metrics.FCP=Math.round(s[0]?.startTime),e.disconnect(),this.logDebug(`FCP: ${this.metrics.FCP}ms`))});e.observe({type:"paint",buffered:!0}),this.observers.push(e);const t=new PerformanceObserver(e=>{const t=e.getEntries(),s=t?.[t?.length-1];s.element&&"DIV"===s.element.tagName&&s?.element?.classList?.contains("ad-container")||(this.lcpEntries.push(s),this.metrics.LCP=Math.round(s?.startTime),s?.element&&(this.metrics.LCPElement=`${s?.element?.tagName}.${Array.from(s?.element?.classList).join(".")}`))});t.observe({type:"largest-contentful-paint",buffered:!0}),this.observers.push(t);const s=new PerformanceObserver(e=>{e?.getEntries()?.forEach?.(e=>{if(!this.metrics.FID){["button","a","input"].some(t=>e?.target?.tagName===t.toUpperCase())&&(this.metrics.FID="duration"in e?Math.round(e.duration):Math.round(e?.processingStart-e?.startTime),this.logDebug(`FID: ${this.metrics.FID}ms`))}})});s.observe({type:"first-input",buffered:!0}),this.observers.push(s);const i=new PerformanceObserver(e=>{e?.getEntries()?.forEach?.(e=>{e.duration>0&&e?.interactionId&&(this.inpEntries.push(e),this.metrics.INP=Math.max(this.metrics.INP,Math.round(e.duration)))})});i.observe({type:"event",buffered:!0}),this.observers.push(i);const n=new PerformanceObserver(e=>{e?.getEntries()?.forEach?.(e=>{const t=e;"layout-shift"!==t.entryType||t.hadRecentInput||(this.clsValue+=t.value,this.metrics.CLS=parseFloat(this.clsValue.toFixed(4)))})});n.observe({type:"layout-shift",buffered:!0}),this.observers.push(n);const r=()=>{const e=performance.getEntriesByType("resource").filter(e=>!e?.responseEnd||0===e?.responseEnd),t=performance.getEntriesByType("longtask"),s=t.length>0?Math.max(...t.map(e=>e.startTime+e.duration)):0,i=performance.now(),n=i-Math.max(this.metrics.LCP||0,s),o=this.metrics.networkType?.includes?.("2g")?7e3:5e3;0===e.length&&n>o&&this.metrics.FCP&&this.metrics.FCP>0?(this.metrics.TTI=Math.round(i),this.logDebug(`TTI: ${this.metrics.TTI}ms`),this.ttiResolve&&this.ttiResolve()):setTimeout(r,500)};if(this.metrics.FCP)r();else{const e=setInterval(()=>{this.metrics.FCP&&(clearInterval(e),r())},100)}this.reportTimer=setTimeout(()=>{this.isReported||this.report()},this.config.maxTime),window.addEventListener("pagehide",()=>this.report()),window.addEventListener("beforeunload",()=>this.report()),window?.__isSpa&&window.addEventListener("routeChange",()=>{this.report(),this.resetForNextRoute()})}resetForNextRoute(){this.isReported=!1,this.lcpEntries=[],this.inpEntries=[],this.clsValue=0,this.metrics={...this.metrics,FCP:0,LCP:0,TTI:0,FID:0,INP:0,CLS:0},this.start()}report(){if(this.isReported||!this.isStarted)return;if(this.isReported=!0,this.dispose(),this.lcpEntries.length>0){const e=[...this.lcpEntries]?.sort((e,t)=>t.startTime-e.startTime)?.[0];this.metrics.LCP=Math.round(e.startTime)}if(this.inpEntries.length>0){const e=this.inpEntries?.map(e=>e.duration)?.sort?.((e,t)=>e-t),t=Math.floor(.98*this.inpEntries.length);this.metrics.INP=Math.round(e[t])}const e={appId:this.config.appId,timestamp:(new Date).toISOString(),metrics:this.metrics,userAgent:navigator.userAgent,pageInfo:this.config.pageInfo,warnings:this.checkWarnings(),environment:{networkType:this.metrics.networkType,deviceType:this.metrics.deviceType,memory:this.metrics.memory,cpuCores:this.metrics.cpuCores}};this.config.isDev&&this.renderReport(e),this.config.reportUrl&&this.sendReport(e)}checkWarnings(){const e=[],t=this.metrics,s=this.config.warnings,i="mobile"===this.metrics.deviceType?1.3:1;return t.FCP&&t.FCP>Number(s?.FCP)*i&&e.push(`FCP ${t.FCP}ms > ${Number(s?.FCP)*i}ms`),t.LCP&&t.LCP>Number(s?.LCP)*i&&e.push(`LCP ${t.LCP}ms > ${Number(s?.LCP)*i}ms`),t.TTI&&t.TTI>Number(s?.TTI)*i&&e.push(`TTI ${t.TTI}ms > ${Number(s?.TTI)*i}ms`),t.FID&&t.FID>Number(s?.FID)*i&&e.push(`FID ${t.FID}ms > ${Number(s?.FID)*i}ms`),t.INP&&t.INP>Number(s?.INP)*i&&e.push(`INP ${t.INP}ms > ${Number(s?.INP)*i}ms`),t.CLS&&t.CLS>Number(s?.CLS)&&e.push(`CLS ${t.CLS} > ${s?.CLS}`),e}renderReport(e){const t="performance-monitor-container";let s=document.getElementById(t);s||(s=document.createElement("div"),s.id=t,s.style.cssText="\n position: fixed;\n bottom: 0;\n left: 0;\n right: 0;\n background: rgba(255, 255, 255, 0.95);\n border-top: 1px solid #ddd;\n padding: 12px;\n z-index: 9999;\n max-height: 50vh;\n overflow-y: auto;\n box-shadow: 0 -2px 10px rgba(0,0,0,0.1);\n font-size: 14px;\n ",document.body.appendChild(s));const i="mobile"===this.metrics.deviceType;let n='<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:10px;">\n <h3 style="margin:0;color:#2c3e50;font-size:16px;">性能报告</h3>\n <button style="background:none;border:none;cursor:pointer;font-size:20px;"\n onclick="this.parentElement.parentElement.remove()">×</button>\n </div>';n+=`<div style="${i?"display:block;":"display:grid;grid-template-columns:repeat(auto-fill,minmax(140px,1fr));gap:8px;"}">`;for(const[t,s]of Object.entries(e.metrics))if("number"==typeof s&&s>=0){const e=this.checkWarnings()?.some?.(e=>e.includes(t));n+=`<div style="margin-bottom:6px;">\n <strong>${t}:</strong>\n <span style="${e?"color:#e74c3c;font-weight:bold;":""}">\n ${"CLS"===t?s.toFixed(4):s+"ms"}\n </span>\n </div>`}n+="</div>",n+=`<div style="margin-top:10px;font-size:13px;color:#666;">\n <div><strong>设备:</strong> ${e.environment?.deviceType}</div>\n <div><strong>网络:</strong> ${e.environment?.networkType}</div>\n ${e.environment?.memory?`<div><strong>内存:</strong> ${e.environment?.memory}GB</div>`:""}\n ${e.environment?.cpuCores?`<div><strong>CPU核心:</strong> ${e.environment?.cpuCores}</div>`:""}\n </div>`,e.warnings?.length>0&&(n+='<div style="margin-top:12px;padding:8px;background:#fff8f8;border-radius:4px;border-left:3px solid #e74c3c;">',n+='<div style="font-weight:bold;color:#e74c3c;margin-bottom:6px;">性能警告:</div>',e.warnings?.forEach?.(e=>n+=`<div style="margin-bottom:4px;">${e}</div>`),n+="</div>"),s.innerHTML=n}sendReport(e){if(navigator.sendBeacon){const t=new Blob([JSON.stringify(e)],{type:"application/json"});navigator.sendBeacon(this.config.reportUrl,t)}else fetch(this.config.reportUrl,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e),keepalive:!0}).catch(e=>this.logDebug(`Report failed: ${e}`))}dispose(){this.isStopped||(this.isStopped=!0,this.logDebug("Disposing performance monitors"),this.observers.forEach(e=>e.disconnect()),this.observers=[],this.reportTimer&&(clearTimeout(this.reportTimer),this.reportTimer=null))}},exports.RadarReport=class{constructor(e){this.queue=[],this.sending=!1,this.retryQueue=[],this.retryAttempts=new Map,this.maxRetries=3,this.maxQueueSize=1e3,this.maxRetryQueueSize=500,this.batchSize=5,this.processingTimeout=null,this.STORAGE_KEY="radar_offline_data",this.reportUrl="/api/radar",e&&(this.reportUrl=e.reportUrl||this.reportUrl,this.maxRetries=e.maxRetries||this.maxRetries,this.maxQueueSize=e.maxQueueSize||this.maxQueueSize,this.maxRetryQueueSize=e.maxRetryQueueSize||this.maxRetryQueueSize,this.batchSize=e.batchSize||this.batchSize),this.recoverOfflineData(),"undefined"!=typeof window&&window.addEventListener("unload",()=>this.persistOfflineData())}report(e,t=0){const s={...e,__priority:t};if(this.queue.length>=this.maxQueueSize){const t=this.findLowestPriorityItemIndex();if(!(t>=0&&e.__priority>this.queue[t].__priority))return void console.warn("Report queue full, discarding new data due to lower priority");this.queue.splice(t,1),this.queue.push(s)}else this.queue.push(s);this.scheduleQueueProcessing()}scheduleQueueProcessing(){this.sending||null===this.processingTimeout&&(this.processingTimeout=window.setTimeout(()=>{this.processingTimeout=null,this.processQueue()},0))}async processQueue(){if(!this.sending){this.sending=!0;try{await this.processQueueChunk(this.retryQueue,!0),await this.processQueueChunk(this.queue,!1)}catch(e){console.error("Error processing queue:",e)}finally{this.sending=!1,(this.queue.length>0||this.retryQueue.length>0)&&this.scheduleQueueProcessing()}}}async processQueueChunk(e,t){const s=Math.min(this.batchSize,e.length);if(0===s)return;const i=e.splice(0,s);await this.sendBatch(i,t)}async sendBatch(e,t=!1){if(0===e.length)return;const s=this.generateUniqueId();try{const i=this.reportUrl,n=JSON.stringify(e),r=n.length>6e4;if(!navigator.sendBeacon||r||"hidden"!==document.visibilityState&&!document.hidden){const e=new AbortController,t=setTimeout(()=>e.abort(),1e4);try{const s=await fetch(i,{method:"POST",headers:{"Content-Type":"application/json"},body:n,keepalive:!0,signal:e.signal});if(clearTimeout(t),!s.ok)throw new Error(`HTTP error ${s.status}: ${s.statusText}`)}catch(e){throw clearTimeout(t),e}}else{const e=new Blob([n],{type:"application/json"});if(!navigator.sendBeacon(i,e))throw new Error("Beacon failed")}t&&this.retryAttempts.delete(s)}catch(t){this.handleSendError(e,s,t)}}handleSendError(e,t,s){const i=this.retryAttempts.get(t)||0;if(i<this.maxRetries){const n=Math.min(1e3*Math.pow(2,i),3e4);console.warn(`Send failed, retrying (${i+1}/${this.maxRetries}) in ${n}ms: ${s.message}`),this.retryAttempts.set(t,i+1),this.retryQueue.length>=this.maxRetryQueueSize&&this.retryQueue.splice(0,e.length),setTimeout(()=>{this.retryQueue.push(...e),this.scheduleQueueProcessing()},n)}else console.error(`Permanent send failure: ${s.message}`,e),this.storeOfflineData(e),this.retryAttempts.delete(t)}storeOfflineData(e){try{const t=this.getOfflineData(),s=200;t.length>s&&t.splice(0,t.length-s+e.length),t.push(...e),localStorage.setItem(this.STORAGE_KEY,JSON.stringify(t))}catch(e){console.error("Failed to store offline data:",e)}}getOfflineData(){try{const e=localStorage.getItem(this.STORAGE_KEY);return e?JSON.parse(e):[]}catch(e){return console.error("Failed to retrieve offline data:",e),[]}}recoverOfflineData(){try{const e=this.getOfflineData();if(e.length>0){localStorage.removeItem(this.STORAGE_KEY);const t=e.map(e=>({...e,__priority:999})),s=t.slice(-Math.min(t.length,this.maxRetryQueueSize));this.retryQueue.push(...s),console.log(`Recovered ${s.length} offline data items`),this.scheduleQueueProcessing()}}catch(e){console.error("Failed to recover offline data:",e)}}persistOfflineData(){if(0!==this.queue.length||0!==this.retryQueue.length)try{const e=[...this.queue,...this.retryQueue],t=[...this.getOfflineData(),...e],s=500,i=t.slice(-s);localStorage.setItem(this.STORAGE_KEY,JSON.stringify(i))}catch(e){console.error("Failed to persist offline data:",e)}}findLowestPriorityItemIndex(){if(0===this.queue.length)return-1;let e=Number.MAX_SAFE_INTEGER,t=-1;for(let s=0;s<this.queue.length;s++){const i=this.queue[s].__priority||0;i<e&&(e=i,t=s)}return t}generateUniqueId(){if("undefined"!=typeof crypto&&crypto.randomUUID)return crypto.randomUUID();return`${Date.now().toString(36)}-${Math.random().toString(36).substring(2,10)}-${this.queue.length}`}getQueueStats(){return{mainQueue:this.queue.length,retryQueue:this.retryQueue.length,activeSending:this.sending,offlineData:this.getOfflineDataCount(),configuration:{batchSize:this.batchSize,maxQueueSize:this.maxQueueSize,maxRetryQueueSize:this.maxRetryQueueSize,maxRetries:this.maxRetries}}}getOfflineDataCount(){try{const e=localStorage.getItem(this.STORAGE_KEY);return e?JSON.parse(e).length:0}catch(e){return 0}}async flush(){this.sending=!1,await this.processQueue()}clear(){this.queue=[],this.retryQueue=[],this.retryAttempts.clear(),localStorage.removeItem(this.STORAGE_KEY)}},exports.initPanelJank=function(){const e=new i;return window.addEventListener("beforeunload",()=>e.destroy()),e},exports.useJankStutter=function(t={},s={}){const n=new r(t),o=new i(s);return e.useEffect(()=>(n.onUpdate=e=>{o.updateMetrics(e)},n.startMonitoring(),()=>{n.stopMonitoring(),o.destroy()}),[]),{panelJank:o}};
//# sourceMappingURL=index.cjs.map