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