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