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