@senzops/web 1.3.3 → 1.3.5

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