@senzops/web 1.3.4 → 1.3.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +4 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.global.js +2 -2
- package/dist/index.js +2 -2
- package/dist/index.mjs +2 -2
- package/package.json +1 -1
- package/src/error.ts +43 -90
- package/src/rum.ts +22 -14
package/dist/index.d.mts
CHANGED
|
@@ -41,7 +41,9 @@ declare class SenzorRumAgent {
|
|
|
41
41
|
private frustrations;
|
|
42
42
|
private clickHistory;
|
|
43
43
|
private flushInterval;
|
|
44
|
+
private flushTimeout;
|
|
44
45
|
private readonly MAX_BATCH_SIZE;
|
|
46
|
+
private readonly MAX_QUEUE_MEMORY;
|
|
45
47
|
private errorEngine;
|
|
46
48
|
init(config: RumConfig): void;
|
|
47
49
|
private manageSession;
|
|
@@ -51,8 +53,10 @@ declare class SenzorRumAgent {
|
|
|
51
53
|
private setupPerformanceObservers;
|
|
52
54
|
private getNavigationTimings;
|
|
53
55
|
private shouldAttachTraceHeader;
|
|
56
|
+
private pushSpan;
|
|
54
57
|
private patchNetwork;
|
|
55
58
|
private setupRoutingListeners;
|
|
59
|
+
private debouncedFlush;
|
|
56
60
|
private flush;
|
|
57
61
|
}
|
|
58
62
|
|
package/dist/index.d.ts
CHANGED
|
@@ -41,7 +41,9 @@ declare class SenzorRumAgent {
|
|
|
41
41
|
private frustrations;
|
|
42
42
|
private clickHistory;
|
|
43
43
|
private flushInterval;
|
|
44
|
+
private flushTimeout;
|
|
44
45
|
private readonly MAX_BATCH_SIZE;
|
|
46
|
+
private readonly MAX_QUEUE_MEMORY;
|
|
45
47
|
private errorEngine;
|
|
46
48
|
init(config: RumConfig): void;
|
|
47
49
|
private manageSession;
|
|
@@ -51,8 +53,10 @@ declare class SenzorRumAgent {
|
|
|
51
53
|
private setupPerformanceObservers;
|
|
52
54
|
private getNavigationTimings;
|
|
53
55
|
private shouldAttachTraceHeader;
|
|
56
|
+
private pushSpan;
|
|
54
57
|
private patchNetwork;
|
|
55
58
|
private setupRoutingListeners;
|
|
59
|
+
private debouncedFlush;
|
|
56
60
|
private flush;
|
|
57
61
|
}
|
|
58
62
|
|
package/dist/index.global.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
(()=>{function v(){return typeof crypto<"u"&&crypto.randomUUID?crypto.randomUUID():"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,
|
|
2
|
-
`);
|
|
1
|
+
(()=>{function v(){return typeof crypto<"u"&&crypto.randomUUID?crypto.randomUUID():"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,s=>{let e=Math.random()*16|0;return(s==="x"?e:e&3|8).toString(16)})}function x(s){let e="";for(;e.length<s;)e+=Math.random().toString(16).slice(2);return e.slice(0,s)}var I=()=>{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}},R=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 E=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 T=class{deps;constructor(e){this.deps=e}setup(){this.setupGlobalErrors(),this.setupPromiseErrors(),this.setupReactConsolePatch()}setupGlobalErrors(){window.addEventListener("error",e=>{if(e.error){this.capture(e.error,"Uncaught Exception");return}e.target&&e.target!==window&&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;this.deps.errorQueue.length>=this.deps.queueLimit&&this.deps.errorQueue.shift();let a={type:t,path:location.pathname,referrer:document.referrer||void 0,...I(),breadcrumbs:[...this.deps.breadcrumbs]};this.deps.errorQueue.push({errorClass:e.name||"Error",message:e.message||String(e),stackTrace:e.stack||"",traceId:this.deps.isSampled?this.deps.traceId():void 0,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://"))}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")||n.includes("A cross-origin error was thrown"))){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"),d=c.find(p=>typeof p=="string"&&p.includes(`
|
|
2
|
+
in `));d&&(o.stack=d),this.capture(o,"React Error")}}catch{}return t.apply(console,c)}}};var _=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;flushTimeout=null;MAX_BATCH_SIZE=50;MAX_QUEUE_MEMORY=500;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 T({isSampled:this.isSampled,traceId:()=>this.traceId,sessionId:this.sessionId,breadcrumbs:this.breadcrumbs,frustrations:this.frustrations,errorQueue:this.errorQueue,queueLimit:this.MAX_QUEUE_MEMORY,flush:()=>this.debouncedFlush()}),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=x(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 d=1;d<this.clickHistory.length;d++){let p=Math.abs(this.clickHistory[d].x-i.x),h=Math.abs(this.clickHistory[d].y-i.y);(p>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 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}}pushSpan(e){this.spanQueue.length>=this.MAX_QUEUE_MEMORY&&this.spanQueue.shift(),this.spanQueue.push(e),this.spanQueue.length>=this.MAX_BATCH_SIZE&&this.debouncedFlush()}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=x(16),d=Date.now()-e.traceStartTime,p=i.__szMethod,h=i.__szUrl;try{h=new URL(i.__szUrl,window.location.origin).toString()}catch{}return e.shouldAttachTraceHeader(h)&&i.setRequestHeader("traceparent",`00-${e.traceId}-${o}-01`),i.addEventListener("loadend",()=>{var w;let g=Date.now()-e.traceStartTime-d,m={};try{m=i.getAllResponseHeaders().trim().split(/[\r\n]+/).reduce((u,S)=>{let b=S.split(": "),f=b.shift(),H=b.join(": ");return f&&(u[f]=H),u},{})}catch{}let y={url:h,method:p,library:"xhr",status:i.status,responseType:i.responseType,requestPayloadSize:R(n),requestHeaders:i.__szHeaders,responseHeaders:m};try{(i.responseType===""||i.responseType==="text")&&(y.responsePayloadSize=(w=i.responseText)==null?void 0:w.length)}catch{}e.pushSpan({spanId:o,name:`${p} ${new URL(h,window.location.origin).pathname}`,type:"http",startTime:d,duration:g,status:i.status,meta:y})}),r.call(this,n)};let c=window.fetch;window.fetch=async function(...n){let i=n[0],o=n[1],d="",p="GET";typeof i=="string"||i instanceof URL?(d=i.toString(),p=((o==null?void 0:o.method)||"GET").toUpperCase()):i instanceof Request&&(d=i.url,p=i.method.toUpperCase());let h=d;try{h=new URL(d,window.location.origin).toString()}catch{}let g=x(16),m=Date.now()-e.traceStartTime,y=z((o==null?void 0:o.headers)||(i instanceof Request?i.headers:{}));if(e.shouldAttachTraceHeader(h)){let l=`00-${e.traceId}-${g}-01`;if(i instanceof Request){let u=new Headers(i.headers);u.set("traceparent",l),n[1]={...o||{},headers:u}}else{let u=new Headers((o==null?void 0:o.headers)||{});u.set("traceparent",l),n[1]={...o||{},headers:u}}y.traceparent=l}let w=(l,u,S)=>{let b=Date.now()-e.traceStartTime-m,f={url:h,method:p,library:"fetch",status:l,requestPayloadSize:R(o==null?void 0:o.body),requestHeaders:y};u&&(f.statusText=u.statusText,f.type=u.type,f.redirected=u.redirected,f.responseHeaders=z(u.headers)),S&&(f.error=S),e.pushSpan({spanId:g,name:`${p} ${new URL(h,window.location.origin).pathname}`,type:"http",startTime:m,duration:b,status:l,meta:f})};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())}debouncedFlush(){this.flushTimeout&&clearTimeout(this.flushTimeout),this.flushTimeout=setTimeout(()=>this.flush(),100)}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},...I(),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 E,C=new _,L={init:s=>k.init(s),initRum:s=>C.init(s)};typeof window<"u"&&(window.Senzor=L);})();
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
var
|
|
2
|
-
`);
|
|
1
|
+
var R=Object.defineProperty;var M=Object.getOwnPropertyDescriptor;var D=Object.getOwnPropertyNames;var P=Object.prototype.hasOwnProperty;var A=(i,e)=>{for(var t in e)R(i,t,{get:e[t],enumerable:!0})},q=(i,e,t,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of D(e))!P.call(i,n)&&n!==t&&R(i,n,{get:()=>e[n],enumerable:!(r=M(e,n))||r.enumerable});return i};var O=i=>q(R({},"__esModule",{value:!0}),i);var B={};A(B,{Analytics:()=>k,RUM:()=>C,Senzor:()=>L});module.exports=O(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 x(i){let e="";for(;e.length<i;)e+=Math.random().toString(16).slice(2);return e.slice(0,i)}var I=()=>{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 E=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 T=class{deps;constructor(e){this.deps=e}setup(){this.setupGlobalErrors(),this.setupPromiseErrors(),this.setupReactConsolePatch()}setupGlobalErrors(){window.addEventListener("error",e=>{if(e.error){this.capture(e.error,"Uncaught Exception");return}e.target&&e.target!==window&&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;this.deps.errorQueue.length>=this.deps.queueLimit&&this.deps.errorQueue.shift();let n={type:t,path:location.pathname,referrer:document.referrer||void 0,...I(),breadcrumbs:[...this.deps.breadcrumbs]};this.deps.errorQueue.push({errorClass:e.name||"Error",message:e.message||String(e),stackTrace:e.stack||"",traceId:this.deps.isSampled?this.deps.traceId():void 0,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://"))}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")||a.includes("A cross-origin error was thrown"))){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"),d=c.find(p=>typeof p=="string"&&p.includes(`
|
|
2
|
+
in `));d&&(o.stack=d),this.capture(o,"React Error")}}catch{}return t.apply(console,c)}}};var _=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;flushTimeout=null;MAX_BATCH_SIZE=50;MAX_QUEUE_MEMORY=500;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 T({isSampled:this.isSampled,traceId:()=>this.traceId,sessionId:this.sessionId,breadcrumbs:this.breadcrumbs,frustrations:this.frustrations,errorQueue:this.errorQueue,queueLimit:this.MAX_QUEUE_MEMORY,flush:()=>this.debouncedFlush()}),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=x(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 d=1;d<this.clickHistory.length;d++){let p=Math.abs(this.clickHistory[d].x-s.x),h=Math.abs(this.clickHistory[d].y-s.y);(p>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 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}}pushSpan(e){this.spanQueue.length>=this.MAX_QUEUE_MEMORY&&this.spanQueue.shift(),this.spanQueue.push(e),this.spanQueue.length>=this.MAX_BATCH_SIZE&&this.debouncedFlush()}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=x(16),d=Date.now()-e.traceStartTime,p=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-d,m={};try{m=s.getAllResponseHeaders().trim().split(/[\r\n]+/).reduce((u,S)=>{let b=S.split(": "),f=b.shift(),U=b.join(": ");return f&&(u[f]=U),u},{})}catch{}let y={url:h,method:p,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.pushSpan({spanId:o,name:`${p} ${new URL(h,window.location.origin).pathname}`,type:"http",startTime:d,duration:g,status:s.status,meta:y})}),r.call(this,a)};let c=window.fetch;window.fetch=async function(...a){let s=a[0],o=a[1],d="",p="GET";typeof s=="string"||s instanceof URL?(d=s.toString(),p=((o==null?void 0:o.method)||"GET").toUpperCase()):s instanceof Request&&(d=s.url,p=s.method.toUpperCase());let h=d;try{h=new URL(d,window.location.origin).toString()}catch{}let g=x(16),m=Date.now()-e.traceStartTime,y=H((o==null?void 0:o.headers)||(s instanceof Request?s.headers:{}));if(e.shouldAttachTraceHeader(h)){let l=`00-${e.traceId}-${g}-01`;if(s instanceof Request){let u=new Headers(s.headers);u.set("traceparent",l),a[1]={...o||{},headers:u}}else{let u=new Headers((o==null?void 0:o.headers)||{});u.set("traceparent",l),a[1]={...o||{},headers:u}}y.traceparent=l}let w=(l,u,S)=>{let b=Date.now()-e.traceStartTime-m,f={url:h,method:p,library:"fetch",status:l,requestPayloadSize:z(o==null?void 0:o.body),requestHeaders:y};u&&(f.statusText=u.statusText,f.type=u.type,f.redirected=u.redirected,f.responseHeaders=H(u.headers)),S&&(f.error=S),e.pushSpan({spanId:g,name:`${p} ${new URL(h,window.location.origin).pathname}`,type:"http",startTime:m,duration:b,status:l,meta:f})};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())}debouncedFlush(){this.flushTimeout&&clearTimeout(this.flushTimeout),this.flushTimeout=setTimeout(()=>this.flush(),100)}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},...I(),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 E,C=new _,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,2 +1,2 @@
|
|
|
1
|
-
function v(){return typeof crypto<"u"&&crypto.randomUUID?crypto.randomUUID():"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,
|
|
2
|
-
`);
|
|
1
|
+
function v(){return typeof crypto<"u"&&crypto.randomUUID?crypto.randomUUID():"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,s=>{let e=Math.random()*16|0;return(s==="x"?e:e&3|8).toString(16)})}function x(s){let e="";for(;e.length<s;)e+=Math.random().toString(16).slice(2);return e.slice(0,s)}var I=()=>{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}},R=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 E=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 T=class{deps;constructor(e){this.deps=e}setup(){this.setupGlobalErrors(),this.setupPromiseErrors(),this.setupReactConsolePatch()}setupGlobalErrors(){window.addEventListener("error",e=>{if(e.error){this.capture(e.error,"Uncaught Exception");return}e.target&&e.target!==window&&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;this.deps.errorQueue.length>=this.deps.queueLimit&&this.deps.errorQueue.shift();let a={type:t,path:location.pathname,referrer:document.referrer||void 0,...I(),breadcrumbs:[...this.deps.breadcrumbs]};this.deps.errorQueue.push({errorClass:e.name||"Error",message:e.message||String(e),stackTrace:e.stack||"",traceId:this.deps.isSampled?this.deps.traceId():void 0,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://"))}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")||n.includes("A cross-origin error was thrown"))){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"),d=c.find(p=>typeof p=="string"&&p.includes(`
|
|
2
|
+
in `));d&&(o.stack=d),this.capture(o,"React Error")}}catch{}return t.apply(console,c)}}};var _=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;flushTimeout=null;MAX_BATCH_SIZE=50;MAX_QUEUE_MEMORY=500;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 T({isSampled:this.isSampled,traceId:()=>this.traceId,sessionId:this.sessionId,breadcrumbs:this.breadcrumbs,frustrations:this.frustrations,errorQueue:this.errorQueue,queueLimit:this.MAX_QUEUE_MEMORY,flush:()=>this.debouncedFlush()}),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=x(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 d=1;d<this.clickHistory.length;d++){let p=Math.abs(this.clickHistory[d].x-i.x),h=Math.abs(this.clickHistory[d].y-i.y);(p>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 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}}pushSpan(e){this.spanQueue.length>=this.MAX_QUEUE_MEMORY&&this.spanQueue.shift(),this.spanQueue.push(e),this.spanQueue.length>=this.MAX_BATCH_SIZE&&this.debouncedFlush()}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=x(16),d=Date.now()-e.traceStartTime,p=i.__szMethod,h=i.__szUrl;try{h=new URL(i.__szUrl,window.location.origin).toString()}catch{}return e.shouldAttachTraceHeader(h)&&i.setRequestHeader("traceparent",`00-${e.traceId}-${o}-01`),i.addEventListener("loadend",()=>{var w;let g=Date.now()-e.traceStartTime-d,m={};try{m=i.getAllResponseHeaders().trim().split(/[\r\n]+/).reduce((u,S)=>{let b=S.split(": "),f=b.shift(),H=b.join(": ");return f&&(u[f]=H),u},{})}catch{}let y={url:h,method:p,library:"xhr",status:i.status,responseType:i.responseType,requestPayloadSize:R(n),requestHeaders:i.__szHeaders,responseHeaders:m};try{(i.responseType===""||i.responseType==="text")&&(y.responsePayloadSize=(w=i.responseText)==null?void 0:w.length)}catch{}e.pushSpan({spanId:o,name:`${p} ${new URL(h,window.location.origin).pathname}`,type:"http",startTime:d,duration:g,status:i.status,meta:y})}),r.call(this,n)};let c=window.fetch;window.fetch=async function(...n){let i=n[0],o=n[1],d="",p="GET";typeof i=="string"||i instanceof URL?(d=i.toString(),p=((o==null?void 0:o.method)||"GET").toUpperCase()):i instanceof Request&&(d=i.url,p=i.method.toUpperCase());let h=d;try{h=new URL(d,window.location.origin).toString()}catch{}let g=x(16),m=Date.now()-e.traceStartTime,y=z((o==null?void 0:o.headers)||(i instanceof Request?i.headers:{}));if(e.shouldAttachTraceHeader(h)){let l=`00-${e.traceId}-${g}-01`;if(i instanceof Request){let u=new Headers(i.headers);u.set("traceparent",l),n[1]={...o||{},headers:u}}else{let u=new Headers((o==null?void 0:o.headers)||{});u.set("traceparent",l),n[1]={...o||{},headers:u}}y.traceparent=l}let w=(l,u,S)=>{let b=Date.now()-e.traceStartTime-m,f={url:h,method:p,library:"fetch",status:l,requestPayloadSize:R(o==null?void 0:o.body),requestHeaders:y};u&&(f.statusText=u.statusText,f.type=u.type,f.redirected=u.redirected,f.responseHeaders=z(u.headers)),S&&(f.error=S),e.pushSpan({spanId:g,name:`${p} ${new URL(h,window.location.origin).pathname}`,type:"http",startTime:m,duration:b,status:l,meta:f})};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())}debouncedFlush(){this.flushTimeout&&clearTimeout(this.flushTimeout),this.flushTimeout=setTimeout(()=>this.flush(),100)}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},...I(),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 E,C=new _,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
CHANGED
|
@@ -7,8 +7,8 @@ export interface ErrorEngineDeps {
|
|
|
7
7
|
breadcrumbs: any[];
|
|
8
8
|
frustrations: { rageClicks: number; deadClicks: number; errorCount: number };
|
|
9
9
|
errorQueue: any[];
|
|
10
|
+
queueLimit: number;
|
|
10
11
|
flush: () => void;
|
|
11
|
-
getLastNetworkSpan?: () => any; // optional correlation hook
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
export class ErrorEngine {
|
|
@@ -21,7 +21,6 @@ export class ErrorEngine {
|
|
|
21
21
|
public setup() {
|
|
22
22
|
this.setupGlobalErrors();
|
|
23
23
|
this.setupPromiseErrors();
|
|
24
|
-
this.setupReactIntegration();
|
|
25
24
|
this.setupReactConsolePatch();
|
|
26
25
|
}
|
|
27
26
|
|
|
@@ -31,19 +30,12 @@ export class ErrorEngine {
|
|
|
31
30
|
(event: ErrorEvent | any) => {
|
|
32
31
|
// JS runtime errors
|
|
33
32
|
if (event.error) {
|
|
34
|
-
this.capture(event.error, "Uncaught Exception"
|
|
35
|
-
file: event.filename,
|
|
36
|
-
line: event.lineno,
|
|
37
|
-
column: event.colno,
|
|
38
|
-
});
|
|
33
|
+
this.capture(event.error, "Uncaught Exception");
|
|
39
34
|
return;
|
|
40
|
-
}
|
|
35
|
+
}
|
|
36
|
+
// Resource errors (script, css, img, font etc)
|
|
41
37
|
if (event.target && event.target !== window) {
|
|
42
|
-
|
|
43
|
-
this.capture(new Error("Resource failed to load"), "Resource Error", {
|
|
44
|
-
file: el?.src || el?.href || "unknown",
|
|
45
|
-
tag: el?.tagName,
|
|
46
|
-
});
|
|
38
|
+
this.capture(new Error("Resource failed to load"), "Resource Error");
|
|
47
39
|
}
|
|
48
40
|
},
|
|
49
41
|
true,
|
|
@@ -59,72 +51,32 @@ export class ErrorEngine {
|
|
|
59
51
|
|
|
60
52
|
private capture(errorObj: Error, type: string, extra?: any) {
|
|
61
53
|
if (this.shouldIgnore(errorObj)) return;
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
try {
|
|
67
|
-
lastNetwork = this.deps.getLastNetworkSpan
|
|
68
|
-
? this.deps.getLastNetworkSpan()
|
|
69
|
-
: undefined;
|
|
70
|
-
} catch {
|
|
71
|
-
lastNetwork = undefined;
|
|
54
|
+
|
|
55
|
+
// Prevent Memory Leaks on catastrophic infinite loops
|
|
56
|
+
if (this.deps.errorQueue.length >= this.deps.queueLimit) {
|
|
57
|
+
this.deps.errorQueue.shift(); // Drop oldest error
|
|
72
58
|
}
|
|
59
|
+
|
|
73
60
|
const context = {
|
|
74
61
|
type, // location
|
|
75
62
|
path: location.pathname,
|
|
76
|
-
referrer: document.referrer || undefined,
|
|
77
|
-
|
|
78
|
-
sessionId: this.deps.sessionId, // source location
|
|
79
|
-
file: extra?.file || topFrame?.file,
|
|
80
|
-
line: extra?.line || topFrame?.line,
|
|
81
|
-
column: extra?.column || topFrame?.column,
|
|
82
|
-
topFrame, // correlations
|
|
83
|
-
lastInteraction,
|
|
84
|
-
lastNetworkSpan: lastNetwork, // UX
|
|
85
|
-
frustrations: { ...this.deps.frustrations }, // browser
|
|
86
|
-
...getBrowserContext(), // breadcrumbs
|
|
63
|
+
referrer: document.referrer || undefined,
|
|
64
|
+
...getBrowserContext(), // userAgent, url, etc.
|
|
87
65
|
breadcrumbs: [...this.deps.breadcrumbs],
|
|
88
66
|
};
|
|
67
|
+
|
|
89
68
|
this.deps.errorQueue.push({
|
|
90
69
|
errorClass: errorObj.name || "Error",
|
|
91
70
|
message: errorObj.message || String(errorObj),
|
|
92
71
|
stackTrace: errorObj.stack || "",
|
|
72
|
+
// CRITICAL FIX: traceId MUST be at the root for backend polymorphic lookup to work
|
|
73
|
+
traceId: this.deps.isSampled ? this.deps.traceId() : undefined,
|
|
93
74
|
context,
|
|
94
75
|
timestamp: new Date().toISOString(),
|
|
95
76
|
});
|
|
96
|
-
this.deps.flush();
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// --- Stack intelligence ---
|
|
100
|
-
private extractTopFrame(error: Error) {
|
|
101
|
-
if (!error.stack) return undefined;
|
|
102
|
-
const lines = error.stack.split("\n");
|
|
103
|
-
for (let i = 1; i < lines.length; i++) {
|
|
104
|
-
const line = lines[i];
|
|
105
|
-
const match = line.match(/\(?(.+):(\d+):(\d+)\)?/);
|
|
106
|
-
if (match) {
|
|
107
|
-
return {
|
|
108
|
-
file: match[1],
|
|
109
|
-
line: Number(match[2]),
|
|
110
|
-
column: Number(match[3]),
|
|
111
|
-
raw: line.trim(),
|
|
112
|
-
};
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
return undefined;
|
|
116
|
-
}
|
|
117
77
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
if (!this.deps.breadcrumbs.length) return undefined;
|
|
121
|
-
for (let i = this.deps.breadcrumbs.length - 1; i >= 0; i--) {
|
|
122
|
-
const crumb = this.deps.breadcrumbs[i];
|
|
123
|
-
if (crumb.type === "click") {
|
|
124
|
-
return crumb;
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
return undefined;
|
|
78
|
+
// This triggers the debounced flush in rum.ts
|
|
79
|
+
this.deps.flush();
|
|
128
80
|
}
|
|
129
81
|
|
|
130
82
|
private normalizeError(reason: any): Error {
|
|
@@ -139,60 +91,61 @@ export class ErrorEngine {
|
|
|
139
91
|
}
|
|
140
92
|
|
|
141
93
|
private shouldIgnore(error: Error) {
|
|
142
|
-
const stack = error.stack || "";
|
|
94
|
+
const stack = error.stack || "";
|
|
95
|
+
// Ignore browser extensions noise to keep dashboard clean
|
|
143
96
|
if (stack.includes("chrome-extension://")) return true;
|
|
144
97
|
if (stack.includes("moz-extension://")) return true;
|
|
145
98
|
if (stack.includes("safari-extension://")) return true;
|
|
146
99
|
return false;
|
|
147
100
|
}
|
|
148
101
|
|
|
149
|
-
private setupReactIntegration() {
|
|
150
|
-
const hook = (window as any).__REACT_DEVTOOLS_GLOBAL_HOOK__;
|
|
151
|
-
if (!hook) return;
|
|
152
|
-
// Prevent multiple patches
|
|
153
|
-
if (hook.__senzor_patched) return;
|
|
154
|
-
hook.__senzor_patched = true;
|
|
155
|
-
const orig = hook.onCommitFiberRoot;
|
|
156
|
-
hook.onCommitFiberRoot = (id: any, root: any, ...rest: any[]) => {
|
|
157
|
-
try {
|
|
158
|
-
// We don't depend on React internals.
|
|
159
|
-
// Only detecting React presence safely.
|
|
160
|
-
} catch {}
|
|
161
|
-
if (orig) {
|
|
162
|
-
return orig.apply(hook, [id, root, ...rest]);
|
|
163
|
-
}
|
|
164
|
-
};
|
|
165
|
-
}
|
|
166
|
-
|
|
167
102
|
private setupReactConsolePatch() {
|
|
168
103
|
const consoleAny: any = console;
|
|
169
|
-
// Prevent multiple patches
|
|
104
|
+
// Prevent multiple patches from hot-reloading
|
|
170
105
|
if (consoleAny.__senzor_react_patch) return;
|
|
171
106
|
consoleAny.__senzor_react_patch = true;
|
|
107
|
+
|
|
172
108
|
const original = console.error;
|
|
109
|
+
|
|
173
110
|
// Prevent duplicate React StrictMode errors
|
|
174
111
|
let lastReactError = "";
|
|
175
112
|
let lastReactErrorTime = 0;
|
|
113
|
+
|
|
176
114
|
console.error = (...args: any[]) => {
|
|
177
115
|
try {
|
|
178
116
|
if (!args || !args.length) return original.apply(console, args);
|
|
179
117
|
const first = args[0];
|
|
180
|
-
|
|
118
|
+
|
|
119
|
+
// React component crash pattern string matching
|
|
181
120
|
if (typeof first === "string") {
|
|
182
|
-
if (
|
|
121
|
+
if (
|
|
122
|
+
first.includes("The above error occurred") ||
|
|
123
|
+
first.includes("A cross-origin error was thrown")
|
|
124
|
+
) {
|
|
183
125
|
const now = Date.now();
|
|
184
|
-
|
|
126
|
+
|
|
127
|
+
// Prevent duplicates (React strict mode throws twice)
|
|
185
128
|
if (first === lastReactError && now - lastReactErrorTime < 2000) {
|
|
186
129
|
return original.apply(console, args);
|
|
187
130
|
}
|
|
131
|
+
|
|
188
132
|
lastReactError = first;
|
|
189
133
|
lastReactErrorTime = now;
|
|
190
|
-
|
|
134
|
+
|
|
135
|
+
const error = new Error("React Component Crash");
|
|
136
|
+
// React usually passes the component stack in args[1] or args[2] depending on version
|
|
137
|
+
const componentStack = args.find(
|
|
138
|
+
(a) => typeof a === "string" && a.includes("\n in "),
|
|
139
|
+
);
|
|
140
|
+
if (componentStack) {
|
|
141
|
+
error.stack = componentStack;
|
|
142
|
+
}
|
|
143
|
+
|
|
191
144
|
this.capture(error, "React Error");
|
|
192
145
|
}
|
|
193
146
|
}
|
|
194
147
|
} catch {
|
|
195
|
-
// Never break console
|
|
148
|
+
// Never break the user's console logging
|
|
196
149
|
}
|
|
197
150
|
return original.apply(console, args);
|
|
198
151
|
};
|
package/src/rum.ts
CHANGED
|
@@ -30,7 +30,7 @@ export class SenzorRumAgent {
|
|
|
30
30
|
private traceStartTime: number = 0;
|
|
31
31
|
private isInitialLoad: boolean = true;
|
|
32
32
|
|
|
33
|
-
// --- BATCHING QUEUES ---
|
|
33
|
+
// --- BATCHING QUEUES & LIMITS ---
|
|
34
34
|
private spanQueue: any[] = [];
|
|
35
35
|
private errorQueue: any[] = [];
|
|
36
36
|
private vitals: any = {};
|
|
@@ -39,7 +39,9 @@ export class SenzorRumAgent {
|
|
|
39
39
|
private clickHistory: { x: number; y: number; time: number }[] = [];
|
|
40
40
|
|
|
41
41
|
private flushInterval: any;
|
|
42
|
+
private flushTimeout: any = null; // Used for debouncing burst errors
|
|
42
43
|
private readonly MAX_BATCH_SIZE = 50;
|
|
44
|
+
private readonly MAX_QUEUE_MEMORY = 500; // Prevent Out-of-Memory (OOM) crashes if offline
|
|
43
45
|
|
|
44
46
|
private errorEngine!: ErrorEngine;
|
|
45
47
|
|
|
@@ -66,11 +68,8 @@ export class SenzorRumAgent {
|
|
|
66
68
|
breadcrumbs: this.breadcrumbs,
|
|
67
69
|
frustrations: this.frustrations,
|
|
68
70
|
errorQueue: this.errorQueue,
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
this.spanQueue?.length
|
|
72
|
-
? this.spanQueue[this.spanQueue.length - 1]
|
|
73
|
-
: undefined,
|
|
71
|
+
queueLimit: this.MAX_QUEUE_MEMORY,
|
|
72
|
+
flush: () => this.debouncedFlush(),
|
|
74
73
|
});
|
|
75
74
|
this.errorEngine.setup();
|
|
76
75
|
|
|
@@ -130,6 +129,7 @@ export class SenzorRumAgent {
|
|
|
130
129
|
target.closest("a") ||
|
|
131
130
|
target.hasAttribute("role") ||
|
|
132
131
|
target.onclick;
|
|
132
|
+
|
|
133
133
|
if (!isInteractive) this.frustrations.deadClicks++;
|
|
134
134
|
|
|
135
135
|
const now = Date.now();
|
|
@@ -225,7 +225,7 @@ export class SenzorRumAgent {
|
|
|
225
225
|
};
|
|
226
226
|
}
|
|
227
227
|
|
|
228
|
-
// --- 3. Distributed Tracing &
|
|
228
|
+
// --- 3. Distributed Tracing & Network Patching ---
|
|
229
229
|
private shouldAttachTraceHeader(url: string): boolean {
|
|
230
230
|
if (!this.config.allowedOrigins || this.config.allowedOrigins.length === 0)
|
|
231
231
|
return false;
|
|
@@ -242,6 +242,12 @@ export class SenzorRumAgent {
|
|
|
242
242
|
}
|
|
243
243
|
}
|
|
244
244
|
|
|
245
|
+
private pushSpan(span: any) {
|
|
246
|
+
if (this.spanQueue.length >= this.MAX_QUEUE_MEMORY) this.spanQueue.shift(); // Drop oldest to prevent OOM
|
|
247
|
+
this.spanQueue.push(span);
|
|
248
|
+
if (this.spanQueue.length >= this.MAX_BATCH_SIZE) this.debouncedFlush();
|
|
249
|
+
}
|
|
250
|
+
|
|
245
251
|
private patchNetwork() {
|
|
246
252
|
const self = this;
|
|
247
253
|
|
|
@@ -322,8 +328,7 @@ export class SenzorRumAgent {
|
|
|
322
328
|
}
|
|
323
329
|
} catch (e) {}
|
|
324
330
|
|
|
325
|
-
|
|
326
|
-
self.spanQueue.push({
|
|
331
|
+
self.pushSpan({
|
|
327
332
|
spanId,
|
|
328
333
|
name: `${method} ${new URL(fullUrl, window.location.origin).pathname}`,
|
|
329
334
|
type: "http",
|
|
@@ -332,8 +337,6 @@ export class SenzorRumAgent {
|
|
|
332
337
|
status: xhr.status,
|
|
333
338
|
meta,
|
|
334
339
|
});
|
|
335
|
-
|
|
336
|
-
if (self.spanQueue.length >= self.MAX_BATCH_SIZE) self.flush();
|
|
337
340
|
});
|
|
338
341
|
|
|
339
342
|
return originalXhrSend.call(this, body);
|
|
@@ -408,7 +411,7 @@ export class SenzorRumAgent {
|
|
|
408
411
|
|
|
409
412
|
if (errorMsg) meta.error = errorMsg;
|
|
410
413
|
|
|
411
|
-
self.
|
|
414
|
+
self.pushSpan({
|
|
412
415
|
spanId,
|
|
413
416
|
name: `${method} ${new URL(fullUrl, window.location.origin).pathname}`,
|
|
414
417
|
type: "http",
|
|
@@ -417,8 +420,6 @@ export class SenzorRumAgent {
|
|
|
417
420
|
status,
|
|
418
421
|
meta,
|
|
419
422
|
});
|
|
420
|
-
|
|
421
|
-
if (self.spanQueue.length >= self.MAX_BATCH_SIZE) self.flush();
|
|
422
423
|
};
|
|
423
424
|
|
|
424
425
|
try {
|
|
@@ -459,6 +460,11 @@ export class SenzorRumAgent {
|
|
|
459
460
|
window.addEventListener("pagehide", () => this.flush());
|
|
460
461
|
}
|
|
461
462
|
|
|
463
|
+
private debouncedFlush() {
|
|
464
|
+
if (this.flushTimeout) clearTimeout(this.flushTimeout);
|
|
465
|
+
this.flushTimeout = setTimeout(() => this.flush(), 100); // 100ms debounce to prevent network spam
|
|
466
|
+
}
|
|
467
|
+
|
|
462
468
|
private flush() {
|
|
463
469
|
if (
|
|
464
470
|
this.spanQueue.length === 0 &&
|
|
@@ -467,6 +473,7 @@ export class SenzorRumAgent {
|
|
|
467
473
|
)
|
|
468
474
|
return;
|
|
469
475
|
|
|
476
|
+
// Drain Queue up to batch limit to ensure payload fits in Beacon API limits
|
|
470
477
|
const spansToSend = this.spanQueue.splice(0, this.MAX_BATCH_SIZE);
|
|
471
478
|
const errorsToSend = this.errorQueue.splice(0, 20);
|
|
472
479
|
|
|
@@ -500,6 +507,7 @@ export class SenzorRumAgent {
|
|
|
500
507
|
const authUrl = `${this.endpoint}${separator}apiKey=${this.config.apiKey}`;
|
|
501
508
|
|
|
502
509
|
if (navigator.sendBeacon && blob.size < 60000) {
|
|
510
|
+
// Safety check against 64k beacon limit
|
|
503
511
|
navigator.sendBeacon(authUrl, blob);
|
|
504
512
|
} else {
|
|
505
513
|
fetch(authUrl, {
|