@senzops/web 1.3.3 → 1.3.4

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.
package/dist/index.d.mts CHANGED
@@ -42,6 +42,7 @@ declare class SenzorRumAgent {
42
42
  private clickHistory;
43
43
  private flushInterval;
44
44
  private readonly MAX_BATCH_SIZE;
45
+ private errorEngine;
45
46
  init(config: RumConfig): void;
46
47
  private manageSession;
47
48
  private startNewTrace;
@@ -51,7 +52,6 @@ declare class SenzorRumAgent {
51
52
  private getNavigationTimings;
52
53
  private shouldAttachTraceHeader;
53
54
  private patchNetwork;
54
- private setupErrorListeners;
55
55
  private setupRoutingListeners;
56
56
  private flush;
57
57
  }
package/dist/index.d.ts CHANGED
@@ -42,6 +42,7 @@ declare class SenzorRumAgent {
42
42
  private clickHistory;
43
43
  private flushInterval;
44
44
  private readonly MAX_BATCH_SIZE;
45
+ private errorEngine;
45
46
  init(config: RumConfig): void;
46
47
  private manageSession;
47
48
  private startNewTrace;
@@ -51,7 +52,6 @@ declare class SenzorRumAgent {
51
52
  private getNavigationTimings;
52
53
  private shouldAttachTraceHeader;
53
54
  private patchNetwork;
54
- private setupErrorListeners;
55
55
  private setupRoutingListeners;
56
56
  private flush;
57
57
  }
@@ -1 +1,2 @@
1
- (()=>{function v(){return typeof crypto<"u"&&crypto.randomUUID?crypto.randomUUID():"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,s=>{let e=Math.random()*16|0;return(s==="x"?e:e&3|8).toString(16)})}function x(s){let e="";for(;e.length<s;)e+=Math.random().toString(16).slice(2);return e.slice(0,s)}var H=()=>{var s;return{userAgent:navigator.userAgent,url:window.location.href,deviceMemory:navigator.deviceMemory||void 0,connectionType:((s=navigator.connection)==null?void 0:s.effectiveType)||void 0}},_=s=>{if(!s)return 0;if(typeof s=="string")return s.length;if(s instanceof Blob||s instanceof File)return s.size;if(s instanceof ArrayBuffer)return s.byteLength},R=s=>{let e={};return s&&(s instanceof Headers?s.forEach((t,i)=>e[i]=t):Array.isArray(s)?s.forEach(([t,i])=>e[t]=i):typeof s=="object"&&Object.assign(e,s)),e};var I=class{config={webId:""};startTime=Date.now();endpoint="https://api.senzor.dev/api/ingest/web";initialized=!1;init(e){if(!this.initialized){if(this.initialized=!0,this.config={...this.config,...e},e.endpoint&&(this.endpoint=e.endpoint),!this.config.webId){console.error("[Senzor] webId is required for Analytics.");return}this.manageSession(),this.trackPageView(),this.setupListeners()}}normalizeUrl(e){return e?e.replace(/^https?:\/\//,"").replace(/^www\./,""):""}manageSession(){let e=Date.now(),t=parseInt(localStorage.getItem("senzor_last_activity")||"0",10),i=1800*1e3;localStorage.getItem("senzor_vid")||localStorage.setItem("senzor_vid",v());let a=sessionStorage.getItem("senzor_sid"),l=e-t>i;!a||l?(a=v(),sessionStorage.setItem("senzor_sid",a),this.determineReferrer(!0)):this.determineReferrer(!1),localStorage.setItem("senzor_last_activity",e.toString())}determineReferrer(e){let t=document.referrer,i=window.location.hostname,a=sessionStorage.getItem("senzor_ref"),l=!1;if(t)try{new URL(t).hostname!==i&&(l=!0)}catch{l=!0}if(l){let n=this.normalizeUrl(t);n!==a&&sessionStorage.setItem("senzor_ref",n)}else e&&!a&&sessionStorage.setItem("senzor_ref","Direct")}getIds(){return localStorage.setItem("senzor_last_activity",Date.now().toString()),{visitorId:localStorage.getItem("senzor_vid")||"unknown",sessionId:sessionStorage.getItem("senzor_sid")||"unknown",referrer:sessionStorage.getItem("senzor_ref")||"Direct"}}trackPageView(){this.manageSession(),this.startTime=Date.now(),this.send({type:"pageview",webId:this.config.webId,...this.getIds(),url:window.location.href,path:window.location.pathname,title:document.title,width:window.innerWidth,timezone:Intl.DateTimeFormat().resolvedOptions().timeZone,referrer:this.getIds().referrer})}trackPing(){let e=Math.floor((Date.now()-this.startTime)/1e3);e>=1&&this.send({type:"ping",webId:this.config.webId,...this.getIds(),url:window.location.href,path:window.location.pathname,title:document.title,width:window.innerWidth,timezone:Intl.DateTimeFormat().resolvedOptions().timeZone,referrer:this.getIds().referrer,duration:e})}send(e){navigator.sendBeacon?navigator.sendBeacon(this.endpoint,new Blob([JSON.stringify(e)],{type:"application/json"}))||this.fallbackSend(e):this.fallbackSend(e)}fallbackSend(e){fetch(this.endpoint,{method:"POST",body:JSON.stringify(e),keepalive:!0,headers:{"Content-Type":"application/json"}}).catch(()=>{})}setupListeners(){let e=history.pushState;history.pushState=(...t)=>{this.trackPing(),e.apply(history,t),this.trackPageView()},window.addEventListener("popstate",()=>{this.trackPing(),this.trackPageView()}),document.addEventListener("visibilitychange",()=>{document.visibilityState==="hidden"?this.trackPing():(this.startTime=Date.now(),this.manageSession())}),window.addEventListener("beforeunload",()=>this.trackPing())}};var T=class{config={apiKey:"",sampleRate:1,allowedOrigins:[]};endpoint="https://api.senzor.dev/api/ingest/rum";initialized=!1;isSampled=!0;sessionId="";traceId="";traceStartTime=0;isInitialLoad=!0;spanQueue=[];errorQueue=[];vitals={};breadcrumbs=[];frustrations={rageClicks:0,deadClicks:0,errorCount:0};clickHistory=[];flushInterval;MAX_BATCH_SIZE=50;init(e){if(!this.initialized){if(this.initialized=!0,this.config={...this.config,...e},e.endpoint&&(this.endpoint=e.endpoint),!this.config.apiKey){console.error("[Senzor RUM] apiKey is required.");return}this.isSampled=Math.random()<=(this.config.sampleRate??1),this.manageSession(),this.startNewTrace(!0),this.setupErrorListeners(),this.setupPerformanceObservers(),this.setupUXListeners(),this.isSampled&&this.patchNetwork(),this.flushInterval=setInterval(()=>this.flush(),5e3),this.setupRoutingListeners()}}manageSession(){sessionStorage.getItem("sz_rum_sid")||sessionStorage.setItem("sz_rum_sid",v()),this.sessionId=sessionStorage.getItem("sz_rum_sid")}startNewTrace(e){this.traceId=x(32),this.traceStartTime=Date.now(),this.isInitialLoad=e,this.vitals={},this.frustrations={rageClicks:0,deadClicks:0,errorCount:0}}addBreadcrumb(e,t,i){this.breadcrumbs.push({type:e,message:t,data:i,time:Date.now()}),this.breadcrumbs.length>20&&this.breadcrumbs.shift()}setupUXListeners(){document.addEventListener("click",e=>{let t=e.target,i=t.tagName?t.tagName.toLowerCase():"";this.addBreadcrumb("click",`Clicked ${i}${t.id?"#"+t.id:""}${t.className?"."+t.className.split(" ")[0]:""}`),["a","button","input","select","textarea","label"].includes(i)||t.closest("button")||t.closest("a")||t.hasAttribute("role")||t.onclick||this.frustrations.deadClicks++;let n=Date.now();if(this.clickHistory.push({x:e.clientX,y:e.clientY,time:n}),this.clickHistory=this.clickHistory.filter(r=>n-r.time<1e3),this.clickHistory.length>=3){let r=this.clickHistory[0],o=!0;for(let p=1;p<this.clickHistory.length;p++){let u=Math.abs(this.clickHistory[p].x-r.x),h=Math.abs(this.clickHistory[p].y-r.y);(u>50||h>50)&&(o=!1)}o&&(this.frustrations.rageClicks++,this.addBreadcrumb("frustration","Rage Click Detected"),this.clickHistory=[])}},{capture:!0,passive:!0})}setupPerformanceObservers(){if(!(!this.isSampled||typeof PerformanceObserver>"u"))try{new PerformanceObserver(t=>{for(let i of t.getEntriesByName("first-contentful-paint"))this.vitals.fcp=i.startTime}).observe({type:"paint",buffered:!0}),new PerformanceObserver(t=>{let i=t.getEntries(),a=i[i.length-1];a&&(this.vitals.lcp=a.startTime)}).observe({type:"largest-contentful-paint",buffered:!0});let e=0;new PerformanceObserver(t=>{for(let i of t.getEntries())i.hadRecentInput||(e+=i.value,this.vitals.cls=e)}).observe({type:"layout-shift",buffered:!0}),new PerformanceObserver(t=>{for(let i of t.getEntries()){let a=i,l=a.duration||(a.processingStart&&a.startTime?a.processingStart-a.startTime:0);(!this.vitals.inp||l>this.vitals.inp)&&(this.vitals.inp=l)}}).observe({type:"event",buffered:!0,durationThreshold:40})}catch{}}getNavigationTimings(){if(typeof performance>"u")return{};let e=performance.getEntriesByType("navigation")[0];return e?{dns:Math.max(0,e.domainLookupEnd-e.domainLookupStart),tcp:Math.max(0,e.connectEnd-e.connectStart),ssl:e.secureConnectionStart?Math.max(0,e.requestStart-e.secureConnectionStart):0,ttfb:Math.max(0,e.responseStart-e.requestStart),domInteractive:Math.max(0,e.domInteractive-e.startTime),domComplete:Math.max(0,e.domComplete-e.startTime)}:{}}shouldAttachTraceHeader(e){if(!this.config.allowedOrigins||this.config.allowedOrigins.length===0)return!1;try{let t=new URL(e,window.location.origin);return this.config.allowedOrigins.some(i=>typeof i=="string"?t.origin.includes(i):i instanceof RegExp?i.test(t.origin):!1)}catch{return!1}}patchNetwork(){let e=this,t=XMLHttpRequest.prototype.open,i=XMLHttpRequest.prototype.send,a=XMLHttpRequest.prototype.setRequestHeader;XMLHttpRequest.prototype.open=function(n,r,...o){return this.__szMethod=n.toUpperCase(),this.__szUrl=r,this.__szHeaders={},t.apply(this,[n,r,...o])},XMLHttpRequest.prototype.setRequestHeader=function(n,r){return this.__szHeaders||(this.__szHeaders={}),this.__szHeaders[n]=r,a.apply(this,[n,r])},XMLHttpRequest.prototype.send=function(n){let r=this,o=x(16),p=Date.now()-e.traceStartTime,u=r.__szMethod,h=r.__szUrl;try{h=new URL(r.__szUrl,window.location.origin).toString()}catch{}return e.shouldAttachTraceHeader(h)&&r.setRequestHeader("traceparent",`00-${e.traceId}-${o}-01`),r.addEventListener("loadend",()=>{var w;let g=Date.now()-e.traceStartTime-p,m={};try{m=r.getAllResponseHeaders().trim().split(/[\r\n]+/).reduce((d,S)=>{let b=S.split(": "),f=b.shift(),z=b.join(": ");return f&&(d[f]=z),d},{})}catch{}let y={url:h,method:u,library:"xhr",status:r.status,responseType:r.responseType,requestPayloadSize:_(n),requestHeaders:r.__szHeaders,responseHeaders:m};try{(r.responseType===""||r.responseType==="text")&&(y.responsePayloadSize=(w=r.responseText)==null?void 0:w.length)}catch{}e.spanQueue.push({spanId:o,name:`${u} ${new URL(h,window.location.origin).pathname}`,type:"http",startTime:p,duration:g,status:r.status,meta:y}),e.spanQueue.length>=e.MAX_BATCH_SIZE&&e.flush()}),i.call(this,n)};let l=window.fetch;window.fetch=async function(...n){let r=n[0],o=n[1],p="",u="GET";typeof r=="string"||r instanceof URL?(p=r.toString(),u=((o==null?void 0:o.method)||"GET").toUpperCase()):r instanceof Request&&(p=r.url,u=r.method.toUpperCase());let h=p;try{h=new URL(p,window.location.origin).toString()}catch{}let g=x(16),m=Date.now()-e.traceStartTime,y=R((o==null?void 0:o.headers)||(r instanceof Request?r.headers:{}));if(e.shouldAttachTraceHeader(h)){let c=`00-${e.traceId}-${g}-01`;if(r instanceof Request){let d=new Headers(r.headers);d.set("traceparent",c),n[1]={...o||{},headers:d}}else{let d=new Headers((o==null?void 0:o.headers)||{});d.set("traceparent",c),n[1]={...o||{},headers:d}}y.traceparent=c}let w=(c,d,S)=>{let b=Date.now()-e.traceStartTime-m,f={url:h,method:u,library:"fetch",status:c,requestPayloadSize:_(o==null?void 0:o.body),requestHeaders:y};d&&(f.statusText=d.statusText,f.type=d.type,f.redirected=d.redirected,f.responseHeaders=R(d.headers)),S&&(f.error=S),e.spanQueue.push({spanId:g,name:`${u} ${new URL(h,window.location.origin).pathname}`,type:"http",startTime:m,duration:b,status:c,meta:f}),e.spanQueue.length>=e.MAX_BATCH_SIZE&&e.flush()};try{let c=await l.apply(this,n);return w(c.status,c),c}catch(c){throw w(0,void 0,c instanceof Error?c.message:String(c)),c}}}setupErrorListeners(){let e=(t,i)=>{this.frustrations.errorCount++;let a=t.message||String(t);this.errorQueue.push({errorClass:t.name||"Error",message:a,stackTrace:t.stack||"",traceId:this.isSampled?this.traceId:void 0,context:{type:i,...H(),breadcrumbs:[...this.breadcrumbs]},timestamp:new Date().toISOString()}),this.flush()};window.addEventListener("error",t=>{t.error&&e(t.error,"Uncaught Exception")}),window.addEventListener("unhandledrejection",t=>{e(t.reason instanceof Error?t.reason:new Error(String(t.reason)),"Unhandled Promise Rejection")})}setupRoutingListeners(){let e=history.pushState;history.pushState=(...t)=>{this.flush(),e.apply(history,t),this.startNewTrace(!1),this.addBreadcrumb("navigation",window.location.pathname)},window.addEventListener("popstate",()=>{this.flush(),this.startNewTrace(!1),this.addBreadcrumb("navigation",window.location.pathname)}),document.addEventListener("visibilitychange",()=>{document.visibilityState==="hidden"&&this.flush()}),window.addEventListener("pagehide",()=>this.flush())}flush(){if(this.spanQueue.length===0&&this.errorQueue.length===0&&!this.isInitialLoad)return;let e=this.spanQueue.splice(0,this.MAX_BATCH_SIZE),t=this.errorQueue.splice(0,20),i={traces:[],errors:t};if(this.isSampled&&i.traces.push({traceId:this.traceId,sessionId:this.sessionId,traceType:this.isInitialLoad?"initial_load":"route_change",path:window.location.pathname,referrer:document.referrer||"",vitals:{...this.vitals},timings:this.isInitialLoad?this.getNavigationTimings():{},frustration:{...this.frustrations},...H(),spans:e,duration:Date.now()-this.traceStartTime,timestamp:new Date(this.traceStartTime).toISOString()}),this.isInitialLoad=!1,i.traces.length>0||i.errors.length>0){let a=new Blob([JSON.stringify(i)],{type:"application/json"}),l=this.endpoint.includes("?")?"&":"?",n=`${this.endpoint}${l}apiKey=${this.config.apiKey}`;navigator.sendBeacon&&a.size<6e4?navigator.sendBeacon(n,a):fetch(n,{method:"POST",body:a,keepalive:!0,headers:{"x-service-api-key":this.config.apiKey}}).catch(()=>{})}}};var k=new I,L=new T,E={init:s=>k.init(s),initRum:s=>L.init(s)};typeof window<"u"&&(window.Senzor=E);})();
1
+ (()=>{function v(){return typeof crypto<"u"&&crypto.randomUUID?crypto.randomUUID():"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,a=>{let e=Math.random()*16|0;return(a==="x"?e:e&3|8).toString(16)})}function I(a){let e="";for(;e.length<a;)e+=Math.random().toString(16).slice(2);return e.slice(0,a)}var _=()=>{var a;return{userAgent:navigator.userAgent,url:window.location.href,deviceMemory:navigator.deviceMemory||void 0,connectionType:((a=navigator.connection)==null?void 0:a.effectiveType)||void 0}},T=a=>{if(!a)return 0;if(typeof a=="string")return a.length;if(a instanceof Blob||a instanceof File)return a.size;if(a instanceof ArrayBuffer)return a.byteLength},k=a=>{let e={};return a&&(a instanceof Headers?a.forEach((t,r)=>e[r]=t):Array.isArray(a)?a.forEach(([t,r])=>e[t]=r):typeof a=="object"&&Object.assign(e,a)),e};var x=class{config={webId:""};startTime=Date.now();endpoint="https://api.senzor.dev/api/ingest/web";initialized=!1;init(e){if(!this.initialized){if(this.initialized=!0,this.config={...this.config,...e},e.endpoint&&(this.endpoint=e.endpoint),!this.config.webId){console.error("[Senzor] webId is required for Analytics.");return}this.manageSession(),this.trackPageView(),this.setupListeners()}}normalizeUrl(e){return e?e.replace(/^https?:\/\//,"").replace(/^www\./,""):""}manageSession(){let e=Date.now(),t=parseInt(localStorage.getItem("senzor_last_activity")||"0",10),r=1800*1e3;localStorage.getItem("senzor_vid")||localStorage.setItem("senzor_vid",v());let i=sessionStorage.getItem("senzor_sid"),o=e-t>r;!i||o?(i=v(),sessionStorage.setItem("senzor_sid",i),this.determineReferrer(!0)):this.determineReferrer(!1),localStorage.setItem("senzor_last_activity",e.toString())}determineReferrer(e){let t=document.referrer,r=window.location.hostname,i=sessionStorage.getItem("senzor_ref"),o=!1;if(t)try{new URL(t).hostname!==r&&(o=!0)}catch{o=!0}if(o){let n=this.normalizeUrl(t);n!==i&&sessionStorage.setItem("senzor_ref",n)}else e&&!i&&sessionStorage.setItem("senzor_ref","Direct")}getIds(){return localStorage.setItem("senzor_last_activity",Date.now().toString()),{visitorId:localStorage.getItem("senzor_vid")||"unknown",sessionId:sessionStorage.getItem("senzor_sid")||"unknown",referrer:sessionStorage.getItem("senzor_ref")||"Direct"}}trackPageView(){this.manageSession(),this.startTime=Date.now(),this.send({type:"pageview",webId:this.config.webId,...this.getIds(),url:window.location.href,path:window.location.pathname,title:document.title,width:window.innerWidth,timezone:Intl.DateTimeFormat().resolvedOptions().timeZone,referrer:this.getIds().referrer})}trackPing(){let e=Math.floor((Date.now()-this.startTime)/1e3);e>=1&&this.send({type:"ping",webId:this.config.webId,...this.getIds(),url:window.location.href,path:window.location.pathname,title:document.title,width:window.innerWidth,timezone:Intl.DateTimeFormat().resolvedOptions().timeZone,referrer:this.getIds().referrer,duration:e})}send(e){navigator.sendBeacon?navigator.sendBeacon(this.endpoint,new Blob([JSON.stringify(e)],{type:"application/json"}))||this.fallbackSend(e):this.fallbackSend(e)}fallbackSend(e){fetch(this.endpoint,{method:"POST",body:JSON.stringify(e),keepalive:!0,headers:{"Content-Type":"application/json"}}).catch(()=>{})}setupListeners(){let e=history.pushState;history.pushState=(...t)=>{this.trackPing(),e.apply(history,t),this.trackPageView()},window.addEventListener("popstate",()=>{this.trackPing(),this.trackPageView()}),document.addEventListener("visibilitychange",()=>{document.visibilityState==="hidden"?this.trackPing():(this.startTime=Date.now(),this.manageSession())}),window.addEventListener("beforeunload",()=>this.trackPing())}};var E=class{deps;constructor(e){this.deps=e}setup(){this.setupGlobalErrors(),this.setupPromiseErrors(),this.setupReactIntegration(),this.setupReactConsolePatch()}setupGlobalErrors(){window.addEventListener("error",e=>{if(e.error){this.capture(e.error,"Uncaught Exception",{file:e.filename,line:e.lineno,column:e.colno});return}if(e.target&&e.target!==window){let t=e.target;this.capture(new Error("Resource failed to load"),"Resource Error",{file:(t==null?void 0:t.src)||(t==null?void 0:t.href)||"unknown",tag:t==null?void 0:t.tagName})}},!0)}setupPromiseErrors(){window.addEventListener("unhandledrejection",e=>{let t=this.normalizeError(e.reason);this.capture(t,"Unhandled Promise Rejection")})}capture(e,t,r){if(this.shouldIgnore(e))return;this.deps.frustrations.errorCount++;let i=this.extractTopFrame(e),o=this.getLastUserInteraction(),n;try{n=this.deps.getLastNetworkSpan?this.deps.getLastNetworkSpan():void 0}catch{n=void 0}let s={type:t,path:location.pathname,referrer:document.referrer||void 0,traceId:this.deps.isSampled?this.deps.traceId():void 0,sessionId:this.deps.sessionId,file:(r==null?void 0:r.file)||(i==null?void 0:i.file),line:(r==null?void 0:r.line)||(i==null?void 0:i.line),column:(r==null?void 0:r.column)||(i==null?void 0:i.column),topFrame:i,lastInteraction:o,lastNetworkSpan:n,frustrations:{...this.deps.frustrations},..._(),breadcrumbs:[...this.deps.breadcrumbs]};this.deps.errorQueue.push({errorClass:e.name||"Error",message:e.message||String(e),stackTrace:e.stack||"",context:s,timestamp:new Date().toISOString()}),this.deps.flush()}extractTopFrame(e){if(!e.stack)return;let t=e.stack.split(`
2
+ `);for(let r=1;r<t.length;r++){let i=t[r],o=i.match(/\(?(.+):(\d+):(\d+)\)?/);if(o)return{file:o[1],line:Number(o[2]),column:Number(o[3]),raw:i.trim()}}}getLastUserInteraction(){if(this.deps.breadcrumbs.length)for(let e=this.deps.breadcrumbs.length-1;e>=0;e--){let t=this.deps.breadcrumbs[e];if(t.type==="click")return t}}normalizeError(e){if(e instanceof Error)return e;if(typeof e=="string")return new Error(e);if(e!=null&&e.message)return new Error(e.message);try{return new Error(JSON.stringify(e))}catch{return new Error("Unknown rejection")}}shouldIgnore(e){let t=e.stack||"";return!!(t.includes("chrome-extension://")||t.includes("moz-extension://")||t.includes("safari-extension://"))}setupReactIntegration(){let e=window.__REACT_DEVTOOLS_GLOBAL_HOOK__;if(!e||e.__senzor_patched)return;e.__senzor_patched=!0;let t=e.onCommitFiberRoot;e.onCommitFiberRoot=(r,i,...o)=>{if(t)return t.apply(e,[r,i,...o])}}setupReactConsolePatch(){let e=console;if(e.__senzor_react_patch)return;e.__senzor_react_patch=!0;let t=console.error,r="",i=0;console.error=(...o)=>{try{if(!o||!o.length)return t.apply(console,o);let n=o[0];if(typeof n=="string"&&n.includes("The above error occurred")){let s=Date.now();if(n===r&&s-i<2e3)return t.apply(console,o);r=n,i=s;let c=new Error("React component crash");this.capture(c,"React Error")}}catch{}return t.apply(console,o)}}};var R=class{config={apiKey:"",sampleRate:1,allowedOrigins:[]};endpoint="https://api.senzor.dev/api/ingest/rum";initialized=!1;isSampled=!0;sessionId="";traceId="";traceStartTime=0;isInitialLoad=!0;spanQueue=[];errorQueue=[];vitals={};breadcrumbs=[];frustrations={rageClicks:0,deadClicks:0,errorCount:0};clickHistory=[];flushInterval;MAX_BATCH_SIZE=50;errorEngine;init(e){if(!this.initialized){if(this.initialized=!0,this.config={...this.config,...e},e.endpoint&&(this.endpoint=e.endpoint),!this.config.apiKey){console.error("[Senzor RUM] apiKey is required.");return}this.isSampled=Math.random()<=(this.config.sampleRate??1),this.manageSession(),this.startNewTrace(!0),this.errorEngine=new E({isSampled:this.isSampled,traceId:()=>this.traceId,sessionId:this.sessionId,breadcrumbs:this.breadcrumbs,frustrations:this.frustrations,errorQueue:this.errorQueue,flush:()=>this.flush(),getLastNetworkSpan:()=>{var t;return(t=this.spanQueue)!=null&&t.length?this.spanQueue[this.spanQueue.length-1]:void 0}}),this.errorEngine.setup(),this.setupPerformanceObservers(),this.setupUXListeners(),this.isSampled&&this.patchNetwork(),this.flushInterval=setInterval(()=>this.flush(),5e3),this.setupRoutingListeners()}}manageSession(){sessionStorage.getItem("sz_rum_sid")||sessionStorage.setItem("sz_rum_sid",v()),this.sessionId=sessionStorage.getItem("sz_rum_sid")}startNewTrace(e){this.traceId=I(32),this.traceStartTime=Date.now(),this.isInitialLoad=e,this.vitals={},this.frustrations={rageClicks:0,deadClicks:0,errorCount:0}}addBreadcrumb(e,t,r){this.breadcrumbs.push({type:e,message:t,data:r,time:Date.now()}),this.breadcrumbs.length>20&&this.breadcrumbs.shift()}setupUXListeners(){document.addEventListener("click",e=>{let t=e.target,r=t.tagName?t.tagName.toLowerCase():"";this.addBreadcrumb("click",`Clicked ${r}${t.id?"#"+t.id:""}${t.className?"."+t.className.split(" ")[0]:""}`),["a","button","input","select","textarea","label"].includes(r)||t.closest("button")||t.closest("a")||t.hasAttribute("role")||t.onclick||this.frustrations.deadClicks++;let n=Date.now();if(this.clickHistory.push({x:e.clientX,y:e.clientY,time:n}),this.clickHistory=this.clickHistory.filter(s=>n-s.time<1e3),this.clickHistory.length>=3){let s=this.clickHistory[0],c=!0;for(let u=1;u<this.clickHistory.length;u++){let h=Math.abs(this.clickHistory[u].x-s.x),p=Math.abs(this.clickHistory[u].y-s.y);(h>50||p>50)&&(c=!1)}c&&(this.frustrations.rageClicks++,this.addBreadcrumb("frustration","Rage Click Detected"),this.clickHistory=[])}},{capture:!0,passive:!0})}setupPerformanceObservers(){if(!(!this.isSampled||typeof PerformanceObserver>"u"))try{new PerformanceObserver(t=>{for(let r of t.getEntriesByName("first-contentful-paint"))this.vitals.fcp=r.startTime}).observe({type:"paint",buffered:!0}),new PerformanceObserver(t=>{let r=t.getEntries(),i=r[r.length-1];i&&(this.vitals.lcp=i.startTime)}).observe({type:"largest-contentful-paint",buffered:!0});let e=0;new PerformanceObserver(t=>{for(let r of t.getEntries())r.hadRecentInput||(e+=r.value,this.vitals.cls=e)}).observe({type:"layout-shift",buffered:!0}),new PerformanceObserver(t=>{for(let r of t.getEntries()){let i=r,o=i.duration||(i.processingStart&&i.startTime?i.processingStart-i.startTime:0);(!this.vitals.inp||o>this.vitals.inp)&&(this.vitals.inp=o)}}).observe({type:"event",buffered:!0,durationThreshold:40})}catch{}}getNavigationTimings(){if(typeof performance>"u")return{};let e=performance.getEntriesByType("navigation")[0];return e?{dns:Math.max(0,e.domainLookupEnd-e.domainLookupStart),tcp:Math.max(0,e.connectEnd-e.connectStart),ssl:e.secureConnectionStart?Math.max(0,e.requestStart-e.secureConnectionStart):0,ttfb:Math.max(0,e.responseStart-e.requestStart),domInteractive:Math.max(0,e.domInteractive-e.startTime),domComplete:Math.max(0,e.domComplete-e.startTime)}:{}}shouldAttachTraceHeader(e){if(!this.config.allowedOrigins||this.config.allowedOrigins.length===0)return!1;try{let t=new URL(e,window.location.origin);return this.config.allowedOrigins.some(r=>typeof r=="string"?t.origin.includes(r):r instanceof RegExp?r.test(t.origin):!1)}catch{return!1}}patchNetwork(){let e=this,t=XMLHttpRequest.prototype.open,r=XMLHttpRequest.prototype.send,i=XMLHttpRequest.prototype.setRequestHeader;XMLHttpRequest.prototype.open=function(n,s,...c){return this.__szMethod=n.toUpperCase(),this.__szUrl=s,this.__szHeaders={},t.apply(this,[n,s,...c])},XMLHttpRequest.prototype.setRequestHeader=function(n,s){return this.__szHeaders||(this.__szHeaders={}),this.__szHeaders[n]=s,i.apply(this,[n,s])},XMLHttpRequest.prototype.send=function(n){let s=this,c=I(16),u=Date.now()-e.traceStartTime,h=s.__szMethod,p=s.__szUrl;try{p=new URL(s.__szUrl,window.location.origin).toString()}catch{}return e.shouldAttachTraceHeader(p)&&s.setRequestHeader("traceparent",`00-${e.traceId}-${c}-01`),s.addEventListener("loadend",()=>{var w;let g=Date.now()-e.traceStartTime-u,m={};try{m=s.getAllResponseHeaders().trim().split(/[\r\n]+/).reduce((d,S)=>{let b=S.split(": "),f=b.shift(),z=b.join(": ");return f&&(d[f]=z),d},{})}catch{}let y={url:p,method:h,library:"xhr",status:s.status,responseType:s.responseType,requestPayloadSize:T(n),requestHeaders:s.__szHeaders,responseHeaders:m};try{(s.responseType===""||s.responseType==="text")&&(y.responsePayloadSize=(w=s.responseText)==null?void 0:w.length)}catch{}e.spanQueue.push({spanId:c,name:`${h} ${new URL(p,window.location.origin).pathname}`,type:"http",startTime:u,duration:g,status:s.status,meta:y}),e.spanQueue.length>=e.MAX_BATCH_SIZE&&e.flush()}),r.call(this,n)};let o=window.fetch;window.fetch=async function(...n){let s=n[0],c=n[1],u="",h="GET";typeof s=="string"||s instanceof URL?(u=s.toString(),h=((c==null?void 0:c.method)||"GET").toUpperCase()):s instanceof Request&&(u=s.url,h=s.method.toUpperCase());let p=u;try{p=new URL(u,window.location.origin).toString()}catch{}let g=I(16),m=Date.now()-e.traceStartTime,y=k((c==null?void 0:c.headers)||(s instanceof Request?s.headers:{}));if(e.shouldAttachTraceHeader(p)){let l=`00-${e.traceId}-${g}-01`;if(s instanceof Request){let d=new Headers(s.headers);d.set("traceparent",l),n[1]={...c||{},headers:d}}else{let d=new Headers((c==null?void 0:c.headers)||{});d.set("traceparent",l),n[1]={...c||{},headers:d}}y.traceparent=l}let w=(l,d,S)=>{let b=Date.now()-e.traceStartTime-m,f={url:p,method:h,library:"fetch",status:l,requestPayloadSize:T(c==null?void 0:c.body),requestHeaders:y};d&&(f.statusText=d.statusText,f.type=d.type,f.redirected=d.redirected,f.responseHeaders=k(d.headers)),S&&(f.error=S),e.spanQueue.push({spanId:g,name:`${h} ${new URL(p,window.location.origin).pathname}`,type:"http",startTime:m,duration:b,status:l,meta:f}),e.spanQueue.length>=e.MAX_BATCH_SIZE&&e.flush()};try{let l=await o.apply(this,n);return w(l.status,l),l}catch(l){throw w(0,void 0,l instanceof Error?l.message:String(l)),l}}}setupRoutingListeners(){let e=history.pushState;history.pushState=(...t)=>{this.flush(),e.apply(history,t),this.startNewTrace(!1),this.addBreadcrumb("navigation",window.location.pathname)},window.addEventListener("popstate",()=>{this.flush(),this.startNewTrace(!1),this.addBreadcrumb("navigation",window.location.pathname)}),document.addEventListener("visibilitychange",()=>{document.visibilityState==="hidden"&&this.flush()}),window.addEventListener("pagehide",()=>this.flush())}flush(){if(this.spanQueue.length===0&&this.errorQueue.length===0&&!this.isInitialLoad)return;let e=this.spanQueue.splice(0,this.MAX_BATCH_SIZE),t=this.errorQueue.splice(0,20),r={traces:[],errors:t};if(this.isSampled&&r.traces.push({traceId:this.traceId,sessionId:this.sessionId,traceType:this.isInitialLoad?"initial_load":"route_change",path:window.location.pathname,referrer:document.referrer||"",vitals:{...this.vitals},timings:this.isInitialLoad?this.getNavigationTimings():{},frustration:{...this.frustrations},..._(),spans:e,duration:Date.now()-this.traceStartTime,timestamp:new Date(this.traceStartTime).toISOString()}),this.isInitialLoad=!1,r.traces.length>0||r.errors.length>0){let i=new Blob([JSON.stringify(r)],{type:"application/json"}),o=this.endpoint.includes("?")?"&":"?",n=`${this.endpoint}${o}apiKey=${this.config.apiKey}`;navigator.sendBeacon&&i.size<6e4?navigator.sendBeacon(n,i):fetch(n,{method:"POST",body:i,keepalive:!0,headers:{"x-service-api-key":this.config.apiKey}}).catch(()=>{})}}};var H=new x,L=new R,C={init:a=>H.init(a),initRum:a=>L.init(a)};typeof window<"u"&&(window.Senzor=C);})();
package/dist/index.js CHANGED
@@ -1 +1,2 @@
1
- var H=Object.defineProperty;var U=Object.getOwnPropertyDescriptor;var M=Object.getOwnPropertyNames;var A=Object.prototype.hasOwnProperty;var D=(r,e)=>{for(var t in e)H(r,t,{get:e[t],enumerable:!0})},P=(r,e,t,i)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of M(e))!A.call(r,n)&&n!==t&&H(r,n,{get:()=>e[n],enumerable:!(i=U(e,n))||i.enumerable});return r};var q=r=>P(H({},"__esModule",{value:!0}),r);var B={};D(B,{Analytics:()=>k,RUM:()=>L,Senzor:()=>E});module.exports=q(B);function v(){return typeof crypto<"u"&&crypto.randomUUID?crypto.randomUUID():"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,r=>{let e=Math.random()*16|0;return(r==="x"?e:e&3|8).toString(16)})}function x(r){let e="";for(;e.length<r;)e+=Math.random().toString(16).slice(2);return e.slice(0,r)}var _=()=>{var r;return{userAgent:navigator.userAgent,url:window.location.href,deviceMemory:navigator.deviceMemory||void 0,connectionType:((r=navigator.connection)==null?void 0:r.effectiveType)||void 0}},R=r=>{if(!r)return 0;if(typeof r=="string")return r.length;if(r instanceof Blob||r instanceof File)return r.size;if(r instanceof ArrayBuffer)return r.byteLength},z=r=>{let e={};return r&&(r instanceof Headers?r.forEach((t,i)=>e[i]=t):Array.isArray(r)?r.forEach(([t,i])=>e[t]=i):typeof r=="object"&&Object.assign(e,r)),e};var I=class{config={webId:""};startTime=Date.now();endpoint="https://api.senzor.dev/api/ingest/web";initialized=!1;init(e){if(!this.initialized){if(this.initialized=!0,this.config={...this.config,...e},e.endpoint&&(this.endpoint=e.endpoint),!this.config.webId){console.error("[Senzor] webId is required for Analytics.");return}this.manageSession(),this.trackPageView(),this.setupListeners()}}normalizeUrl(e){return e?e.replace(/^https?:\/\//,"").replace(/^www\./,""):""}manageSession(){let e=Date.now(),t=parseInt(localStorage.getItem("senzor_last_activity")||"0",10),i=1800*1e3;localStorage.getItem("senzor_vid")||localStorage.setItem("senzor_vid",v());let n=sessionStorage.getItem("senzor_sid"),l=e-t>i;!n||l?(n=v(),sessionStorage.setItem("senzor_sid",n),this.determineReferrer(!0)):this.determineReferrer(!1),localStorage.setItem("senzor_last_activity",e.toString())}determineReferrer(e){let t=document.referrer,i=window.location.hostname,n=sessionStorage.getItem("senzor_ref"),l=!1;if(t)try{new URL(t).hostname!==i&&(l=!0)}catch{l=!0}if(l){let a=this.normalizeUrl(t);a!==n&&sessionStorage.setItem("senzor_ref",a)}else e&&!n&&sessionStorage.setItem("senzor_ref","Direct")}getIds(){return localStorage.setItem("senzor_last_activity",Date.now().toString()),{visitorId:localStorage.getItem("senzor_vid")||"unknown",sessionId:sessionStorage.getItem("senzor_sid")||"unknown",referrer:sessionStorage.getItem("senzor_ref")||"Direct"}}trackPageView(){this.manageSession(),this.startTime=Date.now(),this.send({type:"pageview",webId:this.config.webId,...this.getIds(),url:window.location.href,path:window.location.pathname,title:document.title,width:window.innerWidth,timezone:Intl.DateTimeFormat().resolvedOptions().timeZone,referrer:this.getIds().referrer})}trackPing(){let e=Math.floor((Date.now()-this.startTime)/1e3);e>=1&&this.send({type:"ping",webId:this.config.webId,...this.getIds(),url:window.location.href,path:window.location.pathname,title:document.title,width:window.innerWidth,timezone:Intl.DateTimeFormat().resolvedOptions().timeZone,referrer:this.getIds().referrer,duration:e})}send(e){navigator.sendBeacon?navigator.sendBeacon(this.endpoint,new Blob([JSON.stringify(e)],{type:"application/json"}))||this.fallbackSend(e):this.fallbackSend(e)}fallbackSend(e){fetch(this.endpoint,{method:"POST",body:JSON.stringify(e),keepalive:!0,headers:{"Content-Type":"application/json"}}).catch(()=>{})}setupListeners(){let e=history.pushState;history.pushState=(...t)=>{this.trackPing(),e.apply(history,t),this.trackPageView()},window.addEventListener("popstate",()=>{this.trackPing(),this.trackPageView()}),document.addEventListener("visibilitychange",()=>{document.visibilityState==="hidden"?this.trackPing():(this.startTime=Date.now(),this.manageSession())}),window.addEventListener("beforeunload",()=>this.trackPing())}};var T=class{config={apiKey:"",sampleRate:1,allowedOrigins:[]};endpoint="https://api.senzor.dev/api/ingest/rum";initialized=!1;isSampled=!0;sessionId="";traceId="";traceStartTime=0;isInitialLoad=!0;spanQueue=[];errorQueue=[];vitals={};breadcrumbs=[];frustrations={rageClicks:0,deadClicks:0,errorCount:0};clickHistory=[];flushInterval;MAX_BATCH_SIZE=50;init(e){if(!this.initialized){if(this.initialized=!0,this.config={...this.config,...e},e.endpoint&&(this.endpoint=e.endpoint),!this.config.apiKey){console.error("[Senzor RUM] apiKey is required.");return}this.isSampled=Math.random()<=(this.config.sampleRate??1),this.manageSession(),this.startNewTrace(!0),this.setupErrorListeners(),this.setupPerformanceObservers(),this.setupUXListeners(),this.isSampled&&this.patchNetwork(),this.flushInterval=setInterval(()=>this.flush(),5e3),this.setupRoutingListeners()}}manageSession(){sessionStorage.getItem("sz_rum_sid")||sessionStorage.setItem("sz_rum_sid",v()),this.sessionId=sessionStorage.getItem("sz_rum_sid")}startNewTrace(e){this.traceId=x(32),this.traceStartTime=Date.now(),this.isInitialLoad=e,this.vitals={},this.frustrations={rageClicks:0,deadClicks:0,errorCount:0}}addBreadcrumb(e,t,i){this.breadcrumbs.push({type:e,message:t,data:i,time:Date.now()}),this.breadcrumbs.length>20&&this.breadcrumbs.shift()}setupUXListeners(){document.addEventListener("click",e=>{let t=e.target,i=t.tagName?t.tagName.toLowerCase():"";this.addBreadcrumb("click",`Clicked ${i}${t.id?"#"+t.id:""}${t.className?"."+t.className.split(" ")[0]:""}`),["a","button","input","select","textarea","label"].includes(i)||t.closest("button")||t.closest("a")||t.hasAttribute("role")||t.onclick||this.frustrations.deadClicks++;let a=Date.now();if(this.clickHistory.push({x:e.clientX,y:e.clientY,time:a}),this.clickHistory=this.clickHistory.filter(s=>a-s.time<1e3),this.clickHistory.length>=3){let s=this.clickHistory[0],o=!0;for(let p=1;p<this.clickHistory.length;p++){let u=Math.abs(this.clickHistory[p].x-s.x),h=Math.abs(this.clickHistory[p].y-s.y);(u>50||h>50)&&(o=!1)}o&&(this.frustrations.rageClicks++,this.addBreadcrumb("frustration","Rage Click Detected"),this.clickHistory=[])}},{capture:!0,passive:!0})}setupPerformanceObservers(){if(!(!this.isSampled||typeof PerformanceObserver>"u"))try{new PerformanceObserver(t=>{for(let i of t.getEntriesByName("first-contentful-paint"))this.vitals.fcp=i.startTime}).observe({type:"paint",buffered:!0}),new PerformanceObserver(t=>{let i=t.getEntries(),n=i[i.length-1];n&&(this.vitals.lcp=n.startTime)}).observe({type:"largest-contentful-paint",buffered:!0});let e=0;new PerformanceObserver(t=>{for(let i of t.getEntries())i.hadRecentInput||(e+=i.value,this.vitals.cls=e)}).observe({type:"layout-shift",buffered:!0}),new PerformanceObserver(t=>{for(let i of t.getEntries()){let n=i,l=n.duration||(n.processingStart&&n.startTime?n.processingStart-n.startTime:0);(!this.vitals.inp||l>this.vitals.inp)&&(this.vitals.inp=l)}}).observe({type:"event",buffered:!0,durationThreshold:40})}catch{}}getNavigationTimings(){if(typeof performance>"u")return{};let e=performance.getEntriesByType("navigation")[0];return e?{dns:Math.max(0,e.domainLookupEnd-e.domainLookupStart),tcp:Math.max(0,e.connectEnd-e.connectStart),ssl:e.secureConnectionStart?Math.max(0,e.requestStart-e.secureConnectionStart):0,ttfb:Math.max(0,e.responseStart-e.requestStart),domInteractive:Math.max(0,e.domInteractive-e.startTime),domComplete:Math.max(0,e.domComplete-e.startTime)}:{}}shouldAttachTraceHeader(e){if(!this.config.allowedOrigins||this.config.allowedOrigins.length===0)return!1;try{let t=new URL(e,window.location.origin);return this.config.allowedOrigins.some(i=>typeof i=="string"?t.origin.includes(i):i instanceof RegExp?i.test(t.origin):!1)}catch{return!1}}patchNetwork(){let e=this,t=XMLHttpRequest.prototype.open,i=XMLHttpRequest.prototype.send,n=XMLHttpRequest.prototype.setRequestHeader;XMLHttpRequest.prototype.open=function(a,s,...o){return this.__szMethod=a.toUpperCase(),this.__szUrl=s,this.__szHeaders={},t.apply(this,[a,s,...o])},XMLHttpRequest.prototype.setRequestHeader=function(a,s){return this.__szHeaders||(this.__szHeaders={}),this.__szHeaders[a]=s,n.apply(this,[a,s])},XMLHttpRequest.prototype.send=function(a){let s=this,o=x(16),p=Date.now()-e.traceStartTime,u=s.__szMethod,h=s.__szUrl;try{h=new URL(s.__szUrl,window.location.origin).toString()}catch{}return e.shouldAttachTraceHeader(h)&&s.setRequestHeader("traceparent",`00-${e.traceId}-${o}-01`),s.addEventListener("loadend",()=>{var w;let g=Date.now()-e.traceStartTime-p,m={};try{m=s.getAllResponseHeaders().trim().split(/[\r\n]+/).reduce((d,S)=>{let b=S.split(": "),f=b.shift(),C=b.join(": ");return f&&(d[f]=C),d},{})}catch{}let y={url:h,method:u,library:"xhr",status:s.status,responseType:s.responseType,requestPayloadSize:R(a),requestHeaders:s.__szHeaders,responseHeaders:m};try{(s.responseType===""||s.responseType==="text")&&(y.responsePayloadSize=(w=s.responseText)==null?void 0:w.length)}catch{}e.spanQueue.push({spanId:o,name:`${u} ${new URL(h,window.location.origin).pathname}`,type:"http",startTime:p,duration:g,status:s.status,meta:y}),e.spanQueue.length>=e.MAX_BATCH_SIZE&&e.flush()}),i.call(this,a)};let l=window.fetch;window.fetch=async function(...a){let s=a[0],o=a[1],p="",u="GET";typeof s=="string"||s instanceof URL?(p=s.toString(),u=((o==null?void 0:o.method)||"GET").toUpperCase()):s instanceof Request&&(p=s.url,u=s.method.toUpperCase());let h=p;try{h=new URL(p,window.location.origin).toString()}catch{}let g=x(16),m=Date.now()-e.traceStartTime,y=z((o==null?void 0:o.headers)||(s instanceof Request?s.headers:{}));if(e.shouldAttachTraceHeader(h)){let c=`00-${e.traceId}-${g}-01`;if(s instanceof Request){let d=new Headers(s.headers);d.set("traceparent",c),a[1]={...o||{},headers:d}}else{let d=new Headers((o==null?void 0:o.headers)||{});d.set("traceparent",c),a[1]={...o||{},headers:d}}y.traceparent=c}let w=(c,d,S)=>{let b=Date.now()-e.traceStartTime-m,f={url:h,method:u,library:"fetch",status:c,requestPayloadSize:R(o==null?void 0:o.body),requestHeaders:y};d&&(f.statusText=d.statusText,f.type=d.type,f.redirected=d.redirected,f.responseHeaders=z(d.headers)),S&&(f.error=S),e.spanQueue.push({spanId:g,name:`${u} ${new URL(h,window.location.origin).pathname}`,type:"http",startTime:m,duration:b,status:c,meta:f}),e.spanQueue.length>=e.MAX_BATCH_SIZE&&e.flush()};try{let c=await l.apply(this,a);return w(c.status,c),c}catch(c){throw w(0,void 0,c instanceof Error?c.message:String(c)),c}}}setupErrorListeners(){let e=(t,i)=>{this.frustrations.errorCount++;let n=t.message||String(t);this.errorQueue.push({errorClass:t.name||"Error",message:n,stackTrace:t.stack||"",traceId:this.isSampled?this.traceId:void 0,context:{type:i,..._(),breadcrumbs:[...this.breadcrumbs]},timestamp:new Date().toISOString()}),this.flush()};window.addEventListener("error",t=>{t.error&&e(t.error,"Uncaught Exception")}),window.addEventListener("unhandledrejection",t=>{e(t.reason instanceof Error?t.reason:new Error(String(t.reason)),"Unhandled Promise Rejection")})}setupRoutingListeners(){let e=history.pushState;history.pushState=(...t)=>{this.flush(),e.apply(history,t),this.startNewTrace(!1),this.addBreadcrumb("navigation",window.location.pathname)},window.addEventListener("popstate",()=>{this.flush(),this.startNewTrace(!1),this.addBreadcrumb("navigation",window.location.pathname)}),document.addEventListener("visibilitychange",()=>{document.visibilityState==="hidden"&&this.flush()}),window.addEventListener("pagehide",()=>this.flush())}flush(){if(this.spanQueue.length===0&&this.errorQueue.length===0&&!this.isInitialLoad)return;let e=this.spanQueue.splice(0,this.MAX_BATCH_SIZE),t=this.errorQueue.splice(0,20),i={traces:[],errors:t};if(this.isSampled&&i.traces.push({traceId:this.traceId,sessionId:this.sessionId,traceType:this.isInitialLoad?"initial_load":"route_change",path:window.location.pathname,referrer:document.referrer||"",vitals:{...this.vitals},timings:this.isInitialLoad?this.getNavigationTimings():{},frustration:{...this.frustrations},..._(),spans:e,duration:Date.now()-this.traceStartTime,timestamp:new Date(this.traceStartTime).toISOString()}),this.isInitialLoad=!1,i.traces.length>0||i.errors.length>0){let n=new Blob([JSON.stringify(i)],{type:"application/json"}),l=this.endpoint.includes("?")?"&":"?",a=`${this.endpoint}${l}apiKey=${this.config.apiKey}`;navigator.sendBeacon&&n.size<6e4?navigator.sendBeacon(a,n):fetch(a,{method:"POST",body:n,keepalive:!0,headers:{"x-service-api-key":this.config.apiKey}}).catch(()=>{})}}};var k=new I,L=new T,E={init:r=>k.init(r),initRum:r=>L.init(r)};typeof window<"u"&&(window.Senzor=E);0&&(module.exports={Analytics,RUM,Senzor});
1
+ var T=Object.defineProperty;var D=Object.getOwnPropertyDescriptor;var P=Object.getOwnPropertyNames;var A=Object.prototype.hasOwnProperty;var M=(n,e)=>{for(var t in e)T(n,t,{get:e[t],enumerable:!0})},N=(n,e,t,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of P(e))!A.call(n,i)&&i!==t&&T(n,i,{get:()=>e[i],enumerable:!(r=D(e,i))||r.enumerable});return n};var O=n=>N(T({},"__esModule",{value:!0}),n);var q={};M(q,{Analytics:()=>H,RUM:()=>L,Senzor:()=>C});module.exports=O(q);function v(){return typeof crypto<"u"&&crypto.randomUUID?crypto.randomUUID():"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,n=>{let e=Math.random()*16|0;return(n==="x"?e:e&3|8).toString(16)})}function I(n){let e="";for(;e.length<n;)e+=Math.random().toString(16).slice(2);return e.slice(0,n)}var _=()=>{var n;return{userAgent:navigator.userAgent,url:window.location.href,deviceMemory:navigator.deviceMemory||void 0,connectionType:((n=navigator.connection)==null?void 0:n.effectiveType)||void 0}},k=n=>{if(!n)return 0;if(typeof n=="string")return n.length;if(n instanceof Blob||n instanceof File)return n.size;if(n instanceof ArrayBuffer)return n.byteLength},z=n=>{let e={};return n&&(n instanceof Headers?n.forEach((t,r)=>e[r]=t):Array.isArray(n)?n.forEach(([t,r])=>e[t]=r):typeof n=="object"&&Object.assign(e,n)),e};var x=class{config={webId:""};startTime=Date.now();endpoint="https://api.senzor.dev/api/ingest/web";initialized=!1;init(e){if(!this.initialized){if(this.initialized=!0,this.config={...this.config,...e},e.endpoint&&(this.endpoint=e.endpoint),!this.config.webId){console.error("[Senzor] webId is required for Analytics.");return}this.manageSession(),this.trackPageView(),this.setupListeners()}}normalizeUrl(e){return e?e.replace(/^https?:\/\//,"").replace(/^www\./,""):""}manageSession(){let e=Date.now(),t=parseInt(localStorage.getItem("senzor_last_activity")||"0",10),r=1800*1e3;localStorage.getItem("senzor_vid")||localStorage.setItem("senzor_vid",v());let i=sessionStorage.getItem("senzor_sid"),o=e-t>r;!i||o?(i=v(),sessionStorage.setItem("senzor_sid",i),this.determineReferrer(!0)):this.determineReferrer(!1),localStorage.setItem("senzor_last_activity",e.toString())}determineReferrer(e){let t=document.referrer,r=window.location.hostname,i=sessionStorage.getItem("senzor_ref"),o=!1;if(t)try{new URL(t).hostname!==r&&(o=!0)}catch{o=!0}if(o){let a=this.normalizeUrl(t);a!==i&&sessionStorage.setItem("senzor_ref",a)}else e&&!i&&sessionStorage.setItem("senzor_ref","Direct")}getIds(){return localStorage.setItem("senzor_last_activity",Date.now().toString()),{visitorId:localStorage.getItem("senzor_vid")||"unknown",sessionId:sessionStorage.getItem("senzor_sid")||"unknown",referrer:sessionStorage.getItem("senzor_ref")||"Direct"}}trackPageView(){this.manageSession(),this.startTime=Date.now(),this.send({type:"pageview",webId:this.config.webId,...this.getIds(),url:window.location.href,path:window.location.pathname,title:document.title,width:window.innerWidth,timezone:Intl.DateTimeFormat().resolvedOptions().timeZone,referrer:this.getIds().referrer})}trackPing(){let e=Math.floor((Date.now()-this.startTime)/1e3);e>=1&&this.send({type:"ping",webId:this.config.webId,...this.getIds(),url:window.location.href,path:window.location.pathname,title:document.title,width:window.innerWidth,timezone:Intl.DateTimeFormat().resolvedOptions().timeZone,referrer:this.getIds().referrer,duration:e})}send(e){navigator.sendBeacon?navigator.sendBeacon(this.endpoint,new Blob([JSON.stringify(e)],{type:"application/json"}))||this.fallbackSend(e):this.fallbackSend(e)}fallbackSend(e){fetch(this.endpoint,{method:"POST",body:JSON.stringify(e),keepalive:!0,headers:{"Content-Type":"application/json"}}).catch(()=>{})}setupListeners(){let e=history.pushState;history.pushState=(...t)=>{this.trackPing(),e.apply(history,t),this.trackPageView()},window.addEventListener("popstate",()=>{this.trackPing(),this.trackPageView()}),document.addEventListener("visibilitychange",()=>{document.visibilityState==="hidden"?this.trackPing():(this.startTime=Date.now(),this.manageSession())}),window.addEventListener("beforeunload",()=>this.trackPing())}};var E=class{deps;constructor(e){this.deps=e}setup(){this.setupGlobalErrors(),this.setupPromiseErrors(),this.setupReactIntegration(),this.setupReactConsolePatch()}setupGlobalErrors(){window.addEventListener("error",e=>{if(e.error){this.capture(e.error,"Uncaught Exception",{file:e.filename,line:e.lineno,column:e.colno});return}if(e.target&&e.target!==window){let t=e.target;this.capture(new Error("Resource failed to load"),"Resource Error",{file:(t==null?void 0:t.src)||(t==null?void 0:t.href)||"unknown",tag:t==null?void 0:t.tagName})}},!0)}setupPromiseErrors(){window.addEventListener("unhandledrejection",e=>{let t=this.normalizeError(e.reason);this.capture(t,"Unhandled Promise Rejection")})}capture(e,t,r){if(this.shouldIgnore(e))return;this.deps.frustrations.errorCount++;let i=this.extractTopFrame(e),o=this.getLastUserInteraction(),a;try{a=this.deps.getLastNetworkSpan?this.deps.getLastNetworkSpan():void 0}catch{a=void 0}let s={type:t,path:location.pathname,referrer:document.referrer||void 0,traceId:this.deps.isSampled?this.deps.traceId():void 0,sessionId:this.deps.sessionId,file:(r==null?void 0:r.file)||(i==null?void 0:i.file),line:(r==null?void 0:r.line)||(i==null?void 0:i.line),column:(r==null?void 0:r.column)||(i==null?void 0:i.column),topFrame:i,lastInteraction:o,lastNetworkSpan:a,frustrations:{...this.deps.frustrations},..._(),breadcrumbs:[...this.deps.breadcrumbs]};this.deps.errorQueue.push({errorClass:e.name||"Error",message:e.message||String(e),stackTrace:e.stack||"",context:s,timestamp:new Date().toISOString()}),this.deps.flush()}extractTopFrame(e){if(!e.stack)return;let t=e.stack.split(`
2
+ `);for(let r=1;r<t.length;r++){let i=t[r],o=i.match(/\(?(.+):(\d+):(\d+)\)?/);if(o)return{file:o[1],line:Number(o[2]),column:Number(o[3]),raw:i.trim()}}}getLastUserInteraction(){if(this.deps.breadcrumbs.length)for(let e=this.deps.breadcrumbs.length-1;e>=0;e--){let t=this.deps.breadcrumbs[e];if(t.type==="click")return t}}normalizeError(e){if(e instanceof Error)return e;if(typeof e=="string")return new Error(e);if(e!=null&&e.message)return new Error(e.message);try{return new Error(JSON.stringify(e))}catch{return new Error("Unknown rejection")}}shouldIgnore(e){let t=e.stack||"";return!!(t.includes("chrome-extension://")||t.includes("moz-extension://")||t.includes("safari-extension://"))}setupReactIntegration(){let e=window.__REACT_DEVTOOLS_GLOBAL_HOOK__;if(!e||e.__senzor_patched)return;e.__senzor_patched=!0;let t=e.onCommitFiberRoot;e.onCommitFiberRoot=(r,i,...o)=>{if(t)return t.apply(e,[r,i,...o])}}setupReactConsolePatch(){let e=console;if(e.__senzor_react_patch)return;e.__senzor_react_patch=!0;let t=console.error,r="",i=0;console.error=(...o)=>{try{if(!o||!o.length)return t.apply(console,o);let a=o[0];if(typeof a=="string"&&a.includes("The above error occurred")){let s=Date.now();if(a===r&&s-i<2e3)return t.apply(console,o);r=a,i=s;let c=new Error("React component crash");this.capture(c,"React Error")}}catch{}return t.apply(console,o)}}};var R=class{config={apiKey:"",sampleRate:1,allowedOrigins:[]};endpoint="https://api.senzor.dev/api/ingest/rum";initialized=!1;isSampled=!0;sessionId="";traceId="";traceStartTime=0;isInitialLoad=!0;spanQueue=[];errorQueue=[];vitals={};breadcrumbs=[];frustrations={rageClicks:0,deadClicks:0,errorCount:0};clickHistory=[];flushInterval;MAX_BATCH_SIZE=50;errorEngine;init(e){if(!this.initialized){if(this.initialized=!0,this.config={...this.config,...e},e.endpoint&&(this.endpoint=e.endpoint),!this.config.apiKey){console.error("[Senzor RUM] apiKey is required.");return}this.isSampled=Math.random()<=(this.config.sampleRate??1),this.manageSession(),this.startNewTrace(!0),this.errorEngine=new E({isSampled:this.isSampled,traceId:()=>this.traceId,sessionId:this.sessionId,breadcrumbs:this.breadcrumbs,frustrations:this.frustrations,errorQueue:this.errorQueue,flush:()=>this.flush(),getLastNetworkSpan:()=>{var t;return(t=this.spanQueue)!=null&&t.length?this.spanQueue[this.spanQueue.length-1]:void 0}}),this.errorEngine.setup(),this.setupPerformanceObservers(),this.setupUXListeners(),this.isSampled&&this.patchNetwork(),this.flushInterval=setInterval(()=>this.flush(),5e3),this.setupRoutingListeners()}}manageSession(){sessionStorage.getItem("sz_rum_sid")||sessionStorage.setItem("sz_rum_sid",v()),this.sessionId=sessionStorage.getItem("sz_rum_sid")}startNewTrace(e){this.traceId=I(32),this.traceStartTime=Date.now(),this.isInitialLoad=e,this.vitals={},this.frustrations={rageClicks:0,deadClicks:0,errorCount:0}}addBreadcrumb(e,t,r){this.breadcrumbs.push({type:e,message:t,data:r,time:Date.now()}),this.breadcrumbs.length>20&&this.breadcrumbs.shift()}setupUXListeners(){document.addEventListener("click",e=>{let t=e.target,r=t.tagName?t.tagName.toLowerCase():"";this.addBreadcrumb("click",`Clicked ${r}${t.id?"#"+t.id:""}${t.className?"."+t.className.split(" ")[0]:""}`),["a","button","input","select","textarea","label"].includes(r)||t.closest("button")||t.closest("a")||t.hasAttribute("role")||t.onclick||this.frustrations.deadClicks++;let a=Date.now();if(this.clickHistory.push({x:e.clientX,y:e.clientY,time:a}),this.clickHistory=this.clickHistory.filter(s=>a-s.time<1e3),this.clickHistory.length>=3){let s=this.clickHistory[0],c=!0;for(let u=1;u<this.clickHistory.length;u++){let h=Math.abs(this.clickHistory[u].x-s.x),p=Math.abs(this.clickHistory[u].y-s.y);(h>50||p>50)&&(c=!1)}c&&(this.frustrations.rageClicks++,this.addBreadcrumb("frustration","Rage Click Detected"),this.clickHistory=[])}},{capture:!0,passive:!0})}setupPerformanceObservers(){if(!(!this.isSampled||typeof PerformanceObserver>"u"))try{new PerformanceObserver(t=>{for(let r of t.getEntriesByName("first-contentful-paint"))this.vitals.fcp=r.startTime}).observe({type:"paint",buffered:!0}),new PerformanceObserver(t=>{let r=t.getEntries(),i=r[r.length-1];i&&(this.vitals.lcp=i.startTime)}).observe({type:"largest-contentful-paint",buffered:!0});let e=0;new PerformanceObserver(t=>{for(let r of t.getEntries())r.hadRecentInput||(e+=r.value,this.vitals.cls=e)}).observe({type:"layout-shift",buffered:!0}),new PerformanceObserver(t=>{for(let r of t.getEntries()){let i=r,o=i.duration||(i.processingStart&&i.startTime?i.processingStart-i.startTime:0);(!this.vitals.inp||o>this.vitals.inp)&&(this.vitals.inp=o)}}).observe({type:"event",buffered:!0,durationThreshold:40})}catch{}}getNavigationTimings(){if(typeof performance>"u")return{};let e=performance.getEntriesByType("navigation")[0];return e?{dns:Math.max(0,e.domainLookupEnd-e.domainLookupStart),tcp:Math.max(0,e.connectEnd-e.connectStart),ssl:e.secureConnectionStart?Math.max(0,e.requestStart-e.secureConnectionStart):0,ttfb:Math.max(0,e.responseStart-e.requestStart),domInteractive:Math.max(0,e.domInteractive-e.startTime),domComplete:Math.max(0,e.domComplete-e.startTime)}:{}}shouldAttachTraceHeader(e){if(!this.config.allowedOrigins||this.config.allowedOrigins.length===0)return!1;try{let t=new URL(e,window.location.origin);return this.config.allowedOrigins.some(r=>typeof r=="string"?t.origin.includes(r):r instanceof RegExp?r.test(t.origin):!1)}catch{return!1}}patchNetwork(){let e=this,t=XMLHttpRequest.prototype.open,r=XMLHttpRequest.prototype.send,i=XMLHttpRequest.prototype.setRequestHeader;XMLHttpRequest.prototype.open=function(a,s,...c){return this.__szMethod=a.toUpperCase(),this.__szUrl=s,this.__szHeaders={},t.apply(this,[a,s,...c])},XMLHttpRequest.prototype.setRequestHeader=function(a,s){return this.__szHeaders||(this.__szHeaders={}),this.__szHeaders[a]=s,i.apply(this,[a,s])},XMLHttpRequest.prototype.send=function(a){let s=this,c=I(16),u=Date.now()-e.traceStartTime,h=s.__szMethod,p=s.__szUrl;try{p=new URL(s.__szUrl,window.location.origin).toString()}catch{}return e.shouldAttachTraceHeader(p)&&s.setRequestHeader("traceparent",`00-${e.traceId}-${c}-01`),s.addEventListener("loadend",()=>{var w;let g=Date.now()-e.traceStartTime-u,m={};try{m=s.getAllResponseHeaders().trim().split(/[\r\n]+/).reduce((d,S)=>{let b=S.split(": "),f=b.shift(),U=b.join(": ");return f&&(d[f]=U),d},{})}catch{}let y={url:p,method:h,library:"xhr",status:s.status,responseType:s.responseType,requestPayloadSize:k(a),requestHeaders:s.__szHeaders,responseHeaders:m};try{(s.responseType===""||s.responseType==="text")&&(y.responsePayloadSize=(w=s.responseText)==null?void 0:w.length)}catch{}e.spanQueue.push({spanId:c,name:`${h} ${new URL(p,window.location.origin).pathname}`,type:"http",startTime:u,duration:g,status:s.status,meta:y}),e.spanQueue.length>=e.MAX_BATCH_SIZE&&e.flush()}),r.call(this,a)};let o=window.fetch;window.fetch=async function(...a){let s=a[0],c=a[1],u="",h="GET";typeof s=="string"||s instanceof URL?(u=s.toString(),h=((c==null?void 0:c.method)||"GET").toUpperCase()):s instanceof Request&&(u=s.url,h=s.method.toUpperCase());let p=u;try{p=new URL(u,window.location.origin).toString()}catch{}let g=I(16),m=Date.now()-e.traceStartTime,y=z((c==null?void 0:c.headers)||(s instanceof Request?s.headers:{}));if(e.shouldAttachTraceHeader(p)){let l=`00-${e.traceId}-${g}-01`;if(s instanceof Request){let d=new Headers(s.headers);d.set("traceparent",l),a[1]={...c||{},headers:d}}else{let d=new Headers((c==null?void 0:c.headers)||{});d.set("traceparent",l),a[1]={...c||{},headers:d}}y.traceparent=l}let w=(l,d,S)=>{let b=Date.now()-e.traceStartTime-m,f={url:p,method:h,library:"fetch",status:l,requestPayloadSize:k(c==null?void 0:c.body),requestHeaders:y};d&&(f.statusText=d.statusText,f.type=d.type,f.redirected=d.redirected,f.responseHeaders=z(d.headers)),S&&(f.error=S),e.spanQueue.push({spanId:g,name:`${h} ${new URL(p,window.location.origin).pathname}`,type:"http",startTime:m,duration:b,status:l,meta:f}),e.spanQueue.length>=e.MAX_BATCH_SIZE&&e.flush()};try{let l=await o.apply(this,a);return w(l.status,l),l}catch(l){throw w(0,void 0,l instanceof Error?l.message:String(l)),l}}}setupRoutingListeners(){let e=history.pushState;history.pushState=(...t)=>{this.flush(),e.apply(history,t),this.startNewTrace(!1),this.addBreadcrumb("navigation",window.location.pathname)},window.addEventListener("popstate",()=>{this.flush(),this.startNewTrace(!1),this.addBreadcrumb("navigation",window.location.pathname)}),document.addEventListener("visibilitychange",()=>{document.visibilityState==="hidden"&&this.flush()}),window.addEventListener("pagehide",()=>this.flush())}flush(){if(this.spanQueue.length===0&&this.errorQueue.length===0&&!this.isInitialLoad)return;let e=this.spanQueue.splice(0,this.MAX_BATCH_SIZE),t=this.errorQueue.splice(0,20),r={traces:[],errors:t};if(this.isSampled&&r.traces.push({traceId:this.traceId,sessionId:this.sessionId,traceType:this.isInitialLoad?"initial_load":"route_change",path:window.location.pathname,referrer:document.referrer||"",vitals:{...this.vitals},timings:this.isInitialLoad?this.getNavigationTimings():{},frustration:{...this.frustrations},..._(),spans:e,duration:Date.now()-this.traceStartTime,timestamp:new Date(this.traceStartTime).toISOString()}),this.isInitialLoad=!1,r.traces.length>0||r.errors.length>0){let i=new Blob([JSON.stringify(r)],{type:"application/json"}),o=this.endpoint.includes("?")?"&":"?",a=`${this.endpoint}${o}apiKey=${this.config.apiKey}`;navigator.sendBeacon&&i.size<6e4?navigator.sendBeacon(a,i):fetch(a,{method:"POST",body:i,keepalive:!0,headers:{"x-service-api-key":this.config.apiKey}}).catch(()=>{})}}};var H=new x,L=new R,C={init:n=>H.init(n),initRum:n=>L.init(n)};typeof window<"u"&&(window.Senzor=C);0&&(module.exports={Analytics,RUM,Senzor});
package/dist/index.mjs CHANGED
@@ -1 +1,2 @@
1
- function v(){return typeof crypto<"u"&&crypto.randomUUID?crypto.randomUUID():"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,s=>{let e=Math.random()*16|0;return(s==="x"?e:e&3|8).toString(16)})}function x(s){let e="";for(;e.length<s;)e+=Math.random().toString(16).slice(2);return e.slice(0,s)}var H=()=>{var s;return{userAgent:navigator.userAgent,url:window.location.href,deviceMemory:navigator.deviceMemory||void 0,connectionType:((s=navigator.connection)==null?void 0:s.effectiveType)||void 0}},_=s=>{if(!s)return 0;if(typeof s=="string")return s.length;if(s instanceof Blob||s instanceof File)return s.size;if(s instanceof ArrayBuffer)return s.byteLength},R=s=>{let e={};return s&&(s instanceof Headers?s.forEach((t,i)=>e[i]=t):Array.isArray(s)?s.forEach(([t,i])=>e[t]=i):typeof s=="object"&&Object.assign(e,s)),e};var I=class{config={webId:""};startTime=Date.now();endpoint="https://api.senzor.dev/api/ingest/web";initialized=!1;init(e){if(!this.initialized){if(this.initialized=!0,this.config={...this.config,...e},e.endpoint&&(this.endpoint=e.endpoint),!this.config.webId){console.error("[Senzor] webId is required for Analytics.");return}this.manageSession(),this.trackPageView(),this.setupListeners()}}normalizeUrl(e){return e?e.replace(/^https?:\/\//,"").replace(/^www\./,""):""}manageSession(){let e=Date.now(),t=parseInt(localStorage.getItem("senzor_last_activity")||"0",10),i=1800*1e3;localStorage.getItem("senzor_vid")||localStorage.setItem("senzor_vid",v());let a=sessionStorage.getItem("senzor_sid"),l=e-t>i;!a||l?(a=v(),sessionStorage.setItem("senzor_sid",a),this.determineReferrer(!0)):this.determineReferrer(!1),localStorage.setItem("senzor_last_activity",e.toString())}determineReferrer(e){let t=document.referrer,i=window.location.hostname,a=sessionStorage.getItem("senzor_ref"),l=!1;if(t)try{new URL(t).hostname!==i&&(l=!0)}catch{l=!0}if(l){let n=this.normalizeUrl(t);n!==a&&sessionStorage.setItem("senzor_ref",n)}else e&&!a&&sessionStorage.setItem("senzor_ref","Direct")}getIds(){return localStorage.setItem("senzor_last_activity",Date.now().toString()),{visitorId:localStorage.getItem("senzor_vid")||"unknown",sessionId:sessionStorage.getItem("senzor_sid")||"unknown",referrer:sessionStorage.getItem("senzor_ref")||"Direct"}}trackPageView(){this.manageSession(),this.startTime=Date.now(),this.send({type:"pageview",webId:this.config.webId,...this.getIds(),url:window.location.href,path:window.location.pathname,title:document.title,width:window.innerWidth,timezone:Intl.DateTimeFormat().resolvedOptions().timeZone,referrer:this.getIds().referrer})}trackPing(){let e=Math.floor((Date.now()-this.startTime)/1e3);e>=1&&this.send({type:"ping",webId:this.config.webId,...this.getIds(),url:window.location.href,path:window.location.pathname,title:document.title,width:window.innerWidth,timezone:Intl.DateTimeFormat().resolvedOptions().timeZone,referrer:this.getIds().referrer,duration:e})}send(e){navigator.sendBeacon?navigator.sendBeacon(this.endpoint,new Blob([JSON.stringify(e)],{type:"application/json"}))||this.fallbackSend(e):this.fallbackSend(e)}fallbackSend(e){fetch(this.endpoint,{method:"POST",body:JSON.stringify(e),keepalive:!0,headers:{"Content-Type":"application/json"}}).catch(()=>{})}setupListeners(){let e=history.pushState;history.pushState=(...t)=>{this.trackPing(),e.apply(history,t),this.trackPageView()},window.addEventListener("popstate",()=>{this.trackPing(),this.trackPageView()}),document.addEventListener("visibilitychange",()=>{document.visibilityState==="hidden"?this.trackPing():(this.startTime=Date.now(),this.manageSession())}),window.addEventListener("beforeunload",()=>this.trackPing())}};var T=class{config={apiKey:"",sampleRate:1,allowedOrigins:[]};endpoint="https://api.senzor.dev/api/ingest/rum";initialized=!1;isSampled=!0;sessionId="";traceId="";traceStartTime=0;isInitialLoad=!0;spanQueue=[];errorQueue=[];vitals={};breadcrumbs=[];frustrations={rageClicks:0,deadClicks:0,errorCount:0};clickHistory=[];flushInterval;MAX_BATCH_SIZE=50;init(e){if(!this.initialized){if(this.initialized=!0,this.config={...this.config,...e},e.endpoint&&(this.endpoint=e.endpoint),!this.config.apiKey){console.error("[Senzor RUM] apiKey is required.");return}this.isSampled=Math.random()<=(this.config.sampleRate??1),this.manageSession(),this.startNewTrace(!0),this.setupErrorListeners(),this.setupPerformanceObservers(),this.setupUXListeners(),this.isSampled&&this.patchNetwork(),this.flushInterval=setInterval(()=>this.flush(),5e3),this.setupRoutingListeners()}}manageSession(){sessionStorage.getItem("sz_rum_sid")||sessionStorage.setItem("sz_rum_sid",v()),this.sessionId=sessionStorage.getItem("sz_rum_sid")}startNewTrace(e){this.traceId=x(32),this.traceStartTime=Date.now(),this.isInitialLoad=e,this.vitals={},this.frustrations={rageClicks:0,deadClicks:0,errorCount:0}}addBreadcrumb(e,t,i){this.breadcrumbs.push({type:e,message:t,data:i,time:Date.now()}),this.breadcrumbs.length>20&&this.breadcrumbs.shift()}setupUXListeners(){document.addEventListener("click",e=>{let t=e.target,i=t.tagName?t.tagName.toLowerCase():"";this.addBreadcrumb("click",`Clicked ${i}${t.id?"#"+t.id:""}${t.className?"."+t.className.split(" ")[0]:""}`),["a","button","input","select","textarea","label"].includes(i)||t.closest("button")||t.closest("a")||t.hasAttribute("role")||t.onclick||this.frustrations.deadClicks++;let n=Date.now();if(this.clickHistory.push({x:e.clientX,y:e.clientY,time:n}),this.clickHistory=this.clickHistory.filter(r=>n-r.time<1e3),this.clickHistory.length>=3){let r=this.clickHistory[0],o=!0;for(let p=1;p<this.clickHistory.length;p++){let u=Math.abs(this.clickHistory[p].x-r.x),h=Math.abs(this.clickHistory[p].y-r.y);(u>50||h>50)&&(o=!1)}o&&(this.frustrations.rageClicks++,this.addBreadcrumb("frustration","Rage Click Detected"),this.clickHistory=[])}},{capture:!0,passive:!0})}setupPerformanceObservers(){if(!(!this.isSampled||typeof PerformanceObserver>"u"))try{new PerformanceObserver(t=>{for(let i of t.getEntriesByName("first-contentful-paint"))this.vitals.fcp=i.startTime}).observe({type:"paint",buffered:!0}),new PerformanceObserver(t=>{let i=t.getEntries(),a=i[i.length-1];a&&(this.vitals.lcp=a.startTime)}).observe({type:"largest-contentful-paint",buffered:!0});let e=0;new PerformanceObserver(t=>{for(let i of t.getEntries())i.hadRecentInput||(e+=i.value,this.vitals.cls=e)}).observe({type:"layout-shift",buffered:!0}),new PerformanceObserver(t=>{for(let i of t.getEntries()){let a=i,l=a.duration||(a.processingStart&&a.startTime?a.processingStart-a.startTime:0);(!this.vitals.inp||l>this.vitals.inp)&&(this.vitals.inp=l)}}).observe({type:"event",buffered:!0,durationThreshold:40})}catch{}}getNavigationTimings(){if(typeof performance>"u")return{};let e=performance.getEntriesByType("navigation")[0];return e?{dns:Math.max(0,e.domainLookupEnd-e.domainLookupStart),tcp:Math.max(0,e.connectEnd-e.connectStart),ssl:e.secureConnectionStart?Math.max(0,e.requestStart-e.secureConnectionStart):0,ttfb:Math.max(0,e.responseStart-e.requestStart),domInteractive:Math.max(0,e.domInteractive-e.startTime),domComplete:Math.max(0,e.domComplete-e.startTime)}:{}}shouldAttachTraceHeader(e){if(!this.config.allowedOrigins||this.config.allowedOrigins.length===0)return!1;try{let t=new URL(e,window.location.origin);return this.config.allowedOrigins.some(i=>typeof i=="string"?t.origin.includes(i):i instanceof RegExp?i.test(t.origin):!1)}catch{return!1}}patchNetwork(){let e=this,t=XMLHttpRequest.prototype.open,i=XMLHttpRequest.prototype.send,a=XMLHttpRequest.prototype.setRequestHeader;XMLHttpRequest.prototype.open=function(n,r,...o){return this.__szMethod=n.toUpperCase(),this.__szUrl=r,this.__szHeaders={},t.apply(this,[n,r,...o])},XMLHttpRequest.prototype.setRequestHeader=function(n,r){return this.__szHeaders||(this.__szHeaders={}),this.__szHeaders[n]=r,a.apply(this,[n,r])},XMLHttpRequest.prototype.send=function(n){let r=this,o=x(16),p=Date.now()-e.traceStartTime,u=r.__szMethod,h=r.__szUrl;try{h=new URL(r.__szUrl,window.location.origin).toString()}catch{}return e.shouldAttachTraceHeader(h)&&r.setRequestHeader("traceparent",`00-${e.traceId}-${o}-01`),r.addEventListener("loadend",()=>{var w;let g=Date.now()-e.traceStartTime-p,m={};try{m=r.getAllResponseHeaders().trim().split(/[\r\n]+/).reduce((d,S)=>{let b=S.split(": "),f=b.shift(),z=b.join(": ");return f&&(d[f]=z),d},{})}catch{}let y={url:h,method:u,library:"xhr",status:r.status,responseType:r.responseType,requestPayloadSize:_(n),requestHeaders:r.__szHeaders,responseHeaders:m};try{(r.responseType===""||r.responseType==="text")&&(y.responsePayloadSize=(w=r.responseText)==null?void 0:w.length)}catch{}e.spanQueue.push({spanId:o,name:`${u} ${new URL(h,window.location.origin).pathname}`,type:"http",startTime:p,duration:g,status:r.status,meta:y}),e.spanQueue.length>=e.MAX_BATCH_SIZE&&e.flush()}),i.call(this,n)};let l=window.fetch;window.fetch=async function(...n){let r=n[0],o=n[1],p="",u="GET";typeof r=="string"||r instanceof URL?(p=r.toString(),u=((o==null?void 0:o.method)||"GET").toUpperCase()):r instanceof Request&&(p=r.url,u=r.method.toUpperCase());let h=p;try{h=new URL(p,window.location.origin).toString()}catch{}let g=x(16),m=Date.now()-e.traceStartTime,y=R((o==null?void 0:o.headers)||(r instanceof Request?r.headers:{}));if(e.shouldAttachTraceHeader(h)){let c=`00-${e.traceId}-${g}-01`;if(r instanceof Request){let d=new Headers(r.headers);d.set("traceparent",c),n[1]={...o||{},headers:d}}else{let d=new Headers((o==null?void 0:o.headers)||{});d.set("traceparent",c),n[1]={...o||{},headers:d}}y.traceparent=c}let w=(c,d,S)=>{let b=Date.now()-e.traceStartTime-m,f={url:h,method:u,library:"fetch",status:c,requestPayloadSize:_(o==null?void 0:o.body),requestHeaders:y};d&&(f.statusText=d.statusText,f.type=d.type,f.redirected=d.redirected,f.responseHeaders=R(d.headers)),S&&(f.error=S),e.spanQueue.push({spanId:g,name:`${u} ${new URL(h,window.location.origin).pathname}`,type:"http",startTime:m,duration:b,status:c,meta:f}),e.spanQueue.length>=e.MAX_BATCH_SIZE&&e.flush()};try{let c=await l.apply(this,n);return w(c.status,c),c}catch(c){throw w(0,void 0,c instanceof Error?c.message:String(c)),c}}}setupErrorListeners(){let e=(t,i)=>{this.frustrations.errorCount++;let a=t.message||String(t);this.errorQueue.push({errorClass:t.name||"Error",message:a,stackTrace:t.stack||"",traceId:this.isSampled?this.traceId:void 0,context:{type:i,...H(),breadcrumbs:[...this.breadcrumbs]},timestamp:new Date().toISOString()}),this.flush()};window.addEventListener("error",t=>{t.error&&e(t.error,"Uncaught Exception")}),window.addEventListener("unhandledrejection",t=>{e(t.reason instanceof Error?t.reason:new Error(String(t.reason)),"Unhandled Promise Rejection")})}setupRoutingListeners(){let e=history.pushState;history.pushState=(...t)=>{this.flush(),e.apply(history,t),this.startNewTrace(!1),this.addBreadcrumb("navigation",window.location.pathname)},window.addEventListener("popstate",()=>{this.flush(),this.startNewTrace(!1),this.addBreadcrumb("navigation",window.location.pathname)}),document.addEventListener("visibilitychange",()=>{document.visibilityState==="hidden"&&this.flush()}),window.addEventListener("pagehide",()=>this.flush())}flush(){if(this.spanQueue.length===0&&this.errorQueue.length===0&&!this.isInitialLoad)return;let e=this.spanQueue.splice(0,this.MAX_BATCH_SIZE),t=this.errorQueue.splice(0,20),i={traces:[],errors:t};if(this.isSampled&&i.traces.push({traceId:this.traceId,sessionId:this.sessionId,traceType:this.isInitialLoad?"initial_load":"route_change",path:window.location.pathname,referrer:document.referrer||"",vitals:{...this.vitals},timings:this.isInitialLoad?this.getNavigationTimings():{},frustration:{...this.frustrations},...H(),spans:e,duration:Date.now()-this.traceStartTime,timestamp:new Date(this.traceStartTime).toISOString()}),this.isInitialLoad=!1,i.traces.length>0||i.errors.length>0){let a=new Blob([JSON.stringify(i)],{type:"application/json"}),l=this.endpoint.includes("?")?"&":"?",n=`${this.endpoint}${l}apiKey=${this.config.apiKey}`;navigator.sendBeacon&&a.size<6e4?navigator.sendBeacon(n,a):fetch(n,{method:"POST",body:a,keepalive:!0,headers:{"x-service-api-key":this.config.apiKey}}).catch(()=>{})}}};var k=new I,L=new T,E={init:s=>k.init(s),initRum:s=>L.init(s)};typeof window<"u"&&(window.Senzor=E);export{k as Analytics,L as RUM,E as Senzor};
1
+ function v(){return typeof crypto<"u"&&crypto.randomUUID?crypto.randomUUID():"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,a=>{let e=Math.random()*16|0;return(a==="x"?e:e&3|8).toString(16)})}function I(a){let e="";for(;e.length<a;)e+=Math.random().toString(16).slice(2);return e.slice(0,a)}var _=()=>{var a;return{userAgent:navigator.userAgent,url:window.location.href,deviceMemory:navigator.deviceMemory||void 0,connectionType:((a=navigator.connection)==null?void 0:a.effectiveType)||void 0}},T=a=>{if(!a)return 0;if(typeof a=="string")return a.length;if(a instanceof Blob||a instanceof File)return a.size;if(a instanceof ArrayBuffer)return a.byteLength},k=a=>{let e={};return a&&(a instanceof Headers?a.forEach((t,r)=>e[r]=t):Array.isArray(a)?a.forEach(([t,r])=>e[t]=r):typeof a=="object"&&Object.assign(e,a)),e};var x=class{config={webId:""};startTime=Date.now();endpoint="https://api.senzor.dev/api/ingest/web";initialized=!1;init(e){if(!this.initialized){if(this.initialized=!0,this.config={...this.config,...e},e.endpoint&&(this.endpoint=e.endpoint),!this.config.webId){console.error("[Senzor] webId is required for Analytics.");return}this.manageSession(),this.trackPageView(),this.setupListeners()}}normalizeUrl(e){return e?e.replace(/^https?:\/\//,"").replace(/^www\./,""):""}manageSession(){let e=Date.now(),t=parseInt(localStorage.getItem("senzor_last_activity")||"0",10),r=1800*1e3;localStorage.getItem("senzor_vid")||localStorage.setItem("senzor_vid",v());let i=sessionStorage.getItem("senzor_sid"),o=e-t>r;!i||o?(i=v(),sessionStorage.setItem("senzor_sid",i),this.determineReferrer(!0)):this.determineReferrer(!1),localStorage.setItem("senzor_last_activity",e.toString())}determineReferrer(e){let t=document.referrer,r=window.location.hostname,i=sessionStorage.getItem("senzor_ref"),o=!1;if(t)try{new URL(t).hostname!==r&&(o=!0)}catch{o=!0}if(o){let n=this.normalizeUrl(t);n!==i&&sessionStorage.setItem("senzor_ref",n)}else e&&!i&&sessionStorage.setItem("senzor_ref","Direct")}getIds(){return localStorage.setItem("senzor_last_activity",Date.now().toString()),{visitorId:localStorage.getItem("senzor_vid")||"unknown",sessionId:sessionStorage.getItem("senzor_sid")||"unknown",referrer:sessionStorage.getItem("senzor_ref")||"Direct"}}trackPageView(){this.manageSession(),this.startTime=Date.now(),this.send({type:"pageview",webId:this.config.webId,...this.getIds(),url:window.location.href,path:window.location.pathname,title:document.title,width:window.innerWidth,timezone:Intl.DateTimeFormat().resolvedOptions().timeZone,referrer:this.getIds().referrer})}trackPing(){let e=Math.floor((Date.now()-this.startTime)/1e3);e>=1&&this.send({type:"ping",webId:this.config.webId,...this.getIds(),url:window.location.href,path:window.location.pathname,title:document.title,width:window.innerWidth,timezone:Intl.DateTimeFormat().resolvedOptions().timeZone,referrer:this.getIds().referrer,duration:e})}send(e){navigator.sendBeacon?navigator.sendBeacon(this.endpoint,new Blob([JSON.stringify(e)],{type:"application/json"}))||this.fallbackSend(e):this.fallbackSend(e)}fallbackSend(e){fetch(this.endpoint,{method:"POST",body:JSON.stringify(e),keepalive:!0,headers:{"Content-Type":"application/json"}}).catch(()=>{})}setupListeners(){let e=history.pushState;history.pushState=(...t)=>{this.trackPing(),e.apply(history,t),this.trackPageView()},window.addEventListener("popstate",()=>{this.trackPing(),this.trackPageView()}),document.addEventListener("visibilitychange",()=>{document.visibilityState==="hidden"?this.trackPing():(this.startTime=Date.now(),this.manageSession())}),window.addEventListener("beforeunload",()=>this.trackPing())}};var E=class{deps;constructor(e){this.deps=e}setup(){this.setupGlobalErrors(),this.setupPromiseErrors(),this.setupReactIntegration(),this.setupReactConsolePatch()}setupGlobalErrors(){window.addEventListener("error",e=>{if(e.error){this.capture(e.error,"Uncaught Exception",{file:e.filename,line:e.lineno,column:e.colno});return}if(e.target&&e.target!==window){let t=e.target;this.capture(new Error("Resource failed to load"),"Resource Error",{file:(t==null?void 0:t.src)||(t==null?void 0:t.href)||"unknown",tag:t==null?void 0:t.tagName})}},!0)}setupPromiseErrors(){window.addEventListener("unhandledrejection",e=>{let t=this.normalizeError(e.reason);this.capture(t,"Unhandled Promise Rejection")})}capture(e,t,r){if(this.shouldIgnore(e))return;this.deps.frustrations.errorCount++;let i=this.extractTopFrame(e),o=this.getLastUserInteraction(),n;try{n=this.deps.getLastNetworkSpan?this.deps.getLastNetworkSpan():void 0}catch{n=void 0}let s={type:t,path:location.pathname,referrer:document.referrer||void 0,traceId:this.deps.isSampled?this.deps.traceId():void 0,sessionId:this.deps.sessionId,file:(r==null?void 0:r.file)||(i==null?void 0:i.file),line:(r==null?void 0:r.line)||(i==null?void 0:i.line),column:(r==null?void 0:r.column)||(i==null?void 0:i.column),topFrame:i,lastInteraction:o,lastNetworkSpan:n,frustrations:{...this.deps.frustrations},..._(),breadcrumbs:[...this.deps.breadcrumbs]};this.deps.errorQueue.push({errorClass:e.name||"Error",message:e.message||String(e),stackTrace:e.stack||"",context:s,timestamp:new Date().toISOString()}),this.deps.flush()}extractTopFrame(e){if(!e.stack)return;let t=e.stack.split(`
2
+ `);for(let r=1;r<t.length;r++){let i=t[r],o=i.match(/\(?(.+):(\d+):(\d+)\)?/);if(o)return{file:o[1],line:Number(o[2]),column:Number(o[3]),raw:i.trim()}}}getLastUserInteraction(){if(this.deps.breadcrumbs.length)for(let e=this.deps.breadcrumbs.length-1;e>=0;e--){let t=this.deps.breadcrumbs[e];if(t.type==="click")return t}}normalizeError(e){if(e instanceof Error)return e;if(typeof e=="string")return new Error(e);if(e!=null&&e.message)return new Error(e.message);try{return new Error(JSON.stringify(e))}catch{return new Error("Unknown rejection")}}shouldIgnore(e){let t=e.stack||"";return!!(t.includes("chrome-extension://")||t.includes("moz-extension://")||t.includes("safari-extension://"))}setupReactIntegration(){let e=window.__REACT_DEVTOOLS_GLOBAL_HOOK__;if(!e||e.__senzor_patched)return;e.__senzor_patched=!0;let t=e.onCommitFiberRoot;e.onCommitFiberRoot=(r,i,...o)=>{if(t)return t.apply(e,[r,i,...o])}}setupReactConsolePatch(){let e=console;if(e.__senzor_react_patch)return;e.__senzor_react_patch=!0;let t=console.error,r="",i=0;console.error=(...o)=>{try{if(!o||!o.length)return t.apply(console,o);let n=o[0];if(typeof n=="string"&&n.includes("The above error occurred")){let s=Date.now();if(n===r&&s-i<2e3)return t.apply(console,o);r=n,i=s;let c=new Error("React component crash");this.capture(c,"React Error")}}catch{}return t.apply(console,o)}}};var R=class{config={apiKey:"",sampleRate:1,allowedOrigins:[]};endpoint="https://api.senzor.dev/api/ingest/rum";initialized=!1;isSampled=!0;sessionId="";traceId="";traceStartTime=0;isInitialLoad=!0;spanQueue=[];errorQueue=[];vitals={};breadcrumbs=[];frustrations={rageClicks:0,deadClicks:0,errorCount:0};clickHistory=[];flushInterval;MAX_BATCH_SIZE=50;errorEngine;init(e){if(!this.initialized){if(this.initialized=!0,this.config={...this.config,...e},e.endpoint&&(this.endpoint=e.endpoint),!this.config.apiKey){console.error("[Senzor RUM] apiKey is required.");return}this.isSampled=Math.random()<=(this.config.sampleRate??1),this.manageSession(),this.startNewTrace(!0),this.errorEngine=new E({isSampled:this.isSampled,traceId:()=>this.traceId,sessionId:this.sessionId,breadcrumbs:this.breadcrumbs,frustrations:this.frustrations,errorQueue:this.errorQueue,flush:()=>this.flush(),getLastNetworkSpan:()=>{var t;return(t=this.spanQueue)!=null&&t.length?this.spanQueue[this.spanQueue.length-1]:void 0}}),this.errorEngine.setup(),this.setupPerformanceObservers(),this.setupUXListeners(),this.isSampled&&this.patchNetwork(),this.flushInterval=setInterval(()=>this.flush(),5e3),this.setupRoutingListeners()}}manageSession(){sessionStorage.getItem("sz_rum_sid")||sessionStorage.setItem("sz_rum_sid",v()),this.sessionId=sessionStorage.getItem("sz_rum_sid")}startNewTrace(e){this.traceId=I(32),this.traceStartTime=Date.now(),this.isInitialLoad=e,this.vitals={},this.frustrations={rageClicks:0,deadClicks:0,errorCount:0}}addBreadcrumb(e,t,r){this.breadcrumbs.push({type:e,message:t,data:r,time:Date.now()}),this.breadcrumbs.length>20&&this.breadcrumbs.shift()}setupUXListeners(){document.addEventListener("click",e=>{let t=e.target,r=t.tagName?t.tagName.toLowerCase():"";this.addBreadcrumb("click",`Clicked ${r}${t.id?"#"+t.id:""}${t.className?"."+t.className.split(" ")[0]:""}`),["a","button","input","select","textarea","label"].includes(r)||t.closest("button")||t.closest("a")||t.hasAttribute("role")||t.onclick||this.frustrations.deadClicks++;let n=Date.now();if(this.clickHistory.push({x:e.clientX,y:e.clientY,time:n}),this.clickHistory=this.clickHistory.filter(s=>n-s.time<1e3),this.clickHistory.length>=3){let s=this.clickHistory[0],c=!0;for(let u=1;u<this.clickHistory.length;u++){let h=Math.abs(this.clickHistory[u].x-s.x),p=Math.abs(this.clickHistory[u].y-s.y);(h>50||p>50)&&(c=!1)}c&&(this.frustrations.rageClicks++,this.addBreadcrumb("frustration","Rage Click Detected"),this.clickHistory=[])}},{capture:!0,passive:!0})}setupPerformanceObservers(){if(!(!this.isSampled||typeof PerformanceObserver>"u"))try{new PerformanceObserver(t=>{for(let r of t.getEntriesByName("first-contentful-paint"))this.vitals.fcp=r.startTime}).observe({type:"paint",buffered:!0}),new PerformanceObserver(t=>{let r=t.getEntries(),i=r[r.length-1];i&&(this.vitals.lcp=i.startTime)}).observe({type:"largest-contentful-paint",buffered:!0});let e=0;new PerformanceObserver(t=>{for(let r of t.getEntries())r.hadRecentInput||(e+=r.value,this.vitals.cls=e)}).observe({type:"layout-shift",buffered:!0}),new PerformanceObserver(t=>{for(let r of t.getEntries()){let i=r,o=i.duration||(i.processingStart&&i.startTime?i.processingStart-i.startTime:0);(!this.vitals.inp||o>this.vitals.inp)&&(this.vitals.inp=o)}}).observe({type:"event",buffered:!0,durationThreshold:40})}catch{}}getNavigationTimings(){if(typeof performance>"u")return{};let e=performance.getEntriesByType("navigation")[0];return e?{dns:Math.max(0,e.domainLookupEnd-e.domainLookupStart),tcp:Math.max(0,e.connectEnd-e.connectStart),ssl:e.secureConnectionStart?Math.max(0,e.requestStart-e.secureConnectionStart):0,ttfb:Math.max(0,e.responseStart-e.requestStart),domInteractive:Math.max(0,e.domInteractive-e.startTime),domComplete:Math.max(0,e.domComplete-e.startTime)}:{}}shouldAttachTraceHeader(e){if(!this.config.allowedOrigins||this.config.allowedOrigins.length===0)return!1;try{let t=new URL(e,window.location.origin);return this.config.allowedOrigins.some(r=>typeof r=="string"?t.origin.includes(r):r instanceof RegExp?r.test(t.origin):!1)}catch{return!1}}patchNetwork(){let e=this,t=XMLHttpRequest.prototype.open,r=XMLHttpRequest.prototype.send,i=XMLHttpRequest.prototype.setRequestHeader;XMLHttpRequest.prototype.open=function(n,s,...c){return this.__szMethod=n.toUpperCase(),this.__szUrl=s,this.__szHeaders={},t.apply(this,[n,s,...c])},XMLHttpRequest.prototype.setRequestHeader=function(n,s){return this.__szHeaders||(this.__szHeaders={}),this.__szHeaders[n]=s,i.apply(this,[n,s])},XMLHttpRequest.prototype.send=function(n){let s=this,c=I(16),u=Date.now()-e.traceStartTime,h=s.__szMethod,p=s.__szUrl;try{p=new URL(s.__szUrl,window.location.origin).toString()}catch{}return e.shouldAttachTraceHeader(p)&&s.setRequestHeader("traceparent",`00-${e.traceId}-${c}-01`),s.addEventListener("loadend",()=>{var w;let g=Date.now()-e.traceStartTime-u,m={};try{m=s.getAllResponseHeaders().trim().split(/[\r\n]+/).reduce((d,S)=>{let b=S.split(": "),f=b.shift(),z=b.join(": ");return f&&(d[f]=z),d},{})}catch{}let y={url:p,method:h,library:"xhr",status:s.status,responseType:s.responseType,requestPayloadSize:T(n),requestHeaders:s.__szHeaders,responseHeaders:m};try{(s.responseType===""||s.responseType==="text")&&(y.responsePayloadSize=(w=s.responseText)==null?void 0:w.length)}catch{}e.spanQueue.push({spanId:c,name:`${h} ${new URL(p,window.location.origin).pathname}`,type:"http",startTime:u,duration:g,status:s.status,meta:y}),e.spanQueue.length>=e.MAX_BATCH_SIZE&&e.flush()}),r.call(this,n)};let o=window.fetch;window.fetch=async function(...n){let s=n[0],c=n[1],u="",h="GET";typeof s=="string"||s instanceof URL?(u=s.toString(),h=((c==null?void 0:c.method)||"GET").toUpperCase()):s instanceof Request&&(u=s.url,h=s.method.toUpperCase());let p=u;try{p=new URL(u,window.location.origin).toString()}catch{}let g=I(16),m=Date.now()-e.traceStartTime,y=k((c==null?void 0:c.headers)||(s instanceof Request?s.headers:{}));if(e.shouldAttachTraceHeader(p)){let l=`00-${e.traceId}-${g}-01`;if(s instanceof Request){let d=new Headers(s.headers);d.set("traceparent",l),n[1]={...c||{},headers:d}}else{let d=new Headers((c==null?void 0:c.headers)||{});d.set("traceparent",l),n[1]={...c||{},headers:d}}y.traceparent=l}let w=(l,d,S)=>{let b=Date.now()-e.traceStartTime-m,f={url:p,method:h,library:"fetch",status:l,requestPayloadSize:T(c==null?void 0:c.body),requestHeaders:y};d&&(f.statusText=d.statusText,f.type=d.type,f.redirected=d.redirected,f.responseHeaders=k(d.headers)),S&&(f.error=S),e.spanQueue.push({spanId:g,name:`${h} ${new URL(p,window.location.origin).pathname}`,type:"http",startTime:m,duration:b,status:l,meta:f}),e.spanQueue.length>=e.MAX_BATCH_SIZE&&e.flush()};try{let l=await o.apply(this,n);return w(l.status,l),l}catch(l){throw w(0,void 0,l instanceof Error?l.message:String(l)),l}}}setupRoutingListeners(){let e=history.pushState;history.pushState=(...t)=>{this.flush(),e.apply(history,t),this.startNewTrace(!1),this.addBreadcrumb("navigation",window.location.pathname)},window.addEventListener("popstate",()=>{this.flush(),this.startNewTrace(!1),this.addBreadcrumb("navigation",window.location.pathname)}),document.addEventListener("visibilitychange",()=>{document.visibilityState==="hidden"&&this.flush()}),window.addEventListener("pagehide",()=>this.flush())}flush(){if(this.spanQueue.length===0&&this.errorQueue.length===0&&!this.isInitialLoad)return;let e=this.spanQueue.splice(0,this.MAX_BATCH_SIZE),t=this.errorQueue.splice(0,20),r={traces:[],errors:t};if(this.isSampled&&r.traces.push({traceId:this.traceId,sessionId:this.sessionId,traceType:this.isInitialLoad?"initial_load":"route_change",path:window.location.pathname,referrer:document.referrer||"",vitals:{...this.vitals},timings:this.isInitialLoad?this.getNavigationTimings():{},frustration:{...this.frustrations},..._(),spans:e,duration:Date.now()-this.traceStartTime,timestamp:new Date(this.traceStartTime).toISOString()}),this.isInitialLoad=!1,r.traces.length>0||r.errors.length>0){let i=new Blob([JSON.stringify(r)],{type:"application/json"}),o=this.endpoint.includes("?")?"&":"?",n=`${this.endpoint}${o}apiKey=${this.config.apiKey}`;navigator.sendBeacon&&i.size<6e4?navigator.sendBeacon(n,i):fetch(n,{method:"POST",body:i,keepalive:!0,headers:{"x-service-api-key":this.config.apiKey}}).catch(()=>{})}}};var H=new x,L=new R,C={init:a=>H.init(a),initRum:a=>L.init(a)};typeof window<"u"&&(window.Senzor=C);export{H as Analytics,L as RUM,C as Senzor};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@senzops/web",
3
- "version": "1.3.3",
3
+ "version": "1.3.4",
4
4
  "description": "Senzor Web Analytics and RUM SDK",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
package/src/error.ts ADDED
@@ -0,0 +1,200 @@
1
+ import { getBrowserContext } from "./utils";
2
+
3
+ export interface ErrorEngineDeps {
4
+ isSampled: boolean;
5
+ traceId: () => string;
6
+ sessionId: string;
7
+ breadcrumbs: any[];
8
+ frustrations: { rageClicks: number; deadClicks: number; errorCount: number };
9
+ errorQueue: any[];
10
+ flush: () => void;
11
+ getLastNetworkSpan?: () => any; // optional correlation hook
12
+ }
13
+
14
+ export class ErrorEngine {
15
+ private deps: ErrorEngineDeps;
16
+
17
+ constructor(deps: ErrorEngineDeps) {
18
+ this.deps = deps;
19
+ }
20
+
21
+ public setup() {
22
+ this.setupGlobalErrors();
23
+ this.setupPromiseErrors();
24
+ this.setupReactIntegration();
25
+ this.setupReactConsolePatch();
26
+ }
27
+
28
+ private setupGlobalErrors() {
29
+ window.addEventListener(
30
+ "error",
31
+ (event: ErrorEvent | any) => {
32
+ // JS runtime errors
33
+ if (event.error) {
34
+ this.capture(event.error, "Uncaught Exception", {
35
+ file: event.filename,
36
+ line: event.lineno,
37
+ column: event.colno,
38
+ });
39
+ return;
40
+ } // Resource errors (script, css, img, font etc)
41
+ if (event.target && event.target !== window) {
42
+ const el: any = event.target;
43
+ this.capture(new Error("Resource failed to load"), "Resource Error", {
44
+ file: el?.src || el?.href || "unknown",
45
+ tag: el?.tagName,
46
+ });
47
+ }
48
+ },
49
+ true,
50
+ );
51
+ }
52
+
53
+ private setupPromiseErrors() {
54
+ window.addEventListener("unhandledrejection", (event) => {
55
+ const error = this.normalizeError(event.reason);
56
+ this.capture(error, "Unhandled Promise Rejection");
57
+ });
58
+ }
59
+
60
+ private capture(errorObj: Error, type: string, extra?: any) {
61
+ if (this.shouldIgnore(errorObj)) return;
62
+ this.deps.frustrations.errorCount++;
63
+ const topFrame = this.extractTopFrame(errorObj);
64
+ const lastInteraction = this.getLastUserInteraction();
65
+ let lastNetwork;
66
+ try {
67
+ lastNetwork = this.deps.getLastNetworkSpan
68
+ ? this.deps.getLastNetworkSpan()
69
+ : undefined;
70
+ } catch {
71
+ lastNetwork = undefined;
72
+ }
73
+ const context = {
74
+ type, // location
75
+ path: location.pathname,
76
+ referrer: document.referrer || undefined, // tracing
77
+ traceId: this.deps.isSampled ? this.deps.traceId() : undefined,
78
+ sessionId: this.deps.sessionId, // source location
79
+ file: extra?.file || topFrame?.file,
80
+ line: extra?.line || topFrame?.line,
81
+ column: extra?.column || topFrame?.column,
82
+ topFrame, // correlations
83
+ lastInteraction,
84
+ lastNetworkSpan: lastNetwork, // UX
85
+ frustrations: { ...this.deps.frustrations }, // browser
86
+ ...getBrowserContext(), // breadcrumbs
87
+ breadcrumbs: [...this.deps.breadcrumbs],
88
+ };
89
+ this.deps.errorQueue.push({
90
+ errorClass: errorObj.name || "Error",
91
+ message: errorObj.message || String(errorObj),
92
+ stackTrace: errorObj.stack || "",
93
+ context,
94
+ timestamp: new Date().toISOString(),
95
+ });
96
+ this.deps.flush();
97
+ }
98
+
99
+ // --- Stack intelligence ---
100
+ private extractTopFrame(error: Error) {
101
+ if (!error.stack) return undefined;
102
+ const lines = error.stack.split("\n");
103
+ for (let i = 1; i < lines.length; i++) {
104
+ const line = lines[i];
105
+ const match = line.match(/\(?(.+):(\d+):(\d+)\)?/);
106
+ if (match) {
107
+ return {
108
+ file: match[1],
109
+ line: Number(match[2]),
110
+ column: Number(match[3]),
111
+ raw: line.trim(),
112
+ };
113
+ }
114
+ }
115
+ return undefined;
116
+ }
117
+
118
+ // --- User interaction intelligence ---
119
+ private getLastUserInteraction() {
120
+ if (!this.deps.breadcrumbs.length) return undefined;
121
+ for (let i = this.deps.breadcrumbs.length - 1; i >= 0; i--) {
122
+ const crumb = this.deps.breadcrumbs[i];
123
+ if (crumb.type === "click") {
124
+ return crumb;
125
+ }
126
+ }
127
+ return undefined;
128
+ }
129
+
130
+ private normalizeError(reason: any): Error {
131
+ if (reason instanceof Error) return reason;
132
+ if (typeof reason === "string") return new Error(reason);
133
+ if (reason?.message) return new Error(reason.message);
134
+ try {
135
+ return new Error(JSON.stringify(reason));
136
+ } catch {
137
+ return new Error("Unknown rejection");
138
+ }
139
+ }
140
+
141
+ private shouldIgnore(error: Error) {
142
+ const stack = error.stack || ""; // Ignore browser extensions noise
143
+ if (stack.includes("chrome-extension://")) return true;
144
+ if (stack.includes("moz-extension://")) return true;
145
+ if (stack.includes("safari-extension://")) return true;
146
+ return false;
147
+ }
148
+
149
+ private setupReactIntegration() {
150
+ const hook = (window as any).__REACT_DEVTOOLS_GLOBAL_HOOK__;
151
+ if (!hook) return;
152
+ // Prevent multiple patches
153
+ if (hook.__senzor_patched) return;
154
+ hook.__senzor_patched = true;
155
+ const orig = hook.onCommitFiberRoot;
156
+ hook.onCommitFiberRoot = (id: any, root: any, ...rest: any[]) => {
157
+ try {
158
+ // We don't depend on React internals.
159
+ // Only detecting React presence safely.
160
+ } catch {}
161
+ if (orig) {
162
+ return orig.apply(hook, [id, root, ...rest]);
163
+ }
164
+ };
165
+ }
166
+
167
+ private setupReactConsolePatch() {
168
+ const consoleAny: any = console;
169
+ // Prevent multiple patches
170
+ if (consoleAny.__senzor_react_patch) return;
171
+ consoleAny.__senzor_react_patch = true;
172
+ const original = console.error;
173
+ // Prevent duplicate React StrictMode errors
174
+ let lastReactError = "";
175
+ let lastReactErrorTime = 0;
176
+ console.error = (...args: any[]) => {
177
+ try {
178
+ if (!args || !args.length) return original.apply(console, args);
179
+ const first = args[0];
180
+ // React component crash pattern
181
+ if (typeof first === "string") {
182
+ if (first.includes("The above error occurred")) {
183
+ const now = Date.now();
184
+ // Prevent duplicates (React strict mode)
185
+ if (first === lastReactError && now - lastReactErrorTime < 2000) {
186
+ return original.apply(console, args);
187
+ }
188
+ lastReactError = first;
189
+ lastReactErrorTime = now;
190
+ const error = new Error("React component crash");
191
+ this.capture(error, "React Error");
192
+ }
193
+ }
194
+ } catch {
195
+ // Never break console
196
+ }
197
+ return original.apply(console, args);
198
+ };
199
+ }
200
+ }
package/src/rum.ts CHANGED
@@ -1,4 +1,11 @@
1
- import { generateHex, generateUUID, getBrowserContext, getPayloadSize, extractHeaders } from './utils';
1
+ import {
2
+ generateHex,
3
+ generateUUID,
4
+ getBrowserContext,
5
+ getPayloadSize,
6
+ extractHeaders,
7
+ } from "./utils";
8
+ import { ErrorEngine } from "./error";
2
9
 
3
10
  export interface RumConfig {
4
11
  apiKey: string;
@@ -8,14 +15,18 @@ export interface RumConfig {
8
15
  }
9
16
 
10
17
  export class SenzorRumAgent {
11
- private config: RumConfig = { apiKey: '', sampleRate: 1.0, allowedOrigins: [] };
12
- private endpoint: string = 'https://api.senzor.dev/api/ingest/rum';
18
+ private config: RumConfig = {
19
+ apiKey: "",
20
+ sampleRate: 1.0,
21
+ allowedOrigins: [],
22
+ };
23
+ private endpoint: string = "https://api.senzor.dev/api/ingest/rum";
13
24
  private initialized: boolean = false;
14
25
  private isSampled: boolean = true;
15
26
 
16
27
  // State
17
- private sessionId: string = '';
18
- private traceId: string = '';
28
+ private sessionId: string = "";
29
+ private traceId: string = "";
19
30
  private traceStartTime: number = 0;
20
31
  private isInitialLoad: boolean = true;
21
32
 
@@ -30,6 +41,8 @@ export class SenzorRumAgent {
30
41
  private flushInterval: any;
31
42
  private readonly MAX_BATCH_SIZE = 50;
32
43
 
44
+ private errorEngine!: ErrorEngine;
45
+
33
46
  public init(config: RumConfig) {
34
47
  if (this.initialized) return;
35
48
  this.initialized = true;
@@ -37,7 +50,7 @@ export class SenzorRumAgent {
37
50
  if (config.endpoint) this.endpoint = config.endpoint;
38
51
 
39
52
  if (!this.config.apiKey) {
40
- console.error('[Senzor RUM] apiKey is required.');
53
+ console.error("[Senzor RUM] apiKey is required.");
41
54
  return;
42
55
  }
43
56
 
@@ -46,7 +59,21 @@ export class SenzorRumAgent {
46
59
  this.manageSession();
47
60
  this.startNewTrace(true);
48
61
 
49
- this.setupErrorListeners();
62
+ this.errorEngine = new ErrorEngine({
63
+ isSampled: this.isSampled,
64
+ traceId: () => this.traceId,
65
+ sessionId: this.sessionId,
66
+ breadcrumbs: this.breadcrumbs,
67
+ frustrations: this.frustrations,
68
+ errorQueue: this.errorQueue,
69
+ flush: () => this.flush(),
70
+ getLastNetworkSpan: () =>
71
+ this.spanQueue?.length
72
+ ? this.spanQueue[this.spanQueue.length - 1]
73
+ : undefined,
74
+ });
75
+ this.errorEngine.setup();
76
+
50
77
  this.setupPerformanceObservers();
51
78
  this.setupUXListeners();
52
79
  if (this.isSampled) this.patchNetwork();
@@ -57,10 +84,10 @@ export class SenzorRumAgent {
57
84
  }
58
85
 
59
86
  private manageSession() {
60
- if (!sessionStorage.getItem('sz_rum_sid')) {
61
- sessionStorage.setItem('sz_rum_sid', generateUUID());
87
+ if (!sessionStorage.getItem("sz_rum_sid")) {
88
+ sessionStorage.setItem("sz_rum_sid", generateUUID());
62
89
  }
63
- this.sessionId = sessionStorage.getItem('sz_rum_sid') as string;
90
+ this.sessionId = sessionStorage.getItem("sz_rum_sid") as string;
64
91
  }
65
92
 
66
93
  private startNewTrace(isInitialLoad: boolean) {
@@ -78,53 +105,76 @@ export class SenzorRumAgent {
78
105
 
79
106
  // --- 1. UX Frustration Detection ---
80
107
  private setupUXListeners() {
81
- document.addEventListener('click', (e) => {
82
- const target = e.target as HTMLElement;
83
- const tag = target.tagName ? target.tagName.toLowerCase() : '';
84
-
85
- this.addBreadcrumb('click', `Clicked ${tag}${target.id ? '#' + target.id : ''}${target.className ? '.' + target.className.split(' ')[0] : ''}`);
86
-
87
- const interactiveElements = ['a', 'button', 'input', 'select', 'textarea', 'label'];
88
- const isInteractive = interactiveElements.includes(tag) || target.closest('button') || target.closest('a') || target.hasAttribute('role') || target.onclick;
89
- if (!isInteractive) this.frustrations.deadClicks++;
90
-
91
- const now = Date.now();
92
- this.clickHistory.push({ x: e.clientX, y: e.clientY, time: now });
93
- this.clickHistory = this.clickHistory.filter(c => now - c.time < 1000);
94
-
95
- if (this.clickHistory.length >= 3) {
96
- const first = this.clickHistory[0];
97
- let isRage = true;
98
- for (let i = 1; i < this.clickHistory.length; i++) {
99
- const dx = Math.abs(this.clickHistory[i].x - first.x);
100
- const dy = Math.abs(this.clickHistory[i].y - first.y);
101
- if (dx > 50 || dy > 50) isRage = false;
102
- }
103
- if (isRage) {
104
- this.frustrations.rageClicks++;
105
- this.addBreadcrumb('frustration', 'Rage Click Detected');
106
- this.clickHistory = [];
108
+ document.addEventListener(
109
+ "click",
110
+ (e) => {
111
+ const target = e.target as HTMLElement;
112
+ const tag = target.tagName ? target.tagName.toLowerCase() : "";
113
+
114
+ this.addBreadcrumb(
115
+ "click",
116
+ `Clicked ${tag}${target.id ? "#" + target.id : ""}${target.className ? "." + target.className.split(" ")[0] : ""}`,
117
+ );
118
+
119
+ const interactiveElements = [
120
+ "a",
121
+ "button",
122
+ "input",
123
+ "select",
124
+ "textarea",
125
+ "label",
126
+ ];
127
+ const isInteractive =
128
+ interactiveElements.includes(tag) ||
129
+ target.closest("button") ||
130
+ target.closest("a") ||
131
+ target.hasAttribute("role") ||
132
+ target.onclick;
133
+ if (!isInteractive) this.frustrations.deadClicks++;
134
+
135
+ const now = Date.now();
136
+ this.clickHistory.push({ x: e.clientX, y: e.clientY, time: now });
137
+ this.clickHistory = this.clickHistory.filter(
138
+ (c) => now - c.time < 1000,
139
+ );
140
+
141
+ if (this.clickHistory.length >= 3) {
142
+ const first = this.clickHistory[0];
143
+ let isRage = true;
144
+ for (let i = 1; i < this.clickHistory.length; i++) {
145
+ const dx = Math.abs(this.clickHistory[i].x - first.x);
146
+ const dy = Math.abs(this.clickHistory[i].y - first.y);
147
+ if (dx > 50 || dy > 50) isRage = false;
148
+ }
149
+ if (isRage) {
150
+ this.frustrations.rageClicks++;
151
+ this.addBreadcrumb("frustration", "Rage Click Detected");
152
+ this.clickHistory = [];
153
+ }
107
154
  }
108
- }
109
- }, { capture: true, passive: true });
155
+ },
156
+ { capture: true, passive: true },
157
+ );
110
158
  }
111
159
 
112
160
  // --- 2. Google Core Web Vitals ---
113
161
  private setupPerformanceObservers() {
114
- if (!this.isSampled || typeof PerformanceObserver === 'undefined') return;
162
+ if (!this.isSampled || typeof PerformanceObserver === "undefined") return;
115
163
 
116
164
  try {
117
165
  new PerformanceObserver((entryList) => {
118
- for (const entry of entryList.getEntriesByName('first-contentful-paint')) {
166
+ for (const entry of entryList.getEntriesByName(
167
+ "first-contentful-paint",
168
+ )) {
119
169
  this.vitals.fcp = entry.startTime;
120
170
  }
121
- }).observe({ type: 'paint', buffered: true });
171
+ }).observe({ type: "paint", buffered: true });
122
172
 
123
173
  new PerformanceObserver((entryList) => {
124
174
  const entries = entryList.getEntries();
125
175
  const lastEntry = entries[entries.length - 1];
126
176
  if (lastEntry) this.vitals.lcp = lastEntry.startTime;
127
- }).observe({ type: 'largest-contentful-paint', buffered: true });
177
+ }).observe({ type: "largest-contentful-paint", buffered: true });
128
178
 
129
179
  let clsScore = 0;
130
180
  new PerformanceObserver((entryList) => {
@@ -134,30 +184,41 @@ export class SenzorRumAgent {
134
184
  this.vitals.cls = clsScore;
135
185
  }
136
186
  }
137
- }).observe({ type: 'layout-shift', buffered: true });
187
+ }).observe({ type: "layout-shift", buffered: true });
138
188
 
139
189
  new PerformanceObserver((entryList) => {
140
190
  for (const entry of entryList.getEntries()) {
141
191
  const evt = entry as any;
142
- const delay = evt.duration || (evt.processingStart && evt.startTime ? evt.processingStart - evt.startTime : 0);
192
+ const delay =
193
+ evt.duration ||
194
+ (evt.processingStart && evt.startTime
195
+ ? evt.processingStart - evt.startTime
196
+ : 0);
143
197
  if (!this.vitals.inp || delay > this.vitals.inp) {
144
198
  this.vitals.inp = delay;
145
199
  }
146
200
  }
147
- }).observe({ type: 'event', buffered: true, durationThreshold: 40 } as any);
148
-
149
- } catch (e) { }
201
+ }).observe({
202
+ type: "event",
203
+ buffered: true,
204
+ durationThreshold: 40,
205
+ } as any);
206
+ } catch (e) {}
150
207
  }
151
208
 
152
209
  private getNavigationTimings() {
153
- if (typeof performance === 'undefined') return {};
154
- const nav = performance.getEntriesByType('navigation')[0] as PerformanceNavigationTiming;
210
+ if (typeof performance === "undefined") return {};
211
+ const nav = performance.getEntriesByType(
212
+ "navigation",
213
+ )[0] as PerformanceNavigationTiming;
155
214
  if (!nav) return {};
156
215
 
157
216
  return {
158
217
  dns: Math.max(0, nav.domainLookupEnd - nav.domainLookupStart),
159
218
  tcp: Math.max(0, nav.connectEnd - nav.connectStart),
160
- ssl: nav.secureConnectionStart ? Math.max(0, nav.requestStart - nav.secureConnectionStart) : 0,
219
+ ssl: nav.secureConnectionStart
220
+ ? Math.max(0, nav.requestStart - nav.secureConnectionStart)
221
+ : 0,
161
222
  ttfb: Math.max(0, nav.responseStart - nav.requestStart),
162
223
  domInteractive: Math.max(0, nav.domInteractive - nav.startTime),
163
224
  domComplete: Math.max(0, nav.domComplete - nav.startTime),
@@ -166,15 +227,19 @@ export class SenzorRumAgent {
166
227
 
167
228
  // --- 3. Distributed Tracing & Verbose Network Meta ---
168
229
  private shouldAttachTraceHeader(url: string): boolean {
169
- if (!this.config.allowedOrigins || this.config.allowedOrigins.length === 0) return false;
230
+ if (!this.config.allowedOrigins || this.config.allowedOrigins.length === 0)
231
+ return false;
170
232
  try {
171
233
  const targetUrl = new URL(url, window.location.origin);
172
- return this.config.allowedOrigins.some(allowed => {
173
- if (typeof allowed === 'string') return targetUrl.origin.includes(allowed);
234
+ return this.config.allowedOrigins.some((allowed) => {
235
+ if (typeof allowed === "string")
236
+ return targetUrl.origin.includes(allowed);
174
237
  if (allowed instanceof RegExp) return allowed.test(targetUrl.origin);
175
238
  return false;
176
239
  });
177
- } catch { return false; }
240
+ } catch {
241
+ return false;
242
+ }
178
243
  }
179
244
 
180
245
  private patchNetwork() {
@@ -185,73 +250,87 @@ export class SenzorRumAgent {
185
250
  const originalXhrSend = XMLHttpRequest.prototype.send;
186
251
  const originalXhrSetReqHeader = XMLHttpRequest.prototype.setRequestHeader;
187
252
 
188
- XMLHttpRequest.prototype.open = function (method: string, url: string, ...rest: any[]) {
253
+ XMLHttpRequest.prototype.open = function (
254
+ method: string,
255
+ url: string,
256
+ ...rest: any[]
257
+ ) {
189
258
  (this as any).__szMethod = method.toUpperCase();
190
259
  (this as any).__szUrl = url;
191
260
  (this as any).__szHeaders = {};
192
261
  return originalXhrOpen.apply(this, [method, url, ...rest] as any);
193
262
  };
194
263
 
195
- XMLHttpRequest.prototype.setRequestHeader = function (header: string, value: string) {
264
+ XMLHttpRequest.prototype.setRequestHeader = function (
265
+ header: string,
266
+ value: string,
267
+ ) {
196
268
  if (!(this as any).__szHeaders) (this as any).__szHeaders = {};
197
269
  (this as any).__szHeaders[header] = value;
198
270
  return originalXhrSetReqHeader.apply(this, [header, value]);
199
271
  };
200
272
 
201
- XMLHttpRequest.prototype.send = function (body?: Document | XMLHttpRequestBodyInit | null) {
273
+ XMLHttpRequest.prototype.send = function (
274
+ body?: Document | XMLHttpRequestBodyInit | null,
275
+ ) {
202
276
  const xhr = this as any;
203
277
  const spanId = generateHex(16);
204
278
  const startTime = Date.now() - self.traceStartTime;
205
279
  const method = xhr.__szMethod;
206
280
  let fullUrl = xhr.__szUrl;
207
281
 
208
- try { fullUrl = new URL(xhr.__szUrl, window.location.origin).toString(); } catch (e) { }
282
+ try {
283
+ fullUrl = new URL(xhr.__szUrl, window.location.origin).toString();
284
+ } catch (e) {}
209
285
 
210
286
  if (self.shouldAttachTraceHeader(fullUrl)) {
211
- xhr.setRequestHeader('traceparent', `00-${self.traceId}-${spanId}-01`);
287
+ xhr.setRequestHeader("traceparent", `00-${self.traceId}-${spanId}-01`);
212
288
  }
213
289
 
214
- xhr.addEventListener('loadend', () => {
215
- const duration = (Date.now() - self.traceStartTime) - startTime;
290
+ xhr.addEventListener("loadend", () => {
291
+ const duration = Date.now() - self.traceStartTime - startTime;
216
292
 
217
293
  let responseHeaders = {};
218
294
  try {
219
295
  const rawHeaders = xhr.getAllResponseHeaders();
220
- responseHeaders = rawHeaders.trim().split(/[\r\n]+/).reduce((acc: any, line: string) => {
221
- const parts = line.split(': ');
222
- const header = parts.shift();
223
- const value = parts.join(': ');
224
- if (header) acc[header] = value;
225
- return acc;
226
- }, {});
227
- } catch (e) { }
296
+ responseHeaders = rawHeaders
297
+ .trim()
298
+ .split(/[\r\n]+/)
299
+ .reduce((acc: any, line: string) => {
300
+ const parts = line.split(": ");
301
+ const header = parts.shift();
302
+ const value = parts.join(": ");
303
+ if (header) acc[header] = value;
304
+ return acc;
305
+ }, {});
306
+ } catch (e) {}
228
307
 
229
308
  const meta: any = {
230
309
  url: fullUrl,
231
310
  method,
232
- library: 'xhr',
311
+ library: "xhr",
233
312
  status: xhr.status,
234
313
  responseType: xhr.responseType,
235
314
  requestPayloadSize: getPayloadSize(body),
236
315
  requestHeaders: xhr.__szHeaders,
237
- responseHeaders
316
+ responseHeaders,
238
317
  };
239
318
 
240
319
  try {
241
- if (xhr.responseType === '' || xhr.responseType === 'text') {
320
+ if (xhr.responseType === "" || xhr.responseType === "text") {
242
321
  meta.responsePayloadSize = xhr.responseText?.length;
243
322
  }
244
- } catch (e) { }
323
+ } catch (e) {}
245
324
 
246
325
  // Queue Span
247
326
  self.spanQueue.push({
248
327
  spanId,
249
328
  name: `${method} ${new URL(fullUrl, window.location.origin).pathname}`,
250
- type: 'http',
329
+ type: "http",
251
330
  startTime,
252
331
  duration,
253
332
  status: xhr.status,
254
- meta
333
+ meta,
255
334
  });
256
335
 
257
336
  if (self.spanQueue.length >= self.MAX_BATCH_SIZE) self.flush();
@@ -266,46 +345,55 @@ export class SenzorRumAgent {
266
345
  const requestInfo = args[0];
267
346
  const init = args[1];
268
347
 
269
- let url = '';
270
- let method = 'GET';
348
+ let url = "";
349
+ let method = "GET";
271
350
 
272
- if (typeof requestInfo === 'string' || requestInfo instanceof URL) {
351
+ if (typeof requestInfo === "string" || requestInfo instanceof URL) {
273
352
  url = requestInfo.toString();
274
- method = (init?.method || 'GET').toUpperCase();
353
+ method = (init?.method || "GET").toUpperCase();
275
354
  } else if (requestInfo instanceof Request) {
276
355
  url = requestInfo.url;
277
356
  method = requestInfo.method.toUpperCase();
278
357
  }
279
358
 
280
359
  let fullUrl = url;
281
- try { fullUrl = new URL(url, window.location.origin).toString(); } catch (e) { }
360
+ try {
361
+ fullUrl = new URL(url, window.location.origin).toString();
362
+ } catch (e) {}
282
363
 
283
364
  const spanId = generateHex(16);
284
365
  const startTime = Date.now() - self.traceStartTime;
285
366
 
286
- let reqHeadersObj = extractHeaders(init?.headers || (requestInfo instanceof Request ? requestInfo.headers : {}));
367
+ let reqHeadersObj = extractHeaders(
368
+ init?.headers ||
369
+ (requestInfo instanceof Request ? requestInfo.headers : {}),
370
+ );
287
371
 
288
372
  if (self.shouldAttachTraceHeader(fullUrl)) {
289
373
  const traceHeader = `00-${self.traceId}-${spanId}-01`;
290
374
  if (requestInfo instanceof Request) {
291
375
  const currentHeaders = new Headers(requestInfo.headers);
292
- currentHeaders.set('traceparent', traceHeader);
376
+ currentHeaders.set("traceparent", traceHeader);
293
377
  args[1] = { ...(init || {}), headers: currentHeaders };
294
378
  } else {
295
379
  const currentHeaders = new Headers(init?.headers || {});
296
- currentHeaders.set('traceparent', traceHeader);
380
+ currentHeaders.set("traceparent", traceHeader);
297
381
  args[1] = { ...(init || {}), headers: currentHeaders };
298
382
  }
299
- reqHeadersObj['traceparent'] = traceHeader;
383
+ reqHeadersObj["traceparent"] = traceHeader;
300
384
  }
301
385
 
302
- const captureSpan = (status: number, response?: Response, errorMsg?: string) => {
303
- const duration = (Date.now() - self.traceStartTime) - startTime;
386
+ const captureSpan = (
387
+ status: number,
388
+ response?: Response,
389
+ errorMsg?: string,
390
+ ) => {
391
+ const duration = Date.now() - self.traceStartTime - startTime;
304
392
 
305
393
  const meta: any = {
306
394
  url: fullUrl,
307
395
  method,
308
- library: 'fetch',
396
+ library: "fetch",
309
397
  status,
310
398
  requestPayloadSize: getPayloadSize(init?.body),
311
399
  requestHeaders: reqHeadersObj,
@@ -323,11 +411,11 @@ export class SenzorRumAgent {
323
411
  self.spanQueue.push({
324
412
  spanId,
325
413
  name: `${method} ${new URL(fullUrl, window.location.origin).pathname}`,
326
- type: 'http',
414
+ type: "http",
327
415
  startTime,
328
416
  duration,
329
417
  status,
330
- meta
418
+ meta,
331
419
  });
332
420
 
333
421
  if (self.spanQueue.length >= self.MAX_BATCH_SIZE) self.flush();
@@ -338,68 +426,46 @@ export class SenzorRumAgent {
338
426
  captureSpan(response.status, response);
339
427
  return response;
340
428
  } catch (error) {
341
- captureSpan(0, undefined, error instanceof Error ? error.message : String(error));
429
+ captureSpan(
430
+ 0,
431
+ undefined,
432
+ error instanceof Error ? error.message : String(error),
433
+ );
342
434
  throw error;
343
435
  }
344
436
  };
345
437
  }
346
438
 
347
- // --- 4. Universal Error Engine Hooks ---
348
- private setupErrorListeners() {
349
- const handleGlobalError = (errorObj: Error, type: string) => {
350
- this.frustrations.errorCount++;
351
- const message = errorObj.message || String(errorObj);
352
-
353
- this.errorQueue.push({
354
- errorClass: errorObj.name || 'Error',
355
- message: message,
356
- stackTrace: errorObj.stack || '',
357
- traceId: this.isSampled ? this.traceId : undefined,
358
- context: {
359
- type,
360
- ...getBrowserContext(),
361
- breadcrumbs: [...this.breadcrumbs]
362
- },
363
- timestamp: new Date().toISOString()
364
- });
365
-
366
- this.flush(); // Flush immediately on critical error
367
- };
368
-
369
- window.addEventListener('error', (event) => {
370
- if (event.error) handleGlobalError(event.error, 'Uncaught Exception');
371
- });
372
-
373
- window.addEventListener('unhandledrejection', (event) => {
374
- handleGlobalError(event.reason instanceof Error ? event.reason : new Error(String(event.reason)), 'Unhandled Promise Rejection');
375
- });
376
- }
377
-
378
- // --- 5. Lifecycle & Beaconing ---
439
+ // --- 4. Lifecycle & Beaconing ---
379
440
  private setupRoutingListeners() {
380
441
  const originalPushState = history.pushState;
381
442
  history.pushState = (...args) => {
382
443
  this.flush();
383
444
  originalPushState.apply(history, args);
384
445
  this.startNewTrace(false);
385
- this.addBreadcrumb('navigation', window.location.pathname);
446
+ this.addBreadcrumb("navigation", window.location.pathname);
386
447
  };
387
448
 
388
- window.addEventListener('popstate', () => {
449
+ window.addEventListener("popstate", () => {
389
450
  this.flush();
390
451
  this.startNewTrace(false);
391
- this.addBreadcrumb('navigation', window.location.pathname);
452
+ this.addBreadcrumb("navigation", window.location.pathname);
392
453
  });
393
454
 
394
- document.addEventListener('visibilitychange', () => {
395
- if (document.visibilityState === 'hidden') this.flush();
455
+ document.addEventListener("visibilitychange", () => {
456
+ if (document.visibilityState === "hidden") this.flush();
396
457
  });
397
458
 
398
- window.addEventListener('pagehide', () => this.flush());
459
+ window.addEventListener("pagehide", () => this.flush());
399
460
  }
400
461
 
401
462
  private flush() {
402
- if (this.spanQueue.length === 0 && this.errorQueue.length === 0 && !this.isInitialLoad) return;
463
+ if (
464
+ this.spanQueue.length === 0 &&
465
+ this.errorQueue.length === 0 &&
466
+ !this.isInitialLoad
467
+ )
468
+ return;
403
469
 
404
470
  const spansToSend = this.spanQueue.splice(0, this.MAX_BATCH_SIZE);
405
471
  const errorsToSend = this.errorQueue.splice(0, 20);
@@ -410,37 +476,39 @@ export class SenzorRumAgent {
410
476
  payload.traces.push({
411
477
  traceId: this.traceId,
412
478
  sessionId: this.sessionId,
413
- traceType: this.isInitialLoad ? 'initial_load' : 'route_change',
479
+ traceType: this.isInitialLoad ? "initial_load" : "route_change",
414
480
  path: window.location.pathname,
415
- referrer: document.referrer || '',
481
+ referrer: document.referrer || "",
416
482
  vitals: { ...this.vitals },
417
483
  timings: this.isInitialLoad ? this.getNavigationTimings() : {},
418
484
  frustration: { ...this.frustrations },
419
485
  ...getBrowserContext(),
420
486
  spans: spansToSend,
421
487
  duration: Date.now() - this.traceStartTime,
422
- timestamp: new Date(this.traceStartTime).toISOString()
488
+ timestamp: new Date(this.traceStartTime).toISOString(),
423
489
  });
424
490
  }
425
491
 
426
492
  this.isInitialLoad = false;
427
493
 
428
494
  if (payload.traces.length > 0 || payload.errors.length > 0) {
429
- const blob = new Blob([JSON.stringify(payload)], { type: 'application/json' });
495
+ const blob = new Blob([JSON.stringify(payload)], {
496
+ type: "application/json",
497
+ });
430
498
 
431
- const separator = this.endpoint.includes('?') ? '&' : '?';
499
+ const separator = this.endpoint.includes("?") ? "&" : "?";
432
500
  const authUrl = `${this.endpoint}${separator}apiKey=${this.config.apiKey}`;
433
501
 
434
502
  if (navigator.sendBeacon && blob.size < 60000) {
435
503
  navigator.sendBeacon(authUrl, blob);
436
504
  } else {
437
505
  fetch(authUrl, {
438
- method: 'POST',
506
+ method: "POST",
439
507
  body: blob,
440
508
  keepalive: true,
441
- headers: { 'x-service-api-key': this.config.apiKey }
442
- }).catch(() => { });
509
+ headers: { "x-service-api-key": this.config.apiKey },
510
+ }).catch(() => {});
443
511
  }
444
512
  }
445
513
  }
446
- }
514
+ }