@senzops/web 1.3.2 → 1.3.4

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