@senzops/web 1.3.2 → 1.3.3

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,14 @@ 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;
44
45
  init(config: RumConfig): void;
45
46
  private manageSession;
46
47
  private startNewTrace;
package/dist/index.d.ts CHANGED
@@ -34,13 +34,14 @@ 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;
44
45
  init(config: RumConfig): void;
45
46
  private manageSession;
46
47
  private startNewTrace;
@@ -1 +1 @@
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,s=>{let e=Math.random()*16|0;return(s==="x"?e:e&3|8).toString(16)})}function x(s){let e="";for(;e.length<s;)e+=Math.random().toString(16).slice(2);return e.slice(0,s)}var H=()=>{var s;return{userAgent:navigator.userAgent,url:window.location.href,deviceMemory:navigator.deviceMemory||void 0,connectionType:((s=navigator.connection)==null?void 0:s.effectiveType)||void 0}},_=s=>{if(!s)return 0;if(typeof s=="string")return s.length;if(s instanceof Blob||s instanceof File)return s.size;if(s instanceof ArrayBuffer)return s.byteLength},R=s=>{let e={};return s&&(s instanceof Headers?s.forEach((t,i)=>e[i]=t):Array.isArray(s)?s.forEach(([t,i])=>e[t]=i):typeof s=="object"&&Object.assign(e,s)),e};var I=class{config={webId:""};startTime=Date.now();endpoint="https://api.senzor.dev/api/ingest/web";initialized=!1;init(e){if(!this.initialized){if(this.initialized=!0,this.config={...this.config,...e},e.endpoint&&(this.endpoint=e.endpoint),!this.config.webId){console.error("[Senzor] webId is required for Analytics.");return}this.manageSession(),this.trackPageView(),this.setupListeners()}}normalizeUrl(e){return e?e.replace(/^https?:\/\//,"").replace(/^www\./,""):""}manageSession(){let e=Date.now(),t=parseInt(localStorage.getItem("senzor_last_activity")||"0",10),i=1800*1e3;localStorage.getItem("senzor_vid")||localStorage.setItem("senzor_vid",v());let a=sessionStorage.getItem("senzor_sid"),l=e-t>i;!a||l?(a=v(),sessionStorage.setItem("senzor_sid",a),this.determineReferrer(!0)):this.determineReferrer(!1),localStorage.setItem("senzor_last_activity",e.toString())}determineReferrer(e){let t=document.referrer,i=window.location.hostname,a=sessionStorage.getItem("senzor_ref"),l=!1;if(t)try{new URL(t).hostname!==i&&(l=!0)}catch{l=!0}if(l){let n=this.normalizeUrl(t);n!==a&&sessionStorage.setItem("senzor_ref",n)}else e&&!a&&sessionStorage.setItem("senzor_ref","Direct")}getIds(){return localStorage.setItem("senzor_last_activity",Date.now().toString()),{visitorId:localStorage.getItem("senzor_vid")||"unknown",sessionId:sessionStorage.getItem("senzor_sid")||"unknown",referrer:sessionStorage.getItem("senzor_ref")||"Direct"}}trackPageView(){this.manageSession(),this.startTime=Date.now(),this.send({type:"pageview",webId:this.config.webId,...this.getIds(),url:window.location.href,path:window.location.pathname,title:document.title,width:window.innerWidth,timezone:Intl.DateTimeFormat().resolvedOptions().timeZone,referrer:this.getIds().referrer})}trackPing(){let e=Math.floor((Date.now()-this.startTime)/1e3);e>=1&&this.send({type:"ping",webId:this.config.webId,...this.getIds(),url:window.location.href,path:window.location.pathname,title:document.title,width:window.innerWidth,timezone:Intl.DateTimeFormat().resolvedOptions().timeZone,referrer:this.getIds().referrer,duration:e})}send(e){navigator.sendBeacon?navigator.sendBeacon(this.endpoint,new Blob([JSON.stringify(e)],{type:"application/json"}))||this.fallbackSend(e):this.fallbackSend(e)}fallbackSend(e){fetch(this.endpoint,{method:"POST",body:JSON.stringify(e),keepalive:!0,headers:{"Content-Type":"application/json"}}).catch(()=>{})}setupListeners(){let e=history.pushState;history.pushState=(...t)=>{this.trackPing(),e.apply(history,t),this.trackPageView()},window.addEventListener("popstate",()=>{this.trackPing(),this.trackPageView()}),document.addEventListener("visibilitychange",()=>{document.visibilityState==="hidden"?this.trackPing():(this.startTime=Date.now(),this.manageSession())}),window.addEventListener("beforeunload",()=>this.trackPing())}};var T=class{config={apiKey:"",sampleRate:1,allowedOrigins:[]};endpoint="https://api.senzor.dev/api/ingest/rum";initialized=!1;isSampled=!0;sessionId="";traceId="";traceStartTime=0;isInitialLoad=!0;spanQueue=[];errorQueue=[];vitals={};breadcrumbs=[];frustrations={rageClicks:0,deadClicks:0,errorCount:0};clickHistory=[];flushInterval;MAX_BATCH_SIZE=50;init(e){if(!this.initialized){if(this.initialized=!0,this.config={...this.config,...e},e.endpoint&&(this.endpoint=e.endpoint),!this.config.apiKey){console.error("[Senzor RUM] apiKey is required.");return}this.isSampled=Math.random()<=(this.config.sampleRate??1),this.manageSession(),this.startNewTrace(!0),this.setupErrorListeners(),this.setupPerformanceObservers(),this.setupUXListeners(),this.isSampled&&this.patchNetwork(),this.flushInterval=setInterval(()=>this.flush(),5e3),this.setupRoutingListeners()}}manageSession(){sessionStorage.getItem("sz_rum_sid")||sessionStorage.setItem("sz_rum_sid",v()),this.sessionId=sessionStorage.getItem("sz_rum_sid")}startNewTrace(e){this.traceId=x(32),this.traceStartTime=Date.now(),this.isInitialLoad=e,this.vitals={},this.frustrations={rageClicks:0,deadClicks:0,errorCount:0}}addBreadcrumb(e,t,i){this.breadcrumbs.push({type:e,message:t,data:i,time:Date.now()}),this.breadcrumbs.length>20&&this.breadcrumbs.shift()}setupUXListeners(){document.addEventListener("click",e=>{let t=e.target,i=t.tagName?t.tagName.toLowerCase():"";this.addBreadcrumb("click",`Clicked ${i}${t.id?"#"+t.id:""}${t.className?"."+t.className.split(" ")[0]:""}`),["a","button","input","select","textarea","label"].includes(i)||t.closest("button")||t.closest("a")||t.hasAttribute("role")||t.onclick||this.frustrations.deadClicks++;let n=Date.now();if(this.clickHistory.push({x:e.clientX,y:e.clientY,time:n}),this.clickHistory=this.clickHistory.filter(r=>n-r.time<1e3),this.clickHistory.length>=3){let r=this.clickHistory[0],o=!0;for(let p=1;p<this.clickHistory.length;p++){let u=Math.abs(this.clickHistory[p].x-r.x),h=Math.abs(this.clickHistory[p].y-r.y);(u>50||h>50)&&(o=!1)}o&&(this.frustrations.rageClicks++,this.addBreadcrumb("frustration","Rage Click Detected"),this.clickHistory=[])}},{capture:!0,passive:!0})}setupPerformanceObservers(){if(!(!this.isSampled||typeof PerformanceObserver>"u"))try{new PerformanceObserver(t=>{for(let i of t.getEntriesByName("first-contentful-paint"))this.vitals.fcp=i.startTime}).observe({type:"paint",buffered:!0}),new PerformanceObserver(t=>{let i=t.getEntries(),a=i[i.length-1];a&&(this.vitals.lcp=a.startTime)}).observe({type:"largest-contentful-paint",buffered:!0});let e=0;new PerformanceObserver(t=>{for(let i of t.getEntries())i.hadRecentInput||(e+=i.value,this.vitals.cls=e)}).observe({type:"layout-shift",buffered:!0}),new PerformanceObserver(t=>{for(let i of t.getEntries()){let a=i,l=a.duration||(a.processingStart&&a.startTime?a.processingStart-a.startTime:0);(!this.vitals.inp||l>this.vitals.inp)&&(this.vitals.inp=l)}}).observe({type:"event",buffered:!0,durationThreshold:40})}catch{}}getNavigationTimings(){if(typeof performance>"u")return{};let e=performance.getEntriesByType("navigation")[0];return e?{dns:Math.max(0,e.domainLookupEnd-e.domainLookupStart),tcp:Math.max(0,e.connectEnd-e.connectStart),ssl:e.secureConnectionStart?Math.max(0,e.requestStart-e.secureConnectionStart):0,ttfb:Math.max(0,e.responseStart-e.requestStart),domInteractive:Math.max(0,e.domInteractive-e.startTime),domComplete:Math.max(0,e.domComplete-e.startTime)}:{}}shouldAttachTraceHeader(e){if(!this.config.allowedOrigins||this.config.allowedOrigins.length===0)return!1;try{let t=new URL(e,window.location.origin);return this.config.allowedOrigins.some(i=>typeof i=="string"?t.origin.includes(i):i instanceof RegExp?i.test(t.origin):!1)}catch{return!1}}patchNetwork(){let e=this,t=XMLHttpRequest.prototype.open,i=XMLHttpRequest.prototype.send,a=XMLHttpRequest.prototype.setRequestHeader;XMLHttpRequest.prototype.open=function(n,r,...o){return this.__szMethod=n.toUpperCase(),this.__szUrl=r,this.__szHeaders={},t.apply(this,[n,r,...o])},XMLHttpRequest.prototype.setRequestHeader=function(n,r){return this.__szHeaders||(this.__szHeaders={}),this.__szHeaders[n]=r,a.apply(this,[n,r])},XMLHttpRequest.prototype.send=function(n){let r=this,o=x(16),p=Date.now()-e.traceStartTime,u=r.__szMethod,h=r.__szUrl;try{h=new URL(r.__szUrl,window.location.origin).toString()}catch{}return e.shouldAttachTraceHeader(h)&&r.setRequestHeader("traceparent",`00-${e.traceId}-${o}-01`),r.addEventListener("loadend",()=>{var w;let g=Date.now()-e.traceStartTime-p,m={};try{m=r.getAllResponseHeaders().trim().split(/[\r\n]+/).reduce((d,S)=>{let b=S.split(": "),f=b.shift(),z=b.join(": ");return f&&(d[f]=z),d},{})}catch{}let y={url:h,method:u,library:"xhr",status:r.status,responseType:r.responseType,requestPayloadSize:_(n),requestHeaders:r.__szHeaders,responseHeaders:m};try{(r.responseType===""||r.responseType==="text")&&(y.responsePayloadSize=(w=r.responseText)==null?void 0:w.length)}catch{}e.spanQueue.push({spanId:o,name:`${u} ${new URL(h,window.location.origin).pathname}`,type:"http",startTime:p,duration:g,status:r.status,meta:y}),e.spanQueue.length>=e.MAX_BATCH_SIZE&&e.flush()}),i.call(this,n)};let l=window.fetch;window.fetch=async function(...n){let r=n[0],o=n[1],p="",u="GET";typeof r=="string"||r instanceof URL?(p=r.toString(),u=((o==null?void 0:o.method)||"GET").toUpperCase()):r instanceof Request&&(p=r.url,u=r.method.toUpperCase());let h=p;try{h=new URL(p,window.location.origin).toString()}catch{}let g=x(16),m=Date.now()-e.traceStartTime,y=R((o==null?void 0:o.headers)||(r instanceof Request?r.headers:{}));if(e.shouldAttachTraceHeader(h)){let c=`00-${e.traceId}-${g}-01`;if(r instanceof Request){let d=new Headers(r.headers);d.set("traceparent",c),n[1]={...o||{},headers:d}}else{let d=new Headers((o==null?void 0:o.headers)||{});d.set("traceparent",c),n[1]={...o||{},headers:d}}y.traceparent=c}let w=(c,d,S)=>{let b=Date.now()-e.traceStartTime-m,f={url:h,method:u,library:"fetch",status:c,requestPayloadSize:_(o==null?void 0:o.body),requestHeaders:y};d&&(f.statusText=d.statusText,f.type=d.type,f.redirected=d.redirected,f.responseHeaders=R(d.headers)),S&&(f.error=S),e.spanQueue.push({spanId:g,name:`${u} ${new URL(h,window.location.origin).pathname}`,type:"http",startTime:m,duration:b,status:c,meta:f}),e.spanQueue.length>=e.MAX_BATCH_SIZE&&e.flush()};try{let c=await l.apply(this,n);return w(c.status,c),c}catch(c){throw w(0,void 0,c instanceof Error?c.message:String(c)),c}}}setupErrorListeners(){let e=(t,i)=>{this.frustrations.errorCount++;let a=t.message||String(t);this.errorQueue.push({errorClass:t.name||"Error",message:a,stackTrace:t.stack||"",traceId:this.isSampled?this.traceId:void 0,context:{type:i,...H(),breadcrumbs:[...this.breadcrumbs]},timestamp:new Date().toISOString()}),this.flush()};window.addEventListener("error",t=>{t.error&&e(t.error,"Uncaught Exception")}),window.addEventListener("unhandledrejection",t=>{e(t.reason instanceof Error?t.reason:new Error(String(t.reason)),"Unhandled Promise Rejection")})}setupRoutingListeners(){let e=history.pushState;history.pushState=(...t)=>{this.flush(),e.apply(history,t),this.startNewTrace(!1),this.addBreadcrumb("navigation",window.location.pathname)},window.addEventListener("popstate",()=>{this.flush(),this.startNewTrace(!1),this.addBreadcrumb("navigation",window.location.pathname)}),document.addEventListener("visibilitychange",()=>{document.visibilityState==="hidden"&&this.flush()}),window.addEventListener("pagehide",()=>this.flush())}flush(){if(this.spanQueue.length===0&&this.errorQueue.length===0&&!this.isInitialLoad)return;let e=this.spanQueue.splice(0,this.MAX_BATCH_SIZE),t=this.errorQueue.splice(0,20),i={traces:[],errors:t};if(this.isSampled&&i.traces.push({traceId:this.traceId,sessionId:this.sessionId,traceType:this.isInitialLoad?"initial_load":"route_change",path:window.location.pathname,referrer:document.referrer||"",vitals:{...this.vitals},timings:this.isInitialLoad?this.getNavigationTimings():{},frustration:{...this.frustrations},...H(),spans:e,duration:Date.now()-this.traceStartTime,timestamp:new Date(this.traceStartTime).toISOString()}),this.isInitialLoad=!1,i.traces.length>0||i.errors.length>0){let a=new Blob([JSON.stringify(i)],{type:"application/json"}),l=this.endpoint.includes("?")?"&":"?",n=`${this.endpoint}${l}apiKey=${this.config.apiKey}`;navigator.sendBeacon&&a.size<6e4?navigator.sendBeacon(n,a):fetch(n,{method:"POST",body:a,keepalive:!0,headers:{"x-service-api-key":this.config.apiKey}}).catch(()=>{})}}};var k=new I,L=new T,E={init:s=>k.init(s),initRum:s=>L.init(s)};typeof window<"u"&&(window.Senzor=E);})();
package/dist/index.js CHANGED
@@ -1 +1 @@
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 H=Object.defineProperty;var U=Object.getOwnPropertyDescriptor;var M=Object.getOwnPropertyNames;var A=Object.prototype.hasOwnProperty;var D=(r,e)=>{for(var t in e)H(r,t,{get:e[t],enumerable:!0})},P=(r,e,t,i)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of M(e))!A.call(r,n)&&n!==t&&H(r,n,{get:()=>e[n],enumerable:!(i=U(e,n))||i.enumerable});return r};var q=r=>P(H({},"__esModule",{value:!0}),r);var B={};D(B,{Analytics:()=>k,RUM:()=>L,Senzor:()=>E});module.exports=q(B);function v(){return typeof crypto<"u"&&crypto.randomUUID?crypto.randomUUID():"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,r=>{let e=Math.random()*16|0;return(r==="x"?e:e&3|8).toString(16)})}function x(r){let e="";for(;e.length<r;)e+=Math.random().toString(16).slice(2);return e.slice(0,r)}var _=()=>{var r;return{userAgent:navigator.userAgent,url:window.location.href,deviceMemory:navigator.deviceMemory||void 0,connectionType:((r=navigator.connection)==null?void 0:r.effectiveType)||void 0}},R=r=>{if(!r)return 0;if(typeof r=="string")return r.length;if(r instanceof Blob||r instanceof File)return r.size;if(r instanceof ArrayBuffer)return r.byteLength},z=r=>{let e={};return r&&(r instanceof Headers?r.forEach((t,i)=>e[i]=t):Array.isArray(r)?r.forEach(([t,i])=>e[t]=i):typeof r=="object"&&Object.assign(e,r)),e};var I=class{config={webId:""};startTime=Date.now();endpoint="https://api.senzor.dev/api/ingest/web";initialized=!1;init(e){if(!this.initialized){if(this.initialized=!0,this.config={...this.config,...e},e.endpoint&&(this.endpoint=e.endpoint),!this.config.webId){console.error("[Senzor] webId is required for Analytics.");return}this.manageSession(),this.trackPageView(),this.setupListeners()}}normalizeUrl(e){return e?e.replace(/^https?:\/\//,"").replace(/^www\./,""):""}manageSession(){let e=Date.now(),t=parseInt(localStorage.getItem("senzor_last_activity")||"0",10),i=1800*1e3;localStorage.getItem("senzor_vid")||localStorage.setItem("senzor_vid",v());let n=sessionStorage.getItem("senzor_sid"),l=e-t>i;!n||l?(n=v(),sessionStorage.setItem("senzor_sid",n),this.determineReferrer(!0)):this.determineReferrer(!1),localStorage.setItem("senzor_last_activity",e.toString())}determineReferrer(e){let t=document.referrer,i=window.location.hostname,n=sessionStorage.getItem("senzor_ref"),l=!1;if(t)try{new URL(t).hostname!==i&&(l=!0)}catch{l=!0}if(l){let a=this.normalizeUrl(t);a!==n&&sessionStorage.setItem("senzor_ref",a)}else e&&!n&&sessionStorage.setItem("senzor_ref","Direct")}getIds(){return localStorage.setItem("senzor_last_activity",Date.now().toString()),{visitorId:localStorage.getItem("senzor_vid")||"unknown",sessionId:sessionStorage.getItem("senzor_sid")||"unknown",referrer:sessionStorage.getItem("senzor_ref")||"Direct"}}trackPageView(){this.manageSession(),this.startTime=Date.now(),this.send({type:"pageview",webId:this.config.webId,...this.getIds(),url:window.location.href,path:window.location.pathname,title:document.title,width:window.innerWidth,timezone:Intl.DateTimeFormat().resolvedOptions().timeZone,referrer:this.getIds().referrer})}trackPing(){let e=Math.floor((Date.now()-this.startTime)/1e3);e>=1&&this.send({type:"ping",webId:this.config.webId,...this.getIds(),url:window.location.href,path:window.location.pathname,title:document.title,width:window.innerWidth,timezone:Intl.DateTimeFormat().resolvedOptions().timeZone,referrer:this.getIds().referrer,duration:e})}send(e){navigator.sendBeacon?navigator.sendBeacon(this.endpoint,new Blob([JSON.stringify(e)],{type:"application/json"}))||this.fallbackSend(e):this.fallbackSend(e)}fallbackSend(e){fetch(this.endpoint,{method:"POST",body:JSON.stringify(e),keepalive:!0,headers:{"Content-Type":"application/json"}}).catch(()=>{})}setupListeners(){let e=history.pushState;history.pushState=(...t)=>{this.trackPing(),e.apply(history,t),this.trackPageView()},window.addEventListener("popstate",()=>{this.trackPing(),this.trackPageView()}),document.addEventListener("visibilitychange",()=>{document.visibilityState==="hidden"?this.trackPing():(this.startTime=Date.now(),this.manageSession())}),window.addEventListener("beforeunload",()=>this.trackPing())}};var T=class{config={apiKey:"",sampleRate:1,allowedOrigins:[]};endpoint="https://api.senzor.dev/api/ingest/rum";initialized=!1;isSampled=!0;sessionId="";traceId="";traceStartTime=0;isInitialLoad=!0;spanQueue=[];errorQueue=[];vitals={};breadcrumbs=[];frustrations={rageClicks:0,deadClicks:0,errorCount:0};clickHistory=[];flushInterval;MAX_BATCH_SIZE=50;init(e){if(!this.initialized){if(this.initialized=!0,this.config={...this.config,...e},e.endpoint&&(this.endpoint=e.endpoint),!this.config.apiKey){console.error("[Senzor RUM] apiKey is required.");return}this.isSampled=Math.random()<=(this.config.sampleRate??1),this.manageSession(),this.startNewTrace(!0),this.setupErrorListeners(),this.setupPerformanceObservers(),this.setupUXListeners(),this.isSampled&&this.patchNetwork(),this.flushInterval=setInterval(()=>this.flush(),5e3),this.setupRoutingListeners()}}manageSession(){sessionStorage.getItem("sz_rum_sid")||sessionStorage.setItem("sz_rum_sid",v()),this.sessionId=sessionStorage.getItem("sz_rum_sid")}startNewTrace(e){this.traceId=x(32),this.traceStartTime=Date.now(),this.isInitialLoad=e,this.vitals={},this.frustrations={rageClicks:0,deadClicks:0,errorCount:0}}addBreadcrumb(e,t,i){this.breadcrumbs.push({type:e,message:t,data:i,time:Date.now()}),this.breadcrumbs.length>20&&this.breadcrumbs.shift()}setupUXListeners(){document.addEventListener("click",e=>{let t=e.target,i=t.tagName?t.tagName.toLowerCase():"";this.addBreadcrumb("click",`Clicked ${i}${t.id?"#"+t.id:""}${t.className?"."+t.className.split(" ")[0]:""}`),["a","button","input","select","textarea","label"].includes(i)||t.closest("button")||t.closest("a")||t.hasAttribute("role")||t.onclick||this.frustrations.deadClicks++;let a=Date.now();if(this.clickHistory.push({x:e.clientX,y:e.clientY,time:a}),this.clickHistory=this.clickHistory.filter(s=>a-s.time<1e3),this.clickHistory.length>=3){let s=this.clickHistory[0],o=!0;for(let p=1;p<this.clickHistory.length;p++){let u=Math.abs(this.clickHistory[p].x-s.x),h=Math.abs(this.clickHistory[p].y-s.y);(u>50||h>50)&&(o=!1)}o&&(this.frustrations.rageClicks++,this.addBreadcrumb("frustration","Rage Click Detected"),this.clickHistory=[])}},{capture:!0,passive:!0})}setupPerformanceObservers(){if(!(!this.isSampled||typeof PerformanceObserver>"u"))try{new PerformanceObserver(t=>{for(let i of t.getEntriesByName("first-contentful-paint"))this.vitals.fcp=i.startTime}).observe({type:"paint",buffered:!0}),new PerformanceObserver(t=>{let i=t.getEntries(),n=i[i.length-1];n&&(this.vitals.lcp=n.startTime)}).observe({type:"largest-contentful-paint",buffered:!0});let e=0;new PerformanceObserver(t=>{for(let i of t.getEntries())i.hadRecentInput||(e+=i.value,this.vitals.cls=e)}).observe({type:"layout-shift",buffered:!0}),new PerformanceObserver(t=>{for(let i of t.getEntries()){let n=i,l=n.duration||(n.processingStart&&n.startTime?n.processingStart-n.startTime:0);(!this.vitals.inp||l>this.vitals.inp)&&(this.vitals.inp=l)}}).observe({type:"event",buffered:!0,durationThreshold:40})}catch{}}getNavigationTimings(){if(typeof performance>"u")return{};let e=performance.getEntriesByType("navigation")[0];return e?{dns:Math.max(0,e.domainLookupEnd-e.domainLookupStart),tcp:Math.max(0,e.connectEnd-e.connectStart),ssl:e.secureConnectionStart?Math.max(0,e.requestStart-e.secureConnectionStart):0,ttfb:Math.max(0,e.responseStart-e.requestStart),domInteractive:Math.max(0,e.domInteractive-e.startTime),domComplete:Math.max(0,e.domComplete-e.startTime)}:{}}shouldAttachTraceHeader(e){if(!this.config.allowedOrigins||this.config.allowedOrigins.length===0)return!1;try{let t=new URL(e,window.location.origin);return this.config.allowedOrigins.some(i=>typeof i=="string"?t.origin.includes(i):i instanceof RegExp?i.test(t.origin):!1)}catch{return!1}}patchNetwork(){let e=this,t=XMLHttpRequest.prototype.open,i=XMLHttpRequest.prototype.send,n=XMLHttpRequest.prototype.setRequestHeader;XMLHttpRequest.prototype.open=function(a,s,...o){return this.__szMethod=a.toUpperCase(),this.__szUrl=s,this.__szHeaders={},t.apply(this,[a,s,...o])},XMLHttpRequest.prototype.setRequestHeader=function(a,s){return this.__szHeaders||(this.__szHeaders={}),this.__szHeaders[a]=s,n.apply(this,[a,s])},XMLHttpRequest.prototype.send=function(a){let s=this,o=x(16),p=Date.now()-e.traceStartTime,u=s.__szMethod,h=s.__szUrl;try{h=new URL(s.__szUrl,window.location.origin).toString()}catch{}return e.shouldAttachTraceHeader(h)&&s.setRequestHeader("traceparent",`00-${e.traceId}-${o}-01`),s.addEventListener("loadend",()=>{var w;let g=Date.now()-e.traceStartTime-p,m={};try{m=s.getAllResponseHeaders().trim().split(/[\r\n]+/).reduce((d,S)=>{let b=S.split(": "),f=b.shift(),C=b.join(": ");return f&&(d[f]=C),d},{})}catch{}let y={url:h,method:u,library:"xhr",status:s.status,responseType:s.responseType,requestPayloadSize:R(a),requestHeaders:s.__szHeaders,responseHeaders:m};try{(s.responseType===""||s.responseType==="text")&&(y.responsePayloadSize=(w=s.responseText)==null?void 0:w.length)}catch{}e.spanQueue.push({spanId:o,name:`${u} ${new URL(h,window.location.origin).pathname}`,type:"http",startTime:p,duration:g,status:s.status,meta:y}),e.spanQueue.length>=e.MAX_BATCH_SIZE&&e.flush()}),i.call(this,a)};let l=window.fetch;window.fetch=async function(...a){let s=a[0],o=a[1],p="",u="GET";typeof s=="string"||s instanceof URL?(p=s.toString(),u=((o==null?void 0:o.method)||"GET").toUpperCase()):s instanceof Request&&(p=s.url,u=s.method.toUpperCase());let h=p;try{h=new URL(p,window.location.origin).toString()}catch{}let g=x(16),m=Date.now()-e.traceStartTime,y=z((o==null?void 0:o.headers)||(s instanceof Request?s.headers:{}));if(e.shouldAttachTraceHeader(h)){let c=`00-${e.traceId}-${g}-01`;if(s instanceof Request){let d=new Headers(s.headers);d.set("traceparent",c),a[1]={...o||{},headers:d}}else{let d=new Headers((o==null?void 0:o.headers)||{});d.set("traceparent",c),a[1]={...o||{},headers:d}}y.traceparent=c}let w=(c,d,S)=>{let b=Date.now()-e.traceStartTime-m,f={url:h,method:u,library:"fetch",status:c,requestPayloadSize:R(o==null?void 0:o.body),requestHeaders:y};d&&(f.statusText=d.statusText,f.type=d.type,f.redirected=d.redirected,f.responseHeaders=z(d.headers)),S&&(f.error=S),e.spanQueue.push({spanId:g,name:`${u} ${new URL(h,window.location.origin).pathname}`,type:"http",startTime:m,duration:b,status:c,meta:f}),e.spanQueue.length>=e.MAX_BATCH_SIZE&&e.flush()};try{let c=await l.apply(this,a);return w(c.status,c),c}catch(c){throw w(0,void 0,c instanceof Error?c.message:String(c)),c}}}setupErrorListeners(){let e=(t,i)=>{this.frustrations.errorCount++;let n=t.message||String(t);this.errorQueue.push({errorClass:t.name||"Error",message:n,stackTrace:t.stack||"",traceId:this.isSampled?this.traceId:void 0,context:{type:i,..._(),breadcrumbs:[...this.breadcrumbs]},timestamp:new Date().toISOString()}),this.flush()};window.addEventListener("error",t=>{t.error&&e(t.error,"Uncaught Exception")}),window.addEventListener("unhandledrejection",t=>{e(t.reason instanceof Error?t.reason:new Error(String(t.reason)),"Unhandled Promise Rejection")})}setupRoutingListeners(){let e=history.pushState;history.pushState=(...t)=>{this.flush(),e.apply(history,t),this.startNewTrace(!1),this.addBreadcrumb("navigation",window.location.pathname)},window.addEventListener("popstate",()=>{this.flush(),this.startNewTrace(!1),this.addBreadcrumb("navigation",window.location.pathname)}),document.addEventListener("visibilitychange",()=>{document.visibilityState==="hidden"&&this.flush()}),window.addEventListener("pagehide",()=>this.flush())}flush(){if(this.spanQueue.length===0&&this.errorQueue.length===0&&!this.isInitialLoad)return;let e=this.spanQueue.splice(0,this.MAX_BATCH_SIZE),t=this.errorQueue.splice(0,20),i={traces:[],errors:t};if(this.isSampled&&i.traces.push({traceId:this.traceId,sessionId:this.sessionId,traceType:this.isInitialLoad?"initial_load":"route_change",path:window.location.pathname,referrer:document.referrer||"",vitals:{...this.vitals},timings:this.isInitialLoad?this.getNavigationTimings():{},frustration:{...this.frustrations},..._(),spans:e,duration:Date.now()-this.traceStartTime,timestamp:new Date(this.traceStartTime).toISOString()}),this.isInitialLoad=!1,i.traces.length>0||i.errors.length>0){let n=new Blob([JSON.stringify(i)],{type:"application/json"}),l=this.endpoint.includes("?")?"&":"?",a=`${this.endpoint}${l}apiKey=${this.config.apiKey}`;navigator.sendBeacon&&n.size<6e4?navigator.sendBeacon(a,n):fetch(a,{method:"POST",body:n,keepalive:!0,headers:{"x-service-api-key":this.config.apiKey}}).catch(()=>{})}}};var k=new I,L=new T,E={init:r=>k.init(r),initRum:r=>L.init(r)};typeof window<"u"&&(window.Senzor=E);0&&(module.exports={Analytics,RUM,Senzor});
package/dist/index.mjs CHANGED
@@ -1 +1 @@
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,s=>{let e=Math.random()*16|0;return(s==="x"?e:e&3|8).toString(16)})}function x(s){let e="";for(;e.length<s;)e+=Math.random().toString(16).slice(2);return e.slice(0,s)}var H=()=>{var s;return{userAgent:navigator.userAgent,url:window.location.href,deviceMemory:navigator.deviceMemory||void 0,connectionType:((s=navigator.connection)==null?void 0:s.effectiveType)||void 0}},_=s=>{if(!s)return 0;if(typeof s=="string")return s.length;if(s instanceof Blob||s instanceof File)return s.size;if(s instanceof ArrayBuffer)return s.byteLength},R=s=>{let e={};return s&&(s instanceof Headers?s.forEach((t,i)=>e[i]=t):Array.isArray(s)?s.forEach(([t,i])=>e[t]=i):typeof s=="object"&&Object.assign(e,s)),e};var I=class{config={webId:""};startTime=Date.now();endpoint="https://api.senzor.dev/api/ingest/web";initialized=!1;init(e){if(!this.initialized){if(this.initialized=!0,this.config={...this.config,...e},e.endpoint&&(this.endpoint=e.endpoint),!this.config.webId){console.error("[Senzor] webId is required for Analytics.");return}this.manageSession(),this.trackPageView(),this.setupListeners()}}normalizeUrl(e){return e?e.replace(/^https?:\/\//,"").replace(/^www\./,""):""}manageSession(){let e=Date.now(),t=parseInt(localStorage.getItem("senzor_last_activity")||"0",10),i=1800*1e3;localStorage.getItem("senzor_vid")||localStorage.setItem("senzor_vid",v());let a=sessionStorage.getItem("senzor_sid"),l=e-t>i;!a||l?(a=v(),sessionStorage.setItem("senzor_sid",a),this.determineReferrer(!0)):this.determineReferrer(!1),localStorage.setItem("senzor_last_activity",e.toString())}determineReferrer(e){let t=document.referrer,i=window.location.hostname,a=sessionStorage.getItem("senzor_ref"),l=!1;if(t)try{new URL(t).hostname!==i&&(l=!0)}catch{l=!0}if(l){let n=this.normalizeUrl(t);n!==a&&sessionStorage.setItem("senzor_ref",n)}else e&&!a&&sessionStorage.setItem("senzor_ref","Direct")}getIds(){return localStorage.setItem("senzor_last_activity",Date.now().toString()),{visitorId:localStorage.getItem("senzor_vid")||"unknown",sessionId:sessionStorage.getItem("senzor_sid")||"unknown",referrer:sessionStorage.getItem("senzor_ref")||"Direct"}}trackPageView(){this.manageSession(),this.startTime=Date.now(),this.send({type:"pageview",webId:this.config.webId,...this.getIds(),url:window.location.href,path:window.location.pathname,title:document.title,width:window.innerWidth,timezone:Intl.DateTimeFormat().resolvedOptions().timeZone,referrer:this.getIds().referrer})}trackPing(){let e=Math.floor((Date.now()-this.startTime)/1e3);e>=1&&this.send({type:"ping",webId:this.config.webId,...this.getIds(),url:window.location.href,path:window.location.pathname,title:document.title,width:window.innerWidth,timezone:Intl.DateTimeFormat().resolvedOptions().timeZone,referrer:this.getIds().referrer,duration:e})}send(e){navigator.sendBeacon?navigator.sendBeacon(this.endpoint,new Blob([JSON.stringify(e)],{type:"application/json"}))||this.fallbackSend(e):this.fallbackSend(e)}fallbackSend(e){fetch(this.endpoint,{method:"POST",body:JSON.stringify(e),keepalive:!0,headers:{"Content-Type":"application/json"}}).catch(()=>{})}setupListeners(){let e=history.pushState;history.pushState=(...t)=>{this.trackPing(),e.apply(history,t),this.trackPageView()},window.addEventListener("popstate",()=>{this.trackPing(),this.trackPageView()}),document.addEventListener("visibilitychange",()=>{document.visibilityState==="hidden"?this.trackPing():(this.startTime=Date.now(),this.manageSession())}),window.addEventListener("beforeunload",()=>this.trackPing())}};var T=class{config={apiKey:"",sampleRate:1,allowedOrigins:[]};endpoint="https://api.senzor.dev/api/ingest/rum";initialized=!1;isSampled=!0;sessionId="";traceId="";traceStartTime=0;isInitialLoad=!0;spanQueue=[];errorQueue=[];vitals={};breadcrumbs=[];frustrations={rageClicks:0,deadClicks:0,errorCount:0};clickHistory=[];flushInterval;MAX_BATCH_SIZE=50;init(e){if(!this.initialized){if(this.initialized=!0,this.config={...this.config,...e},e.endpoint&&(this.endpoint=e.endpoint),!this.config.apiKey){console.error("[Senzor RUM] apiKey is required.");return}this.isSampled=Math.random()<=(this.config.sampleRate??1),this.manageSession(),this.startNewTrace(!0),this.setupErrorListeners(),this.setupPerformanceObservers(),this.setupUXListeners(),this.isSampled&&this.patchNetwork(),this.flushInterval=setInterval(()=>this.flush(),5e3),this.setupRoutingListeners()}}manageSession(){sessionStorage.getItem("sz_rum_sid")||sessionStorage.setItem("sz_rum_sid",v()),this.sessionId=sessionStorage.getItem("sz_rum_sid")}startNewTrace(e){this.traceId=x(32),this.traceStartTime=Date.now(),this.isInitialLoad=e,this.vitals={},this.frustrations={rageClicks:0,deadClicks:0,errorCount:0}}addBreadcrumb(e,t,i){this.breadcrumbs.push({type:e,message:t,data:i,time:Date.now()}),this.breadcrumbs.length>20&&this.breadcrumbs.shift()}setupUXListeners(){document.addEventListener("click",e=>{let t=e.target,i=t.tagName?t.tagName.toLowerCase():"";this.addBreadcrumb("click",`Clicked ${i}${t.id?"#"+t.id:""}${t.className?"."+t.className.split(" ")[0]:""}`),["a","button","input","select","textarea","label"].includes(i)||t.closest("button")||t.closest("a")||t.hasAttribute("role")||t.onclick||this.frustrations.deadClicks++;let n=Date.now();if(this.clickHistory.push({x:e.clientX,y:e.clientY,time:n}),this.clickHistory=this.clickHistory.filter(r=>n-r.time<1e3),this.clickHistory.length>=3){let r=this.clickHistory[0],o=!0;for(let p=1;p<this.clickHistory.length;p++){let u=Math.abs(this.clickHistory[p].x-r.x),h=Math.abs(this.clickHistory[p].y-r.y);(u>50||h>50)&&(o=!1)}o&&(this.frustrations.rageClicks++,this.addBreadcrumb("frustration","Rage Click Detected"),this.clickHistory=[])}},{capture:!0,passive:!0})}setupPerformanceObservers(){if(!(!this.isSampled||typeof PerformanceObserver>"u"))try{new PerformanceObserver(t=>{for(let i of t.getEntriesByName("first-contentful-paint"))this.vitals.fcp=i.startTime}).observe({type:"paint",buffered:!0}),new PerformanceObserver(t=>{let i=t.getEntries(),a=i[i.length-1];a&&(this.vitals.lcp=a.startTime)}).observe({type:"largest-contentful-paint",buffered:!0});let e=0;new PerformanceObserver(t=>{for(let i of t.getEntries())i.hadRecentInput||(e+=i.value,this.vitals.cls=e)}).observe({type:"layout-shift",buffered:!0}),new PerformanceObserver(t=>{for(let i of t.getEntries()){let a=i,l=a.duration||(a.processingStart&&a.startTime?a.processingStart-a.startTime:0);(!this.vitals.inp||l>this.vitals.inp)&&(this.vitals.inp=l)}}).observe({type:"event",buffered:!0,durationThreshold:40})}catch{}}getNavigationTimings(){if(typeof performance>"u")return{};let e=performance.getEntriesByType("navigation")[0];return e?{dns:Math.max(0,e.domainLookupEnd-e.domainLookupStart),tcp:Math.max(0,e.connectEnd-e.connectStart),ssl:e.secureConnectionStart?Math.max(0,e.requestStart-e.secureConnectionStart):0,ttfb:Math.max(0,e.responseStart-e.requestStart),domInteractive:Math.max(0,e.domInteractive-e.startTime),domComplete:Math.max(0,e.domComplete-e.startTime)}:{}}shouldAttachTraceHeader(e){if(!this.config.allowedOrigins||this.config.allowedOrigins.length===0)return!1;try{let t=new URL(e,window.location.origin);return this.config.allowedOrigins.some(i=>typeof i=="string"?t.origin.includes(i):i instanceof RegExp?i.test(t.origin):!1)}catch{return!1}}patchNetwork(){let e=this,t=XMLHttpRequest.prototype.open,i=XMLHttpRequest.prototype.send,a=XMLHttpRequest.prototype.setRequestHeader;XMLHttpRequest.prototype.open=function(n,r,...o){return this.__szMethod=n.toUpperCase(),this.__szUrl=r,this.__szHeaders={},t.apply(this,[n,r,...o])},XMLHttpRequest.prototype.setRequestHeader=function(n,r){return this.__szHeaders||(this.__szHeaders={}),this.__szHeaders[n]=r,a.apply(this,[n,r])},XMLHttpRequest.prototype.send=function(n){let r=this,o=x(16),p=Date.now()-e.traceStartTime,u=r.__szMethod,h=r.__szUrl;try{h=new URL(r.__szUrl,window.location.origin).toString()}catch{}return e.shouldAttachTraceHeader(h)&&r.setRequestHeader("traceparent",`00-${e.traceId}-${o}-01`),r.addEventListener("loadend",()=>{var w;let g=Date.now()-e.traceStartTime-p,m={};try{m=r.getAllResponseHeaders().trim().split(/[\r\n]+/).reduce((d,S)=>{let b=S.split(": "),f=b.shift(),z=b.join(": ");return f&&(d[f]=z),d},{})}catch{}let y={url:h,method:u,library:"xhr",status:r.status,responseType:r.responseType,requestPayloadSize:_(n),requestHeaders:r.__szHeaders,responseHeaders:m};try{(r.responseType===""||r.responseType==="text")&&(y.responsePayloadSize=(w=r.responseText)==null?void 0:w.length)}catch{}e.spanQueue.push({spanId:o,name:`${u} ${new URL(h,window.location.origin).pathname}`,type:"http",startTime:p,duration:g,status:r.status,meta:y}),e.spanQueue.length>=e.MAX_BATCH_SIZE&&e.flush()}),i.call(this,n)};let l=window.fetch;window.fetch=async function(...n){let r=n[0],o=n[1],p="",u="GET";typeof r=="string"||r instanceof URL?(p=r.toString(),u=((o==null?void 0:o.method)||"GET").toUpperCase()):r instanceof Request&&(p=r.url,u=r.method.toUpperCase());let h=p;try{h=new URL(p,window.location.origin).toString()}catch{}let g=x(16),m=Date.now()-e.traceStartTime,y=R((o==null?void 0:o.headers)||(r instanceof Request?r.headers:{}));if(e.shouldAttachTraceHeader(h)){let c=`00-${e.traceId}-${g}-01`;if(r instanceof Request){let d=new Headers(r.headers);d.set("traceparent",c),n[1]={...o||{},headers:d}}else{let d=new Headers((o==null?void 0:o.headers)||{});d.set("traceparent",c),n[1]={...o||{},headers:d}}y.traceparent=c}let w=(c,d,S)=>{let b=Date.now()-e.traceStartTime-m,f={url:h,method:u,library:"fetch",status:c,requestPayloadSize:_(o==null?void 0:o.body),requestHeaders:y};d&&(f.statusText=d.statusText,f.type=d.type,f.redirected=d.redirected,f.responseHeaders=R(d.headers)),S&&(f.error=S),e.spanQueue.push({spanId:g,name:`${u} ${new URL(h,window.location.origin).pathname}`,type:"http",startTime:m,duration:b,status:c,meta:f}),e.spanQueue.length>=e.MAX_BATCH_SIZE&&e.flush()};try{let c=await l.apply(this,n);return w(c.status,c),c}catch(c){throw w(0,void 0,c instanceof Error?c.message:String(c)),c}}}setupErrorListeners(){let e=(t,i)=>{this.frustrations.errorCount++;let a=t.message||String(t);this.errorQueue.push({errorClass:t.name||"Error",message:a,stackTrace:t.stack||"",traceId:this.isSampled?this.traceId:void 0,context:{type:i,...H(),breadcrumbs:[...this.breadcrumbs]},timestamp:new Date().toISOString()}),this.flush()};window.addEventListener("error",t=>{t.error&&e(t.error,"Uncaught Exception")}),window.addEventListener("unhandledrejection",t=>{e(t.reason instanceof Error?t.reason:new Error(String(t.reason)),"Unhandled Promise Rejection")})}setupRoutingListeners(){let e=history.pushState;history.pushState=(...t)=>{this.flush(),e.apply(history,t),this.startNewTrace(!1),this.addBreadcrumb("navigation",window.location.pathname)},window.addEventListener("popstate",()=>{this.flush(),this.startNewTrace(!1),this.addBreadcrumb("navigation",window.location.pathname)}),document.addEventListener("visibilitychange",()=>{document.visibilityState==="hidden"&&this.flush()}),window.addEventListener("pagehide",()=>this.flush())}flush(){if(this.spanQueue.length===0&&this.errorQueue.length===0&&!this.isInitialLoad)return;let e=this.spanQueue.splice(0,this.MAX_BATCH_SIZE),t=this.errorQueue.splice(0,20),i={traces:[],errors:t};if(this.isSampled&&i.traces.push({traceId:this.traceId,sessionId:this.sessionId,traceType:this.isInitialLoad?"initial_load":"route_change",path:window.location.pathname,referrer:document.referrer||"",vitals:{...this.vitals},timings:this.isInitialLoad?this.getNavigationTimings():{},frustration:{...this.frustrations},...H(),spans:e,duration:Date.now()-this.traceStartTime,timestamp:new Date(this.traceStartTime).toISOString()}),this.isInitialLoad=!1,i.traces.length>0||i.errors.length>0){let a=new Blob([JSON.stringify(i)],{type:"application/json"}),l=this.endpoint.includes("?")?"&":"?",n=`${this.endpoint}${l}apiKey=${this.config.apiKey}`;navigator.sendBeacon&&a.size<6e4?navigator.sendBeacon(n,a):fetch(n,{method:"POST",body:a,keepalive:!0,headers:{"x-service-api-key":this.config.apiKey}}).catch(()=>{})}}};var k=new I,L=new T,E={init:s=>k.init(s),initRum:s=>L.init(s)};typeof window<"u"&&(window.Senzor=E);export{k as Analytics,L as RUM,E as Senzor};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@senzops/web",
3
- "version": "1.3.2",
3
+ "version": "1.3.3",
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/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,4 @@
1
- import { generateHex, generateUUID, getBrowserContext, getPayloadSize } from './utils';
1
+ import { generateHex, generateUUID, getBrowserContext, getPayloadSize, extractHeaders } from './utils';
2
2
 
3
3
  export interface RumConfig {
4
4
  apiKey: string;
@@ -19,16 +19,16 @@ export class SenzorRumAgent {
19
19
  private traceStartTime: number = 0;
20
20
  private isInitialLoad: boolean = true;
21
21
 
22
- // Buffers
23
- private spans: any[] = [];
24
- private errors: any[] = [];
25
- private breadcrumbs: any[] = [];
22
+ // --- BATCHING QUEUES ---
23
+ private spanQueue: any[] = [];
24
+ private errorQueue: any[] = [];
26
25
  private vitals: any = {};
26
+ private breadcrumbs: any[] = [];
27
27
  private frustrations = { rageClicks: 0, deadClicks: 0, errorCount: 0 };
28
28
  private clickHistory: { x: number; y: number; time: number }[] = [];
29
29
 
30
- // Intervals
31
30
  private flushInterval: any;
31
+ private readonly MAX_BATCH_SIZE = 50;
32
32
 
33
33
  public init(config: RumConfig) {
34
34
  if (this.initialized) return;
@@ -41,7 +41,6 @@ export class SenzorRumAgent {
41
41
  return;
42
42
  }
43
43
 
44
- // Determine Sampling (Errors are ALWAYS 100% sampled, only Traces drop)
45
44
  this.isSampled = Math.random() <= (this.config.sampleRate ?? 1.0);
46
45
 
47
46
  this.manageSession();
@@ -52,10 +51,8 @@ export class SenzorRumAgent {
52
51
  this.setupUXListeners();
53
52
  if (this.isSampled) this.patchNetwork();
54
53
 
55
- // Micro-batch flush every 10s
56
- this.flushInterval = setInterval(() => this.flush(), 10000);
57
-
58
- // SPA and Unload Listeners
54
+ // Background Worker: Flushes the queue every 5 seconds
55
+ this.flushInterval = setInterval(() => this.flush(), 5000);
59
56
  this.setupRoutingListeners();
60
57
  }
61
58
 
@@ -67,18 +64,16 @@ export class SenzorRumAgent {
67
64
  }
68
65
 
69
66
  private startNewTrace(isInitialLoad: boolean) {
70
- this.traceId = generateHex(32); // W3C Standard Trace ID
67
+ this.traceId = generateHex(32);
71
68
  this.traceStartTime = Date.now();
72
69
  this.isInitialLoad = isInitialLoad;
73
- this.spans = [];
74
70
  this.vitals = {};
75
71
  this.frustrations = { rageClicks: 0, deadClicks: 0, errorCount: 0 };
76
72
  }
77
73
 
78
- // --- Breadcrumbs (For Error Context) ---
79
74
  private addBreadcrumb(type: string, message: string, data?: any) {
80
75
  this.breadcrumbs.push({ type, message, data, time: Date.now() });
81
- if (this.breadcrumbs.length > 15) this.breadcrumbs.shift(); // Keep last 15 actions
76
+ if (this.breadcrumbs.length > 20) this.breadcrumbs.shift();
82
77
  }
83
78
 
84
79
  // --- 1. UX Frustration Detection ---
@@ -87,17 +82,12 @@ export class SenzorRumAgent {
87
82
  const target = e.target as HTMLElement;
88
83
  const tag = target.tagName ? target.tagName.toLowerCase() : '';
89
84
 
90
- // Breadcrumb
91
85
  this.addBreadcrumb('click', `Clicked ${tag}${target.id ? '#' + target.id : ''}${target.className ? '.' + target.className.split(' ')[0] : ''}`);
92
86
 
93
- // Dead Click Heuristic
94
87
  const interactiveElements = ['a', 'button', 'input', 'select', 'textarea', 'label'];
95
88
  const isInteractive = interactiveElements.includes(tag) || target.closest('button') || target.closest('a') || target.hasAttribute('role') || target.onclick;
96
- if (!isInteractive) {
97
- this.frustrations.deadClicks++;
98
- }
89
+ if (!isInteractive) this.frustrations.deadClicks++;
99
90
 
100
- // Rage Click Heuristic
101
91
  const now = Date.now();
102
92
  this.clickHistory.push({ x: e.clientX, y: e.clientY, time: now });
103
93
  this.clickHistory = this.clickHistory.filter(c => now - c.time < 1000);
@@ -113,7 +103,7 @@ export class SenzorRumAgent {
113
103
  if (isRage) {
114
104
  this.frustrations.rageClicks++;
115
105
  this.addBreadcrumb('frustration', 'Rage Click Detected');
116
- this.clickHistory = []; // Reset
106
+ this.clickHistory = [];
117
107
  }
118
108
  }
119
109
  }, { capture: true, passive: true });
@@ -156,9 +146,7 @@ export class SenzorRumAgent {
156
146
  }
157
147
  }).observe({ type: 'event', buffered: true, durationThreshold: 40 } as any);
158
148
 
159
- } catch (e) {
160
- // Browser doesn't support specific observer type, degrade gracefully
161
- }
149
+ } catch (e) { }
162
150
  }
163
151
 
164
152
  private getNavigationTimings() {
@@ -195,7 +183,7 @@ export class SenzorRumAgent {
195
183
  // --- Patch XHR ---
196
184
  const originalXhrOpen = XMLHttpRequest.prototype.open;
197
185
  const originalXhrSend = XMLHttpRequest.prototype.send;
198
- const originalXhrSetRequestHeader = XMLHttpRequest.prototype.setRequestHeader;
186
+ const originalXhrSetReqHeader = XMLHttpRequest.prototype.setRequestHeader;
199
187
 
200
188
  XMLHttpRequest.prototype.open = function (method: string, url: string, ...rest: any[]) {
201
189
  (this as any).__szMethod = method.toUpperCase();
@@ -205,8 +193,9 @@ export class SenzorRumAgent {
205
193
  };
206
194
 
207
195
  XMLHttpRequest.prototype.setRequestHeader = function (header: string, value: string) {
196
+ if (!(this as any).__szHeaders) (this as any).__szHeaders = {};
208
197
  (this as any).__szHeaders[header] = value;
209
- return originalXhrSetRequestHeader.call(this, header, value);
198
+ return originalXhrSetReqHeader.apply(this, [header, value]);
210
199
  };
211
200
 
212
201
  XMLHttpRequest.prototype.send = function (body?: Document | XMLHttpRequestBodyInit | null) {
@@ -216,9 +205,7 @@ export class SenzorRumAgent {
216
205
  const method = xhr.__szMethod;
217
206
  let fullUrl = xhr.__szUrl;
218
207
 
219
- try {
220
- fullUrl = new URL(xhr.__szUrl, window.location.origin).toString();
221
- } catch (e) { /* ignore */ }
208
+ try { fullUrl = new URL(xhr.__szUrl, window.location.origin).toString(); } catch (e) { }
222
209
 
223
210
  if (self.shouldAttachTraceHeader(fullUrl)) {
224
211
  xhr.setRequestHeader('traceparent', `00-${self.traceId}-${spanId}-01`);
@@ -227,22 +214,37 @@ export class SenzorRumAgent {
227
214
  xhr.addEventListener('loadend', () => {
228
215
  const duration = (Date.now() - self.traceStartTime) - startTime;
229
216
 
230
- // Capture Verbose Metadata for XHR
217
+ let responseHeaders = {};
218
+ try {
219
+ const rawHeaders = xhr.getAllResponseHeaders();
220
+ responseHeaders = rawHeaders.trim().split(/[\r\n]+/).reduce((acc: any, line: string) => {
221
+ const parts = line.split(': ');
222
+ const header = parts.shift();
223
+ const value = parts.join(': ');
224
+ if (header) acc[header] = value;
225
+ return acc;
226
+ }, {});
227
+ } catch (e) { }
228
+
231
229
  const meta: any = {
232
230
  url: fullUrl,
233
- method: method,
231
+ method,
234
232
  library: 'xhr',
235
233
  status: xhr.status,
236
234
  responseType: xhr.responseType,
237
- requestPayloadSize: getPayloadSize(body)
235
+ requestPayloadSize: getPayloadSize(body),
236
+ requestHeaders: xhr.__szHeaders,
237
+ responseHeaders
238
238
  };
239
239
 
240
240
  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 */ }
241
+ if (xhr.responseType === '' || xhr.responseType === 'text') {
242
+ meta.responsePayloadSize = xhr.responseText?.length;
243
+ }
244
+ } catch (e) { }
244
245
 
245
- self.spans.push({
246
+ // Queue Span
247
+ self.spanQueue.push({
246
248
  spanId,
247
249
  name: `${method} ${new URL(fullUrl, window.location.origin).pathname}`,
248
250
  type: 'http',
@@ -251,6 +253,8 @@ export class SenzorRumAgent {
251
253
  status: xhr.status,
252
254
  meta
253
255
  });
256
+
257
+ if (self.spanQueue.length >= self.MAX_BATCH_SIZE) self.flush();
254
258
  });
255
259
 
256
260
  return originalXhrSend.call(this, body);
@@ -279,7 +283,8 @@ export class SenzorRumAgent {
279
283
  const spanId = generateHex(16);
280
284
  const startTime = Date.now() - self.traceStartTime;
281
285
 
282
- // Safely inject traceparent without breaking Streams
286
+ let reqHeadersObj = extractHeaders(init?.headers || (requestInfo instanceof Request ? requestInfo.headers : {}));
287
+
283
288
  if (self.shouldAttachTraceHeader(fullUrl)) {
284
289
  const traceHeader = `00-${self.traceId}-${spanId}-01`;
285
290
  if (requestInfo instanceof Request) {
@@ -291,50 +296,49 @@ export class SenzorRumAgent {
291
296
  currentHeaders.set('traceparent', traceHeader);
292
297
  args[1] = { ...(init || {}), headers: currentHeaders };
293
298
  }
299
+ reqHeadersObj['traceparent'] = traceHeader;
294
300
  }
295
301
 
296
- try {
297
- const response = await originalFetch.apply(this, args);
302
+ const captureSpan = (status: number, response?: Response, errorMsg?: string) => {
298
303
  const duration = (Date.now() - self.traceStartTime) - startTime;
299
304
 
300
- self.spans.push({
305
+ const meta: any = {
306
+ url: fullUrl,
307
+ method,
308
+ library: 'fetch',
309
+ status,
310
+ requestPayloadSize: getPayloadSize(init?.body),
311
+ requestHeaders: reqHeadersObj,
312
+ };
313
+
314
+ if (response) {
315
+ meta.statusText = response.statusText;
316
+ meta.type = response.type;
317
+ meta.redirected = response.redirected;
318
+ meta.responseHeaders = extractHeaders(response.headers);
319
+ }
320
+
321
+ if (errorMsg) meta.error = errorMsg;
322
+
323
+ self.spanQueue.push({
301
324
  spanId,
302
325
  name: `${method} ${new URL(fullUrl, window.location.origin).pathname}`,
303
326
  type: 'http',
304
327
  startTime,
305
328
  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
- }
329
+ status,
330
+ meta
317
331
  });
332
+
333
+ if (self.spanQueue.length >= self.MAX_BATCH_SIZE) self.flush();
334
+ };
335
+
336
+ try {
337
+ const response = await originalFetch.apply(this, args);
338
+ captureSpan(response.status, response);
318
339
  return response;
319
340
  } 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
- });
341
+ captureSpan(0, undefined, error instanceof Error ? error.message : String(error));
338
342
  throw error;
339
343
  }
340
344
  };
@@ -346,7 +350,7 @@ export class SenzorRumAgent {
346
350
  this.frustrations.errorCount++;
347
351
  const message = errorObj.message || String(errorObj);
348
352
 
349
- this.errors.push({
353
+ this.errorQueue.push({
350
354
  errorClass: errorObj.name || 'Error',
351
355
  message: message,
352
356
  stackTrace: errorObj.stack || '',
@@ -354,11 +358,12 @@ export class SenzorRumAgent {
354
358
  context: {
355
359
  type,
356
360
  ...getBrowserContext(),
357
- breadcrumbs: [...this.breadcrumbs] // Snapshot of actions leading up to crash
361
+ breadcrumbs: [...this.breadcrumbs]
358
362
  },
359
363
  timestamp: new Date().toISOString()
360
364
  });
361
- this.flush(); // Flush immediately on error
365
+
366
+ this.flush(); // Flush immediately on critical error
362
367
  };
363
368
 
364
369
  window.addEventListener('error', (event) => {
@@ -374,7 +379,7 @@ export class SenzorRumAgent {
374
379
  private setupRoutingListeners() {
375
380
  const originalPushState = history.pushState;
376
381
  history.pushState = (...args) => {
377
- this.flush(); // Flush previous page view
382
+ this.flush();
378
383
  originalPushState.apply(history, args);
379
384
  this.startNewTrace(false);
380
385
  this.addBreadcrumb('navigation', window.location.pathname);
@@ -394,9 +399,12 @@ export class SenzorRumAgent {
394
399
  }
395
400
 
396
401
  private flush() {
397
- if (this.spans.length === 0 && this.errors.length === 0 && !this.isInitialLoad) return;
402
+ if (this.spanQueue.length === 0 && this.errorQueue.length === 0 && !this.isInitialLoad) return;
398
403
 
399
- const payload: any = { traces: [], errors: this.errors };
404
+ const spansToSend = this.spanQueue.splice(0, this.MAX_BATCH_SIZE);
405
+ const errorsToSend = this.errorQueue.splice(0, 20);
406
+
407
+ const payload: any = { traces: [], errors: errorsToSend };
400
408
 
401
409
  if (this.isSampled) {
402
410
  payload.traces.push({
@@ -408,27 +416,22 @@ export class SenzorRumAgent {
408
416
  vitals: { ...this.vitals },
409
417
  timings: this.isInitialLoad ? this.getNavigationTimings() : {},
410
418
  frustration: { ...this.frustrations },
411
- ...getBrowserContext(), // URL, UserAgent
412
- spans: [...this.spans],
419
+ ...getBrowserContext(),
420
+ spans: spansToSend,
413
421
  duration: Date.now() - this.traceStartTime,
414
422
  timestamp: new Date(this.traceStartTime).toISOString()
415
423
  });
416
424
  }
417
425
 
418
- // Reset Buffers
419
- this.spans = [];
420
- this.errors = [];
421
- this.frustrations = { rageClicks: 0, deadClicks: 0, errorCount: 0 };
422
426
  this.isInitialLoad = false;
423
427
 
424
428
  if (payload.traces.length > 0 || payload.errors.length > 0) {
425
429
  const blob = new Blob([JSON.stringify(payload)], { type: 'application/json' });
426
430
 
427
- // Safely append API Key to URL for Beacon support
428
431
  const separator = this.endpoint.includes('?') ? '&' : '?';
429
432
  const authUrl = `${this.endpoint}${separator}apiKey=${this.config.apiKey}`;
430
433
 
431
- if (navigator.sendBeacon) {
434
+ if (navigator.sendBeacon && blob.size < 60000) {
432
435
  navigator.sendBeacon(authUrl, blob);
433
436
  } else {
434
437
  fetch(authUrl, {
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
  };