@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 +4 -3
- package/dist/index.d.ts +4 -3
- package/dist/index.global.js +1 -1
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/package.json +1 -1
- package/src/analytics.ts +25 -15
- package/src/index.ts +1 -1
- package/src/rum.ts +87 -84
- package/src/utils.ts +14 -2
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
|
|
38
|
-
private
|
|
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
|
|
38
|
-
private
|
|
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.global.js
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);})();
|
|
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
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('
|
|
37
|
-
|
|
36
|
+
const lastActivity = parseInt(localStorage.getItem('senzor_last_activity') || '0', 10);
|
|
37
|
+
const sessionTimeout = 30 * 60 * 1000; // 30 minutes
|
|
38
38
|
|
|
39
|
-
|
|
40
|
-
|
|
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('
|
|
46
|
+
sessionStorage.setItem('senzor_sid', sessionId);
|
|
43
47
|
this.determineReferrer(true);
|
|
44
48
|
} else {
|
|
45
49
|
this.determineReferrer(false);
|
|
46
50
|
}
|
|
47
|
-
localStorage.setItem('
|
|
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 {
|
|
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 !==
|
|
60
|
-
} else if (isNewSession && !
|
|
61
|
-
sessionStorage.setItem('
|
|
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('
|
|
76
|
+
localStorage.setItem('senzor_last_activity', Date.now().toString());
|
|
67
77
|
return {
|
|
68
|
-
visitorId: localStorage.getItem('
|
|
69
|
-
sessionId: sessionStorage.getItem('
|
|
70
|
-
referrer: sessionStorage.getItem('
|
|
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
|
|
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
|
-
//
|
|
23
|
-
private
|
|
24
|
-
private
|
|
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
|
-
//
|
|
56
|
-
this.flushInterval = setInterval(() => this.flush(),
|
|
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);
|
|
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 >
|
|
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 = [];
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
242
|
-
|
|
243
|
-
|
|
241
|
+
if (xhr.responseType === '' || xhr.responseType === 'text') {
|
|
242
|
+
meta.responsePayloadSize = xhr.responseText?.length;
|
|
243
|
+
}
|
|
244
|
+
} catch (e) { }
|
|
244
245
|
|
|
245
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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.
|
|
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]
|
|
361
|
+
breadcrumbs: [...this.breadcrumbs]
|
|
358
362
|
},
|
|
359
363
|
timestamp: new Date().toISOString()
|
|
360
364
|
});
|
|
361
|
-
|
|
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();
|
|
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.
|
|
402
|
+
if (this.spanQueue.length === 0 && this.errorQueue.length === 0 && !this.isInitialLoad) return;
|
|
398
403
|
|
|
399
|
-
const
|
|
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(),
|
|
412
|
-
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
|
};
|