alphana-sdk 0.5.9 → 1.0.0

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.
@@ -1 +1 @@
1
- "use strict";var AlphanaSDK=(()=>{var m=Object.defineProperty;var b=Object.getOwnPropertyDescriptor;var k=Object.getOwnPropertyNames;var P=Object.prototype.hasOwnProperty;var S=(r,t)=>{for(var e in t)m(r,e,{get:t[e],enumerable:!0})},T=(r,t,e,i)=>{if(t&&typeof t=="object"||typeof t=="function")for(let n of k(t))!P.call(r,n)&&n!==e&&m(r,n,{get:()=>t[n],enumerable:!(i=b(t,n))||i.enumerable});return r};var I=r=>T(m({},"__esModule",{value:!0}),r);var x={};S(x,{DEFAULT_ENDPOINT:()=>y,LogCapture:()=>d,UserTracker:()=>v});var w="__ut_vid__";function h(){return typeof crypto!="undefined"&&typeof crypto.randomUUID=="function"?crypto.randomUUID():"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,r=>{let t=Math.random()*16|0;return(r==="x"?t:t&3|8).toString(16)})}function E(){if(typeof localStorage=="undefined")return h();try{let r=localStorage.getItem(w);if(r)return r;let t=h();return localStorage.setItem(w,t),t}catch(r){return h()}}var p=class p{constructor({emit:t,sessionId:e}){this.previousPath="";this.originalPushState=null;this.originalReplaceState=null;this.pageEntryTime=0;this.handlePopState=()=>{this.handleNavigation()};this.emit=t,this.sessionId=e}init(){this.recordPageView(window.location.pathname+window.location.search),window.addEventListener("popstate",this.handlePopState),this.originalPushState=history.pushState.bind(history);let t=this.originalPushState;history.pushState=(i,n,s)=>{t(i,n,s),this.handleNavigation()},this.originalReplaceState=history.replaceState.bind(history);let e=this.originalReplaceState;history.replaceState=(i,n,s)=>{e(i,n,s),this.handleNavigation()}}destroy(){window.removeEventListener("popstate",this.handlePopState),this.originalPushState&&(history.pushState=this.originalPushState),this.originalReplaceState&&(history.replaceState=this.originalReplaceState)}handleNavigation(){let t=window.location.pathname+window.location.search;if(t!==this.previousPath){if(this.previousPath&&this.pageEntryTime>0){let e=Date.now()-this.pageEntryTime;e<=p.UTURN_THRESHOLD_MS&&this.emit({type:"uturn",data:{fromPath:this.previousPath,toPath:t,timeOnPageMs:e,timestamp:Date.now(),sessionId:this.sessionId}})}this.recordPageView(t)}}recordPageView(t){this.previousPath=t,this.pageEntryTime=Date.now(),this.emit({type:"pageview",data:{path:t,title:document.title,timestamp:Date.now(),sessionId:this.sessionId,referrer:document.referrer||void 0}}),window.dispatchEvent(new CustomEvent("tracker:navigate",{detail:{path:t,title:document.title}}))}};p.UTURN_THRESHOLD_MS=5e3;var l=p;var u=class{constructor({emit:t,sessionId:e}){this.currentPath="";this.startTime=0;this.tracking=!1;this.handleNavigate=t=>{this.stopTracking(),this.currentPath=t.detail.path,this.startTracking()};this.handleVisibilityChange=()=>{document.hidden?this.stopTracking():this.startTracking()};this.handleUnload=()=>{this.stopTracking()};this.emit=t,this.sessionId=e}init(){this.currentPath=window.location.pathname+window.location.search,this.startTracking(),window.addEventListener("tracker:navigate",this.handleNavigate),document.addEventListener("visibilitychange",this.handleVisibilityChange),window.addEventListener("beforeunload",this.handleUnload),window.addEventListener("pagehide",this.handleUnload)}destroy(){this.stopTracking(),window.removeEventListener("tracker:navigate",this.handleNavigate),document.removeEventListener("visibilitychange",this.handleVisibilityChange),window.removeEventListener("beforeunload",this.handleUnload),window.removeEventListener("pagehide",this.handleUnload)}startTracking(){this.startTime=Date.now(),this.tracking=!0}stopTracking(){if(!this.tracking||!this.currentPath)return;let t=Date.now()-this.startTime;if(t<100){this.tracking=!1;return}this.emit({type:"timespent",data:{path:this.currentPath,duration:t,sessionId:this.sessionId,timestamp:Date.now()}}),this.tracking=!1}};function f(r,t){let e=0;return(...i)=>{let n=Date.now();n-e>=t&&(e=n,r(...i))}}var o=class o{constructor({emit:t,sessionId:e,sampleRate:i=.3,maxPoints:n=2e3,allowedPaths:s}){this.currentPath="";this.pointCounts={};this.recentClicks=[];this.handleMouseMove=t=>{if(Math.random()>this.sampleRate)return;let e=document.documentElement.scrollWidth,i=document.documentElement.scrollHeight,n=t.clientY+window.scrollY;this.recordPoint({x:t.clientX,y:n,xPct:e>0?t.clientX/e*100:0,yPct:i>0?n/i*100:0,pw:e,ph:i,type:"move"})};this.handleClick=t=>{if(Date.now()-this.lastTouchTime<500)return;let e=document.documentElement.scrollWidth,i=document.documentElement.scrollHeight,n=t.clientY+window.scrollY,s=this.getClickTarget(t);this.recordPoint({x:t.clientX,y:n,xPct:e>0?t.clientX/e*100:0,yPct:i>0?n/i*100:0,pw:e,ph:i,type:"click",...s?{target:s}:{}}),this.checkRageClick(t.clientX,n,this.currentPath)};this.lastTouchTime=0;this.handleTouchEnd=t=>{let e=t.changedTouches[0];if(!e)return;let i=document.documentElement.scrollWidth,n=document.documentElement.scrollHeight,s=e.clientY+window.scrollY;this.lastTouchTime=Date.now(),this.recordPoint({x:e.clientX,y:s,xPct:i>0?e.clientX/i*100:0,yPct:n>0?s/n*100:0,pw:i,ph:n,type:"click"}),this.checkRageClick(e.clientX,s,this.currentPath)};this.handleScroll=()=>{if(Math.random()>this.sampleRate)return;let t=document.documentElement.scrollWidth,e=document.documentElement.scrollHeight,i=window.innerWidth,n=window.scrollX,s=window.scrollY,a=n+i/2;this.recordPoint({x:i/2,y:s,xPct:t>0?a/t*100:50,yPct:e>0?s/e*100:0,pw:t,ph:e,type:"scroll"})};this.handleNavigate=t=>{this.currentPath=t.detail.path};this.emit=t,this.sessionId=e,this.sampleRate=i,this.maxPoints=n,this.allowedPaths=s&&s.length>0?new Set(s):null,this.throttledMouseMove=f(this.handleMouseMove,50),this.throttledScroll=f(this.handleScroll,100)}init(){this.currentPath=window.location.pathname+window.location.search,document.addEventListener("mousemove",this.throttledMouseMove),document.addEventListener("click",this.handleClick),document.addEventListener("touchend",this.handleTouchEnd,{passive:!0}),window.addEventListener("scroll",this.throttledScroll,{passive:!0}),window.addEventListener("tracker:navigate",this.handleNavigate)}destroy(){document.removeEventListener("mousemove",this.throttledMouseMove),document.removeEventListener("click",this.handleClick),document.removeEventListener("touchend",this.handleTouchEnd),window.removeEventListener("scroll",this.throttledScroll),window.removeEventListener("tracker:navigate",this.handleNavigate)}canRecord(){var t;return this.allowedPaths!==null&&!this.allowedPaths.has(this.currentPath)?!1:((t=this.pointCounts[this.currentPath])!=null?t:0)<this.maxPoints}recordPoint(t){var e;this.canRecord()&&(this.pointCounts[this.currentPath]=((e=this.pointCounts[this.currentPath])!=null?e:0)+1,this.emit({type:"heatmap",data:{...t,path:this.currentPath,timestamp:Date.now()}}))}getClickTarget(t){var n,s,a,c;let e=t.target;return e&&(e.getAttribute("aria-label")||((n=e.closest("[aria-label]"))==null?void 0:n.getAttribute("aria-label"))||e.getAttribute("data-track-label")||e.getAttribute("id")||(e instanceof HTMLButtonElement||e instanceof HTMLAnchorElement?(s=e.innerText)==null?void 0:s.trim().slice(0,60):(c=(a=e.closest("button, a"))==null?void 0:a.textContent)==null?void 0:c.trim().slice(0,60)))||void 0}checkRageClick(t,e,i){let n=Date.now();this.recentClicks=this.recentClicks.filter(a=>n-a.t<o.RAGE_WINDOW_MS),this.recentClicks.push({x:t,y:e,t:n});let s=this.recentClicks.filter(a=>Math.hypot(a.x-t,a.y-e)<=o.RAGE_RADIUS_PX);s.length>=o.RAGE_THRESHOLD&&(this.emit({type:"rageclik",data:{path:i,x:t,y:e,count:s.length,timestamp:n,sessionId:this.sessionId}}),this.recentClicks=[])}};o.RAGE_THRESHOLD=3,o.RAGE_WINDOW_MS=1e3,o.RAGE_RADIUS_PX=50;var g=o;var d=class{constructor(t){this.prevOnError=null;this.prevOnUnhandledRejection=null;this.initialized=!1;try{let e=new URL(t.endpoint),i=e.pathname.replace(/\/$/,"").split("/");i.pop(),e.pathname=i.join("/")||"/",this.endpoint=e.toString().replace(/\/$/,"")}catch(e){this.endpoint=t.endpoint}this.sessionId=t.sessionId,this.appId=t.appId,this.authHeaders=t.secretKey?{Authorization:`Bearer ${t.secretKey}`}:{}}init(){typeof window=="undefined"||this.initialized||(this.origInfo=console.info.bind(console),this.origWarn=console.warn.bind(console),this.origError=console.error.bind(console),console.info=(...t)=>{this.origInfo(...t),this.send("info",this.format(t))},console.warn=(...t)=>{this.origWarn(...t),this.send("warn",this.format(t))},console.error=(...t)=>{this.origError(...t);let[e]=t,i=e instanceof Error?e.stack:void 0;this.send("error",this.format(t),{stack:i})},this.prevOnError=window.onerror,window.onerror=(t,e,i,n,s)=>(this.send("error",String(t),{stack:s==null?void 0:s.stack,meta:{src:e,line:i,col:n}}),typeof this.prevOnError=="function"?this.prevOnError(t,e,i,n,s):!1),this.prevOnUnhandledRejection=t=>{let e=t.reason,i=e instanceof Error?e.message:String(e!=null?e:"Unhandled promise rejection");this.send("error",i,{stack:e instanceof Error?e.stack:void 0})},window.addEventListener("unhandledrejection",this.prevOnUnhandledRejection),this.initialized=!0)}destroy(){this.initialized&&(console.info=this.origInfo,console.warn=this.origWarn,console.error=this.origError,window.onerror=this.prevOnError,this.prevOnUnhandledRejection&&window.removeEventListener("unhandledrejection",this.prevOnUnhandledRejection),this.initialized=!1)}capture(t,e,i){this.send(t,e,i)}format(t){return t.map(e=>{if(e instanceof Error)return e.message;if(typeof e=="object")try{return JSON.stringify(e)}catch(i){return String(e)}return String(e)}).join(" ")}send(t,e,i){let n={sessionId:this.sessionId,...this.appId?{appId:this.appId}:{},level:t,message:e,url:typeof window!="undefined"?window.location.href:void 0,stack:i==null?void 0:i.stack,meta:i==null?void 0:i.meta,timestamp:Date.now()},s=`${this.endpoint}/logs/ingest`,a=JSON.stringify(n);fetch(s,{method:"POST",headers:{"Content-Type":"application/json",...this.authHeaders},body:a,keepalive:!0}).catch(c=>{this.origError&&this.origError("[user-tracker] Failed to send log:",c)})}};var y="https://api.alphana.ir/api/events",R={endpoint:y,trackNavigation:!0,trackTime:!0,trackHeatmap:!0,trackLogs:!0,mouseSampleRate:.3,maxHeatmapPoints:2e3,batchSize:20,flushInterval:5e3},v=class{constructor(t={}){this.initialized=!1;this.subscribers=new Set;this.queue=[];this.flushTimer=null;this.heartbeatTimer=null;this.userProperties={};this.flags={};this.flagSubscribers=new Set;this.handleVisibilityChange=()=>{document.visibilityState==="hidden"&&(this.queue.length>0&&this.flushBeacon(),this.sendDeactivate())};this.handlePageHide=()=>{this.queue.length>0&&this.flushBeacon(),this.sendDeactivate()};this.handleNavigate=t=>{};var e;this.cfg={...R,...t};try{new URL(this.cfg.endpoint)}catch(i){throw new Error(`[alpha-tracker] Invalid endpoint URL: "${this.cfg.endpoint}"`)}this.session={id:(e=t.sessionId)!=null?e:h(),visitorId:E(),startedAt:Date.now(),pageViews:[],timeSpent:{},heatmap:{}}}init(){if(typeof window=="undefined"||this.initialized)return this;let t=this.emit.bind(this),{id:e}=this.session;return this.cfg.trackNavigation&&(this.navigation=new l({emit:t,sessionId:e}),this.navigation.init()),this.cfg.trackTime&&(this.time=new u({emit:t,sessionId:e}),this.time.init()),this.cfg.trackHeatmap&&(this.heatmap=new g({emit:t,sessionId:e,sampleRate:this.cfg.mouseSampleRate,maxPoints:this.cfg.maxHeatmapPoints,allowedPaths:this.cfg.heatmapPages}),this.heatmap.init()),this.cfg.endpoint&&(this.flushTimer=setInterval(()=>{this.queue.length>0&&this.flushQueue()},this.cfg.flushInterval),window.addEventListener("visibilitychange",this.handleVisibilityChange),window.addEventListener("pagehide",this.handlePageHide),this.heartbeatTimer=setInterval(()=>{document.visibilityState!=="hidden"&&this.sendHeartbeat()},3e4),this.cfg.trackLogs&&(this.logCapture=new d({endpoint:this.cfg.endpoint,sessionId:this.session.id,secretKey:this.cfg.secretKey,appId:this.cfg.appId}),this.logCapture.init())),this.initialized=!0,this.fetchFlags(),this}destroy(){var t,e,i,n;this.flushTimer!==null&&(clearInterval(this.flushTimer),this.flushTimer=null),this.heartbeatTimer!==null&&(clearInterval(this.heartbeatTimer),this.heartbeatTimer=null),typeof window!="undefined"&&(window.removeEventListener("visibilitychange",this.handleVisibilityChange),window.removeEventListener("pagehide",this.handlePageHide)),(t=this.navigation)==null||t.destroy(),(e=this.time)==null||e.destroy(),(i=this.heatmap)==null||i.destroy(),(n=this.logCapture)==null||n.destroy(),typeof window!="undefined"&&window.removeEventListener("tracker:navigate",this.handleNavigate),this.queue.length>0&&this.cfg.endpoint&&this.flushBeacon(),this.initialized=!1}async sendHeartbeat(){if(!this.cfg.endpoint)return;let t=`${this.cfg.endpoint}/heartbeat`,e=this.cfg.secretKey?{Authorization:`Bearer ${this.cfg.secretKey}`}:{},i=JSON.stringify({sessionId:this.session.id,visitorId:this.session.visitorId,path:typeof window!="undefined"?window.location.pathname:"/",active:!0,...this.cfg.appId?{appId:this.cfg.appId}:{}});try{await fetch(t,{method:"POST",headers:{"Content-Type":"application/json",...e},body:i,keepalive:!0})}catch(n){}}sendDeactivate(){if(!this.cfg.endpoint)return;let t=`${this.cfg.endpoint}/heartbeat`,e=this.cfg.secretKey?{Authorization:`Bearer ${this.cfg.secretKey}`}:{},i=JSON.stringify({sessionId:this.session.id,path:typeof window!="undefined"?window.location.pathname:"/",active:!1,...this.cfg.appId?{appId:this.cfg.appId}:{}});typeof navigator!="undefined"&&navigator.sendBeacon?navigator.sendBeacon(t,new Blob([i],{type:"application/json"})):fetch(t,{method:"POST",headers:{"Content-Type":"application/json",...e},body:i,keepalive:!0}).catch(()=>{})}emit(t){var e,i,n;switch(t.type){case"pageview":this.session.pageViews.push(t.data);break;case"timespent":{let s=(e=this.session.timeSpent[t.data.path])!=null?e:0;this.session.timeSpent[t.data.path]=s+t.data.duration;break}case"heatmap":{let s=t.data.path;this.session.heatmap[s]||(this.session.heatmap[s]=[]);let a=this.session.heatmap[s];a.length<this.cfg.maxHeatmapPoints&&a.push(t.data);break}case"rageclik":case"uturn":break}this.subscribers.forEach(s=>s(t)),(n=(i=this.cfg).onEvent)==null||n.call(i,t),this.cfg.endpoint&&(this.queue.push(t),this.queue.length>=this.cfg.batchSize&&this.flushQueue())}subscribe(t){return this.subscribers.add(t),()=>this.subscribers.delete(t)}trackPageView(t){let e=t!=null?t:typeof window!="undefined"?window.location.pathname+window.location.search:"/";this.emit({type:"pageview",data:{path:e,title:typeof document!="undefined"?document.title:"",timestamp:Date.now(),sessionId:this.session.id,referrer:typeof document!="undefined"&&document.referrer||void 0}}),typeof window!="undefined"&&window.dispatchEvent(new CustomEvent("tracker:navigate",{detail:{path:e,title:document.title}}))}identify(t){this.userProperties={...this.userProperties,...t},this.fetchFlags()}getFlags(){return{...this.flags}}isFeatureEnabled(t){return this.flags[t]===!0}onFlagsChange(t){return this.flagSubscribers.add(t),()=>this.flagSubscribers.delete(t)}async fetchFlags(){if(!this.cfg.endpoint||!this.cfg.secretKey)return;let t=this.cfg.endpoint.replace(/\/events$/,"")+"/feature-flags/evaluate";try{let e=await fetch(t,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.cfg.secretKey}`},body:JSON.stringify({userProperties:this.userProperties})});if(!e.ok)return;let i=await e.json();this.flags=i,this.flagSubscribers.forEach(n=>n({...this.flags}))}catch(e){}}getSession(){return this.session}getPageViews(){return[...this.session.pageViews]}getTimeSpent(){return{...this.session.timeSpent}}getHeatmapData(t){var e;return t!==void 0?[...(e=this.session.heatmap[t])!=null?e:[]]:Object.entries(this.session.heatmap).reduce((i,[n,s])=>(i[n]=[...s],i),{})}flush(){this.flushQueue()}async flushQueue(){if(this.queue.length===0)return;let t=this.queue.splice(0);await this.sendBatch(t)}flushBeacon(){if(this.queue.length===0)return;let t=this.queue.splice(0),e=`${this.cfg.endpoint}/batch`,i=new Blob([this.buildBatchBody(t)],{type:"application/json"});typeof navigator!="undefined"&&navigator.sendBeacon?navigator.sendBeacon(e,i):this.sendBatch(t)}buildBatchBody(t){return JSON.stringify({...this.cfg.appId?{appId:this.cfg.appId}:{},visitorId:this.session.visitorId,events:t.map(e=>({sessionId:this.session.id,type:e.type,data:e.data}))})}async sendBatch(t){let e=`${this.cfg.endpoint}/batch`,i=this.cfg.secretKey?{Authorization:`Bearer ${this.cfg.secretKey}`}:{};try{await fetch(e,{method:"POST",headers:{"Content-Type":"application/json",...i},body:this.buildBatchBody(t),keepalive:!0})}catch(n){}}};return I(x);})();
1
+ "use strict";var AlphanaSDK=(()=>{var m=Object.defineProperty;var T=Object.getOwnPropertyDescriptor;var I=Object.getOwnPropertyNames;var R=Object.prototype.hasOwnProperty;var x=(r,t)=>{for(var e in t)m(r,e,{get:t[e],enumerable:!0})},L=(r,t,e,i)=>{if(t&&typeof t=="object"||typeof t=="function")for(let n of I(t))!R.call(r,n)&&n!==e&&m(r,n,{get:()=>t[n],enumerable:!(i=T(t,n))||i.enumerable});return r};var C=r=>L(m({},"__esModule",{value:!0}),r);var D={};x(D,{DEFAULT_ENDPOINT:()=>y,LogCapture:()=>d,UserTracker:()=>v});var w="__ut_vid__";function h(){return typeof crypto!="undefined"&&typeof crypto.randomUUID=="function"?crypto.randomUUID():"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,r=>{let t=Math.random()*16|0;return(r==="x"?t:t&3|8).toString(16)})}function E(){if(typeof localStorage=="undefined")return h();try{let r=localStorage.getItem(w);if(r)return r;let t=h();return localStorage.setItem(w,t),t}catch(r){return h()}}var k="__ut_last_pv_path__",P="__ut_last_pv_entry_ms__";function b(r){if(typeof sessionStorage=="undefined")return null;try{return sessionStorage.getItem(r)}catch(t){return null}}function S(r,t){if(typeof sessionStorage!="undefined")try{sessionStorage.setItem(r,t)}catch(e){}}function H(r,t){S(k,r),S(P,String(t))}function _(){if(typeof performance=="undefined")return;let t=performance.getEntriesByType("navigation")[0];if(t!=null&&t.type)return t.type;let e=performance.navigation;if((e==null?void 0:e.type)===2)return"back_forward"}var c=class c{constructor({emit:t,sessionId:e}){this.previousPath="";this.originalPushState=null;this.originalReplaceState=null;this.pageEntryTime=0;this.handlePopState=()=>{this.handleNavigation()};this.emit=t,this.sessionId=e}init(){let t=window.location.pathname+window.location.search;_()==="back_forward"&&this.tryEmitMpaHistoryTraversalUturn(t),this.recordPageView(t),window.addEventListener("popstate",this.handlePopState),this.originalPushState=history.pushState.bind(history);let e=this.originalPushState;history.pushState=(n,s,a)=>{e(n,s,a),this.handleNavigation()},this.originalReplaceState=history.replaceState.bind(history);let i=this.originalReplaceState;history.replaceState=(n,s,a)=>{i(n,s,a),this.handleNavigation()}}destroy(){window.removeEventListener("popstate",this.handlePopState),this.originalPushState&&(history.pushState=this.originalPushState),this.originalReplaceState&&(history.replaceState=this.originalReplaceState)}tryEmitMpaHistoryTraversalUturn(t){let e=b(k),i=b(P),n=i?Number(i):0;if(!e||!Number.isFinite(n)||n<=0||e===t)return;let s=Date.now()-n;s<0||s>c.UTURN_THRESHOLD_MS||this.emitUturn(e,t,s)}handleNavigation(){let t=window.location.pathname+window.location.search;if(t!==this.previousPath){if(this.previousPath&&this.pageEntryTime>0){let e=Date.now()-this.pageEntryTime;e<=c.UTURN_THRESHOLD_MS&&this.emitUturn(this.previousPath,t,e)}this.recordPageView(t)}}emitUturn(t,e,i){this.emit({type:"uturn",data:{fromPath:t,toPath:e,timeOnPageMs:i,timestamp:Date.now(),sessionId:this.sessionId}})}recordPageView(t){this.previousPath=t,this.pageEntryTime=Date.now(),H(t,this.pageEntryTime),this.emit({type:"pageview",data:{path:t,title:document.title,timestamp:Date.now(),sessionId:this.sessionId,referrer:document.referrer||void 0}}),window.dispatchEvent(new CustomEvent("tracker:navigate",{detail:{path:t,title:document.title}}))}};c.UTURN_THRESHOLD_MS=5e3;var p=c;var u=class{constructor({emit:t,sessionId:e}){this.currentPath="";this.startTime=0;this.tracking=!1;this.handleNavigate=t=>{this.stopTracking(),this.currentPath=t.detail.path,this.startTracking()};this.handleVisibilityChange=()=>{document.hidden?this.stopTracking():this.startTracking()};this.handleUnload=()=>{this.stopTracking()};this.emit=t,this.sessionId=e}init(){this.currentPath=window.location.pathname+window.location.search,this.startTracking(),window.addEventListener("tracker:navigate",this.handleNavigate),document.addEventListener("visibilitychange",this.handleVisibilityChange),window.addEventListener("beforeunload",this.handleUnload),window.addEventListener("pagehide",this.handleUnload)}destroy(){this.stopTracking(),window.removeEventListener("tracker:navigate",this.handleNavigate),document.removeEventListener("visibilitychange",this.handleVisibilityChange),window.removeEventListener("beforeunload",this.handleUnload),window.removeEventListener("pagehide",this.handleUnload)}startTracking(){this.startTime=Date.now(),this.tracking=!0}stopTracking(){if(!this.tracking||!this.currentPath)return;let t=Date.now()-this.startTime;if(t<100){this.tracking=!1;return}this.emit({type:"timespent",data:{path:this.currentPath,duration:t,sessionId:this.sessionId,timestamp:Date.now()}}),this.tracking=!1}};function f(r,t){let e=0;return(...i)=>{let n=Date.now();n-e>=t&&(e=n,r(...i))}}var o=class o{constructor({emit:t,sessionId:e,sampleRate:i=.3,maxPoints:n=2e3,allowedPaths:s}){this.currentPath="";this.pointCounts={};this.recentClicks=[];this.handleMouseMove=t=>{if(Math.random()>this.sampleRate)return;let e=document.documentElement.scrollWidth,i=document.documentElement.scrollHeight,n=t.clientY+window.scrollY;this.recordPoint({x:t.clientX,y:n,xPct:e>0?t.clientX/e*100:0,yPct:i>0?n/i*100:0,pw:e,ph:i,type:"move"})};this.handleClick=t=>{if(Date.now()-this.lastTouchTime<500)return;let e=document.documentElement.scrollWidth,i=document.documentElement.scrollHeight,n=t.clientY+window.scrollY,s=this.getClickTarget(t);this.recordPoint({x:t.clientX,y:n,xPct:e>0?t.clientX/e*100:0,yPct:i>0?n/i*100:0,pw:e,ph:i,type:"click",...s?{target:s}:{}}),this.checkRageClick(t.clientX,n,this.currentPath)};this.lastTouchTime=0;this.handleTouchEnd=t=>{let e=t.changedTouches[0];if(!e)return;let i=document.documentElement.scrollWidth,n=document.documentElement.scrollHeight,s=e.clientY+window.scrollY;this.lastTouchTime=Date.now(),this.recordPoint({x:e.clientX,y:s,xPct:i>0?e.clientX/i*100:0,yPct:n>0?s/n*100:0,pw:i,ph:n,type:"click"}),this.checkRageClick(e.clientX,s,this.currentPath)};this.handleScroll=()=>{if(Math.random()>this.sampleRate)return;let t=document.documentElement.scrollWidth,e=document.documentElement.scrollHeight,i=window.innerWidth,n=window.scrollX,s=window.scrollY,a=n+i/2;this.recordPoint({x:i/2,y:s,xPct:t>0?a/t*100:50,yPct:e>0?s/e*100:0,pw:t,ph:e,type:"scroll"})};this.handleNavigate=t=>{this.currentPath=t.detail.path};this.emit=t,this.sessionId=e,this.sampleRate=i,this.maxPoints=n,this.allowedPaths=s&&s.length>0?new Set(s):null,this.throttledMouseMove=f(this.handleMouseMove,50),this.throttledScroll=f(this.handleScroll,100)}init(){this.currentPath=window.location.pathname+window.location.search,document.addEventListener("mousemove",this.throttledMouseMove),document.addEventListener("click",this.handleClick),document.addEventListener("touchend",this.handleTouchEnd,{passive:!0}),window.addEventListener("scroll",this.throttledScroll,{passive:!0}),window.addEventListener("tracker:navigate",this.handleNavigate)}destroy(){document.removeEventListener("mousemove",this.throttledMouseMove),document.removeEventListener("click",this.handleClick),document.removeEventListener("touchend",this.handleTouchEnd),window.removeEventListener("scroll",this.throttledScroll),window.removeEventListener("tracker:navigate",this.handleNavigate)}canRecord(){var t;return this.allowedPaths!==null&&!this.allowedPaths.has(this.currentPath)?!1:((t=this.pointCounts[this.currentPath])!=null?t:0)<this.maxPoints}recordPoint(t){var e;this.canRecord()&&(this.pointCounts[this.currentPath]=((e=this.pointCounts[this.currentPath])!=null?e:0)+1,this.emit({type:"heatmap",data:{...t,path:this.currentPath,timestamp:Date.now()}}))}getClickTarget(t){var n,s,a,l;let e=t.target;return e&&(e.getAttribute("aria-label")||((n=e.closest("[aria-label]"))==null?void 0:n.getAttribute("aria-label"))||e.getAttribute("data-track-label")||e.getAttribute("id")||(e instanceof HTMLButtonElement||e instanceof HTMLAnchorElement?(s=e.innerText)==null?void 0:s.trim().slice(0,60):(l=(a=e.closest("button, a"))==null?void 0:a.textContent)==null?void 0:l.trim().slice(0,60)))||void 0}checkRageClick(t,e,i){let n=Date.now();this.recentClicks=this.recentClicks.filter(a=>n-a.t<o.RAGE_WINDOW_MS),this.recentClicks.push({x:t,y:e,t:n});let s=this.recentClicks.filter(a=>Math.hypot(a.x-t,a.y-e)<=o.RAGE_RADIUS_PX);s.length>=o.RAGE_THRESHOLD&&(this.emit({type:"rageclik",data:{path:i,x:t,y:e,count:s.length,timestamp:n,sessionId:this.sessionId}}),this.recentClicks=[])}};o.RAGE_THRESHOLD=3,o.RAGE_WINDOW_MS=1e3,o.RAGE_RADIUS_PX=50;var g=o;var d=class{constructor(t){this.prevOnError=null;this.prevOnUnhandledRejection=null;this.initialized=!1;try{let e=new URL(t.endpoint),i=e.pathname.replace(/\/$/,"").split("/");i.pop(),e.pathname=i.join("/")||"/",this.endpoint=e.toString().replace(/\/$/,"")}catch(e){this.endpoint=t.endpoint}this.sessionId=t.sessionId,this.appId=t.appId,this.authHeaders=t.secretKey?{Authorization:`Bearer ${t.secretKey}`}:{}}init(){typeof window=="undefined"||this.initialized||(this.origInfo=console.info.bind(console),this.origWarn=console.warn.bind(console),this.origError=console.error.bind(console),console.info=(...t)=>{this.origInfo(...t),this.send("info",this.format(t))},console.warn=(...t)=>{this.origWarn(...t),this.send("warn",this.format(t))},console.error=(...t)=>{this.origError(...t);let[e]=t,i=e instanceof Error?e.stack:void 0;this.send("error",this.format(t),{stack:i})},this.prevOnError=window.onerror,window.onerror=(t,e,i,n,s)=>(this.send("error",String(t),{stack:s==null?void 0:s.stack,meta:{src:e,line:i,col:n}}),typeof this.prevOnError=="function"?this.prevOnError(t,e,i,n,s):!1),this.prevOnUnhandledRejection=t=>{let e=t.reason,i=e instanceof Error?e.message:String(e!=null?e:"Unhandled promise rejection");this.send("error",i,{stack:e instanceof Error?e.stack:void 0})},window.addEventListener("unhandledrejection",this.prevOnUnhandledRejection),this.initialized=!0)}destroy(){this.initialized&&(console.info=this.origInfo,console.warn=this.origWarn,console.error=this.origError,window.onerror=this.prevOnError,this.prevOnUnhandledRejection&&window.removeEventListener("unhandledrejection",this.prevOnUnhandledRejection),this.initialized=!1)}capture(t,e,i){this.send(t,e,i)}format(t){return t.map(e=>{if(e instanceof Error)return e.message;if(typeof e=="object")try{return JSON.stringify(e)}catch(i){return String(e)}return String(e)}).join(" ")}send(t,e,i){let n={sessionId:this.sessionId,...this.appId?{appId:this.appId}:{},level:t,message:e,url:typeof window!="undefined"?window.location.href:void 0,stack:i==null?void 0:i.stack,meta:i==null?void 0:i.meta,timestamp:Date.now()},s=`${this.endpoint}/logs/ingest`,a=JSON.stringify(n);fetch(s,{method:"POST",headers:{"Content-Type":"application/json",...this.authHeaders},body:a,keepalive:!0}).catch(l=>{this.origError&&this.origError("[user-tracker] Failed to send log:",l)})}};var y="https://api.alphana.ir/api/events",O={endpoint:y,trackNavigation:!0,trackTime:!0,trackHeatmap:!0,trackLogs:!0,mouseSampleRate:.3,maxHeatmapPoints:2e3,batchSize:20,flushInterval:5e3},v=class{constructor(t={}){this.initialized=!1;this.subscribers=new Set;this.queue=[];this.flushTimer=null;this.heartbeatTimer=null;this.userProperties={};this.flags={};this.flagSubscribers=new Set;this.handleVisibilityChange=()=>{document.visibilityState==="hidden"&&(this.queue.length>0&&this.flushBeacon(),this.sendDeactivate())};this.handlePageHide=()=>{this.queue.length>0&&this.flushBeacon(),this.sendDeactivate()};this.handleNavigate=t=>{};var e;this.cfg={...O,...t};try{new URL(this.cfg.endpoint)}catch(i){throw new Error(`[alpha-tracker] Invalid endpoint URL: "${this.cfg.endpoint}"`)}this.session={id:(e=t.sessionId)!=null?e:h(),visitorId:E(),startedAt:Date.now(),pageViews:[],timeSpent:{},heatmap:{}}}init(){if(typeof window=="undefined"||this.initialized)return this;let t=this.emit.bind(this),{id:e}=this.session;return this.cfg.trackNavigation&&(this.navigation=new p({emit:t,sessionId:e}),this.navigation.init()),this.cfg.trackTime&&(this.time=new u({emit:t,sessionId:e}),this.time.init()),this.cfg.trackHeatmap&&(this.heatmap=new g({emit:t,sessionId:e,sampleRate:this.cfg.mouseSampleRate,maxPoints:this.cfg.maxHeatmapPoints,allowedPaths:this.cfg.heatmapPages}),this.heatmap.init()),this.cfg.endpoint&&(this.flushTimer=setInterval(()=>{this.queue.length>0&&this.flushQueue()},this.cfg.flushInterval),window.addEventListener("visibilitychange",this.handleVisibilityChange),window.addEventListener("pagehide",this.handlePageHide),this.heartbeatTimer=setInterval(()=>{document.visibilityState!=="hidden"&&this.sendHeartbeat()},3e4),this.cfg.trackLogs&&(this.logCapture=new d({endpoint:this.cfg.endpoint,sessionId:this.session.id,secretKey:this.cfg.secretKey,appId:this.cfg.appId}),this.logCapture.init())),this.initialized=!0,this.fetchFlags(),this}destroy(){var t,e,i,n;this.flushTimer!==null&&(clearInterval(this.flushTimer),this.flushTimer=null),this.heartbeatTimer!==null&&(clearInterval(this.heartbeatTimer),this.heartbeatTimer=null),typeof window!="undefined"&&(window.removeEventListener("visibilitychange",this.handleVisibilityChange),window.removeEventListener("pagehide",this.handlePageHide)),(t=this.navigation)==null||t.destroy(),(e=this.time)==null||e.destroy(),(i=this.heatmap)==null||i.destroy(),(n=this.logCapture)==null||n.destroy(),typeof window!="undefined"&&window.removeEventListener("tracker:navigate",this.handleNavigate),this.queue.length>0&&this.cfg.endpoint&&this.flushBeacon(),this.initialized=!1}async sendHeartbeat(){if(!this.cfg.endpoint)return;let t=`${this.cfg.endpoint}/heartbeat`,e=this.cfg.secretKey?{Authorization:`Bearer ${this.cfg.secretKey}`}:{},i=JSON.stringify({sessionId:this.session.id,visitorId:this.session.visitorId,path:typeof window!="undefined"?window.location.pathname:"/",active:!0,...this.cfg.appId?{appId:this.cfg.appId}:{}});try{await fetch(t,{method:"POST",headers:{"Content-Type":"application/json",...e},body:i,keepalive:!0})}catch(n){}}sendDeactivate(){if(!this.cfg.endpoint)return;let t=`${this.cfg.endpoint}/heartbeat`,e=this.cfg.secretKey?{Authorization:`Bearer ${this.cfg.secretKey}`}:{},i=JSON.stringify({sessionId:this.session.id,path:typeof window!="undefined"?window.location.pathname:"/",active:!1,...this.cfg.appId?{appId:this.cfg.appId}:{}});typeof navigator!="undefined"&&navigator.sendBeacon?navigator.sendBeacon(t,new Blob([i],{type:"application/json"})):fetch(t,{method:"POST",headers:{"Content-Type":"application/json",...e},body:i,keepalive:!0}).catch(()=>{})}emit(t){var e,i,n;switch(t.type){case"pageview":this.session.pageViews.push(t.data);break;case"timespent":{let s=(e=this.session.timeSpent[t.data.path])!=null?e:0;this.session.timeSpent[t.data.path]=s+t.data.duration;break}case"heatmap":{let s=t.data.path;this.session.heatmap[s]||(this.session.heatmap[s]=[]);let a=this.session.heatmap[s];a.length<this.cfg.maxHeatmapPoints&&a.push(t.data);break}case"rageclik":case"uturn":break}this.subscribers.forEach(s=>s(t)),(n=(i=this.cfg).onEvent)==null||n.call(i,t),this.cfg.endpoint&&(this.queue.push(t),this.queue.length>=this.cfg.batchSize&&this.flushQueue())}subscribe(t){return this.subscribers.add(t),()=>this.subscribers.delete(t)}trackPageView(t){let e=t!=null?t:typeof window!="undefined"?window.location.pathname+window.location.search:"/";this.emit({type:"pageview",data:{path:e,title:typeof document!="undefined"?document.title:"",timestamp:Date.now(),sessionId:this.session.id,referrer:typeof document!="undefined"&&document.referrer||void 0}}),typeof window!="undefined"&&window.dispatchEvent(new CustomEvent("tracker:navigate",{detail:{path:e,title:document.title}}))}identify(t){this.userProperties={...this.userProperties,...t},this.fetchFlags()}getFlags(){return{...this.flags}}isFeatureEnabled(t){return this.flags[t]===!0}onFlagsChange(t){return this.flagSubscribers.add(t),()=>this.flagSubscribers.delete(t)}async fetchFlags(){if(!this.cfg.endpoint||!this.cfg.secretKey)return;let t=this.cfg.endpoint.replace(/\/events$/,"")+"/feature-flags/evaluate";try{let e=await fetch(t,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.cfg.secretKey}`},body:JSON.stringify({userProperties:this.userProperties})});if(!e.ok)return;let i=await e.json();this.flags=i,this.flagSubscribers.forEach(n=>n({...this.flags}))}catch(e){}}getSession(){return this.session}getPageViews(){return[...this.session.pageViews]}getTimeSpent(){return{...this.session.timeSpent}}getHeatmapData(t){var e;return t!==void 0?[...(e=this.session.heatmap[t])!=null?e:[]]:Object.entries(this.session.heatmap).reduce((i,[n,s])=>(i[n]=[...s],i),{})}flush(){this.flushQueue()}async flushQueue(){if(this.queue.length===0)return;let t=this.queue.splice(0);await this.sendBatch(t)}flushBeacon(){if(this.queue.length===0)return;let t=this.queue.splice(0),e=`${this.cfg.endpoint}/batch`,i=new Blob([this.buildBatchBody(t)],{type:"application/json"});typeof navigator!="undefined"&&navigator.sendBeacon?navigator.sendBeacon(e,i):this.sendBatch(t)}buildBatchBody(t){return JSON.stringify({...this.cfg.appId?{appId:this.cfg.appId}:{},visitorId:this.session.visitorId,events:t.map(e=>({sessionId:this.session.id,type:e.type,data:e.data}))})}async sendBatch(t){let e=`${this.cfg.endpoint}/batch`,i=this.cfg.secretKey?{Authorization:`Bearer ${this.cfg.secretKey}`}:{};try{await fetch(e,{method:"POST",headers:{"Content-Type":"application/json",...i},body:this.buildBatchBody(t),keepalive:!0})}catch(n){}}};return C(D);})();
package/dist/index.js CHANGED
@@ -1,3 +1,3 @@
1
- 'use strict';var f="__ut_vid__";function d(){return typeof crypto!="undefined"&&typeof crypto.randomUUID=="function"?crypto.randomUUID():"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,a=>{let t=Math.random()*16|0;return (a==="x"?t:t&3|8).toString(16)})}function y(){if(typeof localStorage=="undefined")return d();try{let a=localStorage.getItem(f);if(a)return a;let t=d();return localStorage.setItem(f,t),t}catch(a){return d()}}var p=class p{constructor({emit:t,sessionId:e}){this.previousPath="";this.originalPushState=null;this.originalReplaceState=null;this.pageEntryTime=0;this.handlePopState=()=>{this.handleNavigation();};this.emit=t,this.sessionId=e;}init(){this.recordPageView(window.location.pathname+window.location.search),window.addEventListener("popstate",this.handlePopState),this.originalPushState=history.pushState.bind(history);let t=this.originalPushState;history.pushState=(i,n,s)=>{t(i,n,s),this.handleNavigation();},this.originalReplaceState=history.replaceState.bind(history);let e=this.originalReplaceState;history.replaceState=(i,n,s)=>{e(i,n,s),this.handleNavigation();};}destroy(){window.removeEventListener("popstate",this.handlePopState),this.originalPushState&&(history.pushState=this.originalPushState),this.originalReplaceState&&(history.replaceState=this.originalReplaceState);}handleNavigation(){let t=window.location.pathname+window.location.search;if(t!==this.previousPath){if(this.previousPath&&this.pageEntryTime>0){let e=Date.now()-this.pageEntryTime;e<=p.UTURN_THRESHOLD_MS&&this.emit({type:"uturn",data:{fromPath:this.previousPath,toPath:t,timeOnPageMs:e,timestamp:Date.now(),sessionId:this.sessionId}});}this.recordPageView(t);}}recordPageView(t){this.previousPath=t,this.pageEntryTime=Date.now(),this.emit({type:"pageview",data:{path:t,title:document.title,timestamp:Date.now(),sessionId:this.sessionId,referrer:document.referrer||void 0}}),window.dispatchEvent(new CustomEvent("tracker:navigate",{detail:{path:t,title:document.title}}));}};p.UTURN_THRESHOLD_MS=5e3;var l=p;var u=class{constructor({emit:t,sessionId:e}){this.currentPath="";this.startTime=0;this.tracking=false;this.handleNavigate=t=>{this.stopTracking(),this.currentPath=t.detail.path,this.startTracking();};this.handleVisibilityChange=()=>{document.hidden?this.stopTracking():this.startTracking();};this.handleUnload=()=>{this.stopTracking();};this.emit=t,this.sessionId=e;}init(){this.currentPath=window.location.pathname+window.location.search,this.startTracking(),window.addEventListener("tracker:navigate",this.handleNavigate),document.addEventListener("visibilitychange",this.handleVisibilityChange),window.addEventListener("beforeunload",this.handleUnload),window.addEventListener("pagehide",this.handleUnload);}destroy(){this.stopTracking(),window.removeEventListener("tracker:navigate",this.handleNavigate),document.removeEventListener("visibilitychange",this.handleVisibilityChange),window.removeEventListener("beforeunload",this.handleUnload),window.removeEventListener("pagehide",this.handleUnload);}startTracking(){this.startTime=Date.now(),this.tracking=true;}stopTracking(){if(!this.tracking||!this.currentPath)return;let t=Date.now()-this.startTime;if(t<100){this.tracking=false;return}this.emit({type:"timespent",data:{path:this.currentPath,duration:t,sessionId:this.sessionId,timestamp:Date.now()}}),this.tracking=false;}};function v(a,t){let e=0;return (...i)=>{let n=Date.now();n-e>=t&&(e=n,a(...i));}}var o=class o{constructor({emit:t,sessionId:e,sampleRate:i=.3,maxPoints:n=2e3,allowedPaths:s}){this.currentPath="";this.pointCounts={};this.recentClicks=[];this.handleMouseMove=t=>{if(Math.random()>this.sampleRate)return;let e=document.documentElement.scrollWidth,i=document.documentElement.scrollHeight,n=t.clientY+window.scrollY;this.recordPoint({x:t.clientX,y:n,xPct:e>0?t.clientX/e*100:0,yPct:i>0?n/i*100:0,pw:e,ph:i,type:"move"});};this.handleClick=t=>{if(Date.now()-this.lastTouchTime<500)return;let e=document.documentElement.scrollWidth,i=document.documentElement.scrollHeight,n=t.clientY+window.scrollY,s=this.getClickTarget(t);this.recordPoint({x:t.clientX,y:n,xPct:e>0?t.clientX/e*100:0,yPct:i>0?n/i*100:0,pw:e,ph:i,type:"click",...s?{target:s}:{}}),this.checkRageClick(t.clientX,n,this.currentPath);};this.lastTouchTime=0;this.handleTouchEnd=t=>{let e=t.changedTouches[0];if(!e)return;let i=document.documentElement.scrollWidth,n=document.documentElement.scrollHeight,s=e.clientY+window.scrollY;this.lastTouchTime=Date.now(),this.recordPoint({x:e.clientX,y:s,xPct:i>0?e.clientX/i*100:0,yPct:n>0?s/n*100:0,pw:i,ph:n,type:"click"}),this.checkRageClick(e.clientX,s,this.currentPath);};this.handleScroll=()=>{if(Math.random()>this.sampleRate)return;let t=document.documentElement.scrollWidth,e=document.documentElement.scrollHeight,i=window.innerWidth,n=window.scrollX,s=window.scrollY,r=n+i/2;this.recordPoint({x:i/2,y:s,xPct:t>0?r/t*100:50,yPct:e>0?s/e*100:0,pw:t,ph:e,type:"scroll"});};this.handleNavigate=t=>{this.currentPath=t.detail.path;};this.emit=t,this.sessionId=e,this.sampleRate=i,this.maxPoints=n,this.allowedPaths=s&&s.length>0?new Set(s):null,this.throttledMouseMove=v(this.handleMouseMove,50),this.throttledScroll=v(this.handleScroll,100);}init(){this.currentPath=window.location.pathname+window.location.search,document.addEventListener("mousemove",this.throttledMouseMove),document.addEventListener("click",this.handleClick),document.addEventListener("touchend",this.handleTouchEnd,{passive:true}),window.addEventListener("scroll",this.throttledScroll,{passive:true}),window.addEventListener("tracker:navigate",this.handleNavigate);}destroy(){document.removeEventListener("mousemove",this.throttledMouseMove),document.removeEventListener("click",this.handleClick),document.removeEventListener("touchend",this.handleTouchEnd),window.removeEventListener("scroll",this.throttledScroll),window.removeEventListener("tracker:navigate",this.handleNavigate);}canRecord(){var t;return this.allowedPaths!==null&&!this.allowedPaths.has(this.currentPath)?false:((t=this.pointCounts[this.currentPath])!=null?t:0)<this.maxPoints}recordPoint(t){var e;this.canRecord()&&(this.pointCounts[this.currentPath]=((e=this.pointCounts[this.currentPath])!=null?e:0)+1,this.emit({type:"heatmap",data:{...t,path:this.currentPath,timestamp:Date.now()}}));}getClickTarget(t){var n,s,r,c;let e=t.target;return e&&(e.getAttribute("aria-label")||((n=e.closest("[aria-label]"))==null?void 0:n.getAttribute("aria-label"))||e.getAttribute("data-track-label")||e.getAttribute("id")||(e instanceof HTMLButtonElement||e instanceof HTMLAnchorElement?(s=e.innerText)==null?void 0:s.trim().slice(0,60):(c=(r=e.closest("button, a"))==null?void 0:r.textContent)==null?void 0:c.trim().slice(0,60)))||void 0}checkRageClick(t,e,i){let n=Date.now();this.recentClicks=this.recentClicks.filter(r=>n-r.t<o.RAGE_WINDOW_MS),this.recentClicks.push({x:t,y:e,t:n});let s=this.recentClicks.filter(r=>Math.hypot(r.x-t,r.y-e)<=o.RAGE_RADIUS_PX);s.length>=o.RAGE_THRESHOLD&&(this.emit({type:"rageclik",data:{path:i,x:t,y:e,count:s.length,timestamp:n,sessionId:this.sessionId}}),this.recentClicks=[]);}};o.RAGE_THRESHOLD=3,o.RAGE_WINDOW_MS=1e3,o.RAGE_RADIUS_PX=50;var g=o;var h=class{constructor(t){this.prevOnError=null;this.prevOnUnhandledRejection=null;this.initialized=false;try{let e=new URL(t.endpoint),i=e.pathname.replace(/\/$/,"").split("/");i.pop(),e.pathname=i.join("/")||"/",this.endpoint=e.toString().replace(/\/$/,"");}catch(e){this.endpoint=t.endpoint;}this.sessionId=t.sessionId,this.appId=t.appId,this.authHeaders=t.secretKey?{Authorization:`Bearer ${t.secretKey}`}:{};}init(){typeof window=="undefined"||this.initialized||(this.origInfo=console.info.bind(console),this.origWarn=console.warn.bind(console),this.origError=console.error.bind(console),console.info=(...t)=>{this.origInfo(...t),this.send("info",this.format(t));},console.warn=(...t)=>{this.origWarn(...t),this.send("warn",this.format(t));},console.error=(...t)=>{this.origError(...t);let[e]=t,i=e instanceof Error?e.stack:void 0;this.send("error",this.format(t),{stack:i});},this.prevOnError=window.onerror,window.onerror=(t,e,i,n,s)=>(this.send("error",String(t),{stack:s==null?void 0:s.stack,meta:{src:e,line:i,col:n}}),typeof this.prevOnError=="function"?this.prevOnError(t,e,i,n,s):false),this.prevOnUnhandledRejection=t=>{let e=t.reason,i=e instanceof Error?e.message:String(e!=null?e:"Unhandled promise rejection");this.send("error",i,{stack:e instanceof Error?e.stack:void 0});},window.addEventListener("unhandledrejection",this.prevOnUnhandledRejection),this.initialized=true);}destroy(){this.initialized&&(console.info=this.origInfo,console.warn=this.origWarn,console.error=this.origError,window.onerror=this.prevOnError,this.prevOnUnhandledRejection&&window.removeEventListener("unhandledrejection",this.prevOnUnhandledRejection),this.initialized=false);}capture(t,e,i){this.send(t,e,i);}format(t){return t.map(e=>{if(e instanceof Error)return e.message;if(typeof e=="object")try{return JSON.stringify(e)}catch(i){return String(e)}return String(e)}).join(" ")}send(t,e,i){let n={sessionId:this.sessionId,...this.appId?{appId:this.appId}:{},level:t,message:e,url:typeof window!="undefined"?window.location.href:void 0,stack:i==null?void 0:i.stack,meta:i==null?void 0:i.meta,timestamp:Date.now()},s=`${this.endpoint}/logs/ingest`,r=JSON.stringify(n);fetch(s,{method:"POST",headers:{"Content-Type":"application/json",...this.authHeaders},body:r,keepalive:true}).catch(c=>{this.origError&&this.origError("[user-tracker] Failed to send log:",c);});}};var w="https://api.alphana.ir/api/events",E={endpoint:w,trackNavigation:true,trackTime:true,trackHeatmap:true,trackLogs:true,mouseSampleRate:.3,maxHeatmapPoints:2e3,batchSize:20,flushInterval:5e3},m=class{constructor(t={}){this.initialized=false;this.subscribers=new Set;this.queue=[];this.flushTimer=null;this.heartbeatTimer=null;this.userProperties={};this.flags={};this.flagSubscribers=new Set;this.handleVisibilityChange=()=>{document.visibilityState==="hidden"&&(this.queue.length>0&&this.flushBeacon(),this.sendDeactivate());};this.handlePageHide=()=>{this.queue.length>0&&this.flushBeacon(),this.sendDeactivate();};this.handleNavigate=t=>{};var e;this.cfg={...E,...t};try{new URL(this.cfg.endpoint);}catch(i){throw new Error(`[alpha-tracker] Invalid endpoint URL: "${this.cfg.endpoint}"`)}this.session={id:(e=t.sessionId)!=null?e:d(),visitorId:y(),startedAt:Date.now(),pageViews:[],timeSpent:{},heatmap:{}};}init(){if(typeof window=="undefined"||this.initialized)return this;let t=this.emit.bind(this),{id:e}=this.session;return this.cfg.trackNavigation&&(this.navigation=new l({emit:t,sessionId:e}),this.navigation.init()),this.cfg.trackTime&&(this.time=new u({emit:t,sessionId:e}),this.time.init()),this.cfg.trackHeatmap&&(this.heatmap=new g({emit:t,sessionId:e,sampleRate:this.cfg.mouseSampleRate,maxPoints:this.cfg.maxHeatmapPoints,allowedPaths:this.cfg.heatmapPages}),this.heatmap.init()),this.cfg.endpoint&&(this.flushTimer=setInterval(()=>{this.queue.length>0&&this.flushQueue();},this.cfg.flushInterval),window.addEventListener("visibilitychange",this.handleVisibilityChange),window.addEventListener("pagehide",this.handlePageHide),this.heartbeatTimer=setInterval(()=>{document.visibilityState!=="hidden"&&this.sendHeartbeat();},3e4),this.cfg.trackLogs&&(this.logCapture=new h({endpoint:this.cfg.endpoint,sessionId:this.session.id,secretKey:this.cfg.secretKey,appId:this.cfg.appId}),this.logCapture.init())),this.initialized=true,this.fetchFlags(),this}destroy(){var t,e,i,n;this.flushTimer!==null&&(clearInterval(this.flushTimer),this.flushTimer=null),this.heartbeatTimer!==null&&(clearInterval(this.heartbeatTimer),this.heartbeatTimer=null),typeof window!="undefined"&&(window.removeEventListener("visibilitychange",this.handleVisibilityChange),window.removeEventListener("pagehide",this.handlePageHide)),(t=this.navigation)==null||t.destroy(),(e=this.time)==null||e.destroy(),(i=this.heatmap)==null||i.destroy(),(n=this.logCapture)==null||n.destroy(),typeof window!="undefined"&&window.removeEventListener("tracker:navigate",this.handleNavigate),this.queue.length>0&&this.cfg.endpoint&&this.flushBeacon(),this.initialized=false;}async sendHeartbeat(){if(!this.cfg.endpoint)return;let t=`${this.cfg.endpoint}/heartbeat`,e=this.cfg.secretKey?{Authorization:`Bearer ${this.cfg.secretKey}`}:{},i=JSON.stringify({sessionId:this.session.id,visitorId:this.session.visitorId,path:typeof window!="undefined"?window.location.pathname:"/",active:true,...this.cfg.appId?{appId:this.cfg.appId}:{}});try{await fetch(t,{method:"POST",headers:{"Content-Type":"application/json",...e},body:i,keepalive:!0});}catch(n){}}sendDeactivate(){if(!this.cfg.endpoint)return;let t=`${this.cfg.endpoint}/heartbeat`,e=this.cfg.secretKey?{Authorization:`Bearer ${this.cfg.secretKey}`}:{},i=JSON.stringify({sessionId:this.session.id,path:typeof window!="undefined"?window.location.pathname:"/",active:false,...this.cfg.appId?{appId:this.cfg.appId}:{}});typeof navigator!="undefined"&&navigator.sendBeacon?navigator.sendBeacon(t,new Blob([i],{type:"application/json"})):fetch(t,{method:"POST",headers:{"Content-Type":"application/json",...e},body:i,keepalive:true}).catch(()=>{});}emit(t){var e,i,n;switch(t.type){case "pageview":this.session.pageViews.push(t.data);break;case "timespent":{let s=(e=this.session.timeSpent[t.data.path])!=null?e:0;this.session.timeSpent[t.data.path]=s+t.data.duration;break}case "heatmap":{let s=t.data.path;this.session.heatmap[s]||(this.session.heatmap[s]=[]);let r=this.session.heatmap[s];r.length<this.cfg.maxHeatmapPoints&&r.push(t.data);break}}this.subscribers.forEach(s=>s(t)),(n=(i=this.cfg).onEvent)==null||n.call(i,t),this.cfg.endpoint&&(this.queue.push(t),this.queue.length>=this.cfg.batchSize&&this.flushQueue());}subscribe(t){return this.subscribers.add(t),()=>this.subscribers.delete(t)}trackPageView(t){let e=t!=null?t:typeof window!="undefined"?window.location.pathname+window.location.search:"/";this.emit({type:"pageview",data:{path:e,title:typeof document!="undefined"?document.title:"",timestamp:Date.now(),sessionId:this.session.id,referrer:typeof document!="undefined"&&document.referrer||void 0}}),typeof window!="undefined"&&window.dispatchEvent(new CustomEvent("tracker:navigate",{detail:{path:e,title:document.title}}));}identify(t){this.userProperties={...this.userProperties,...t},this.fetchFlags();}getFlags(){return {...this.flags}}isFeatureEnabled(t){return this.flags[t]===true}onFlagsChange(t){return this.flagSubscribers.add(t),()=>this.flagSubscribers.delete(t)}async fetchFlags(){if(!this.cfg.endpoint||!this.cfg.secretKey)return;let t=this.cfg.endpoint.replace(/\/events$/,"")+"/feature-flags/evaluate";try{let e=await fetch(t,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.cfg.secretKey}`},body:JSON.stringify({userProperties:this.userProperties})});if(!e.ok)return;let i=await e.json();this.flags=i,this.flagSubscribers.forEach(n=>n({...this.flags}));}catch(e){}}getSession(){return this.session}getPageViews(){return [...this.session.pageViews]}getTimeSpent(){return {...this.session.timeSpent}}getHeatmapData(t){var e;return t!==void 0?[...(e=this.session.heatmap[t])!=null?e:[]]:Object.entries(this.session.heatmap).reduce((i,[n,s])=>(i[n]=[...s],i),{})}flush(){this.flushQueue();}async flushQueue(){if(this.queue.length===0)return;let t=this.queue.splice(0);await this.sendBatch(t);}flushBeacon(){if(this.queue.length===0)return;let t=this.queue.splice(0),e=`${this.cfg.endpoint}/batch`,i=new Blob([this.buildBatchBody(t)],{type:"application/json"});typeof navigator!="undefined"&&navigator.sendBeacon?navigator.sendBeacon(e,i):this.sendBatch(t);}buildBatchBody(t){return JSON.stringify({...this.cfg.appId?{appId:this.cfg.appId}:{},visitorId:this.session.visitorId,events:t.map(e=>({sessionId:this.session.id,type:e.type,data:e.data}))})}async sendBatch(t){let e=`${this.cfg.endpoint}/batch`,i=this.cfg.secretKey?{Authorization:`Bearer ${this.cfg.secretKey}`}:{};try{await fetch(e,{method:"POST",headers:{"Content-Type":"application/json",...i},body:this.buildBatchBody(t),keepalive:!0});}catch(n){}}};
2
- exports.DEFAULT_ENDPOINT=w;exports.LogCapture=h;exports.UserTracker=m;//# sourceMappingURL=index.js.map
1
+ 'use strict';var f="__ut_vid__";function d(){return typeof crypto!="undefined"&&typeof crypto.randomUUID=="function"?crypto.randomUUID():"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,a=>{let t=Math.random()*16|0;return (a==="x"?t:t&3|8).toString(16)})}function y(){if(typeof localStorage=="undefined")return d();try{let a=localStorage.getItem(f);if(a)return a;let t=d();return localStorage.setItem(f,t),t}catch(a){return d()}}var b="__ut_last_pv_path__",S="__ut_last_pv_entry_ms__";function w(a){if(typeof sessionStorage=="undefined")return null;try{return sessionStorage.getItem(a)}catch(t){return null}}function E(a,t){if(typeof sessionStorage!="undefined")try{sessionStorage.setItem(a,t);}catch(e){}}function P(a,t){E(b,a),E(S,String(t));}function T(){if(typeof performance=="undefined")return;let t=performance.getEntriesByType("navigation")[0];if(t!=null&&t.type)return t.type;let e=performance.navigation;if((e==null?void 0:e.type)===2)return "back_forward"}var h=class h{constructor({emit:t,sessionId:e}){this.previousPath="";this.originalPushState=null;this.originalReplaceState=null;this.pageEntryTime=0;this.handlePopState=()=>{this.handleNavigation();};this.emit=t,this.sessionId=e;}init(){let t=window.location.pathname+window.location.search;T()==="back_forward"&&this.tryEmitMpaHistoryTraversalUturn(t),this.recordPageView(t),window.addEventListener("popstate",this.handlePopState),this.originalPushState=history.pushState.bind(history);let e=this.originalPushState;history.pushState=(n,s,r)=>{e(n,s,r),this.handleNavigation();},this.originalReplaceState=history.replaceState.bind(history);let i=this.originalReplaceState;history.replaceState=(n,s,r)=>{i(n,s,r),this.handleNavigation();};}destroy(){window.removeEventListener("popstate",this.handlePopState),this.originalPushState&&(history.pushState=this.originalPushState),this.originalReplaceState&&(history.replaceState=this.originalReplaceState);}tryEmitMpaHistoryTraversalUturn(t){let e=w(b),i=w(S),n=i?Number(i):0;if(!e||!Number.isFinite(n)||n<=0||e===t)return;let s=Date.now()-n;s<0||s>h.UTURN_THRESHOLD_MS||this.emitUturn(e,t,s);}handleNavigation(){let t=window.location.pathname+window.location.search;if(t!==this.previousPath){if(this.previousPath&&this.pageEntryTime>0){let e=Date.now()-this.pageEntryTime;e<=h.UTURN_THRESHOLD_MS&&this.emitUturn(this.previousPath,t,e);}this.recordPageView(t);}}emitUturn(t,e,i){this.emit({type:"uturn",data:{fromPath:t,toPath:e,timeOnPageMs:i,timestamp:Date.now(),sessionId:this.sessionId}});}recordPageView(t){this.previousPath=t,this.pageEntryTime=Date.now(),P(t,this.pageEntryTime),this.emit({type:"pageview",data:{path:t,title:document.title,timestamp:Date.now(),sessionId:this.sessionId,referrer:document.referrer||void 0}}),window.dispatchEvent(new CustomEvent("tracker:navigate",{detail:{path:t,title:document.title}}));}};h.UTURN_THRESHOLD_MS=5e3;var p=h;var u=class{constructor({emit:t,sessionId:e}){this.currentPath="";this.startTime=0;this.tracking=false;this.handleNavigate=t=>{this.stopTracking(),this.currentPath=t.detail.path,this.startTracking();};this.handleVisibilityChange=()=>{document.hidden?this.stopTracking():this.startTracking();};this.handleUnload=()=>{this.stopTracking();};this.emit=t,this.sessionId=e;}init(){this.currentPath=window.location.pathname+window.location.search,this.startTracking(),window.addEventListener("tracker:navigate",this.handleNavigate),document.addEventListener("visibilitychange",this.handleVisibilityChange),window.addEventListener("beforeunload",this.handleUnload),window.addEventListener("pagehide",this.handleUnload);}destroy(){this.stopTracking(),window.removeEventListener("tracker:navigate",this.handleNavigate),document.removeEventListener("visibilitychange",this.handleVisibilityChange),window.removeEventListener("beforeunload",this.handleUnload),window.removeEventListener("pagehide",this.handleUnload);}startTracking(){this.startTime=Date.now(),this.tracking=true;}stopTracking(){if(!this.tracking||!this.currentPath)return;let t=Date.now()-this.startTime;if(t<100){this.tracking=false;return}this.emit({type:"timespent",data:{path:this.currentPath,duration:t,sessionId:this.sessionId,timestamp:Date.now()}}),this.tracking=false;}};function v(a,t){let e=0;return (...i)=>{let n=Date.now();n-e>=t&&(e=n,a(...i));}}var o=class o{constructor({emit:t,sessionId:e,sampleRate:i=.3,maxPoints:n=2e3,allowedPaths:s}){this.currentPath="";this.pointCounts={};this.recentClicks=[];this.handleMouseMove=t=>{if(Math.random()>this.sampleRate)return;let e=document.documentElement.scrollWidth,i=document.documentElement.scrollHeight,n=t.clientY+window.scrollY;this.recordPoint({x:t.clientX,y:n,xPct:e>0?t.clientX/e*100:0,yPct:i>0?n/i*100:0,pw:e,ph:i,type:"move"});};this.handleClick=t=>{if(Date.now()-this.lastTouchTime<500)return;let e=document.documentElement.scrollWidth,i=document.documentElement.scrollHeight,n=t.clientY+window.scrollY,s=this.getClickTarget(t);this.recordPoint({x:t.clientX,y:n,xPct:e>0?t.clientX/e*100:0,yPct:i>0?n/i*100:0,pw:e,ph:i,type:"click",...s?{target:s}:{}}),this.checkRageClick(t.clientX,n,this.currentPath);};this.lastTouchTime=0;this.handleTouchEnd=t=>{let e=t.changedTouches[0];if(!e)return;let i=document.documentElement.scrollWidth,n=document.documentElement.scrollHeight,s=e.clientY+window.scrollY;this.lastTouchTime=Date.now(),this.recordPoint({x:e.clientX,y:s,xPct:i>0?e.clientX/i*100:0,yPct:n>0?s/n*100:0,pw:i,ph:n,type:"click"}),this.checkRageClick(e.clientX,s,this.currentPath);};this.handleScroll=()=>{if(Math.random()>this.sampleRate)return;let t=document.documentElement.scrollWidth,e=document.documentElement.scrollHeight,i=window.innerWidth,n=window.scrollX,s=window.scrollY,r=n+i/2;this.recordPoint({x:i/2,y:s,xPct:t>0?r/t*100:50,yPct:e>0?s/e*100:0,pw:t,ph:e,type:"scroll"});};this.handleNavigate=t=>{this.currentPath=t.detail.path;};this.emit=t,this.sessionId=e,this.sampleRate=i,this.maxPoints=n,this.allowedPaths=s&&s.length>0?new Set(s):null,this.throttledMouseMove=v(this.handleMouseMove,50),this.throttledScroll=v(this.handleScroll,100);}init(){this.currentPath=window.location.pathname+window.location.search,document.addEventListener("mousemove",this.throttledMouseMove),document.addEventListener("click",this.handleClick),document.addEventListener("touchend",this.handleTouchEnd,{passive:true}),window.addEventListener("scroll",this.throttledScroll,{passive:true}),window.addEventListener("tracker:navigate",this.handleNavigate);}destroy(){document.removeEventListener("mousemove",this.throttledMouseMove),document.removeEventListener("click",this.handleClick),document.removeEventListener("touchend",this.handleTouchEnd),window.removeEventListener("scroll",this.throttledScroll),window.removeEventListener("tracker:navigate",this.handleNavigate);}canRecord(){var t;return this.allowedPaths!==null&&!this.allowedPaths.has(this.currentPath)?false:((t=this.pointCounts[this.currentPath])!=null?t:0)<this.maxPoints}recordPoint(t){var e;this.canRecord()&&(this.pointCounts[this.currentPath]=((e=this.pointCounts[this.currentPath])!=null?e:0)+1,this.emit({type:"heatmap",data:{...t,path:this.currentPath,timestamp:Date.now()}}));}getClickTarget(t){var n,s,r,l;let e=t.target;return e&&(e.getAttribute("aria-label")||((n=e.closest("[aria-label]"))==null?void 0:n.getAttribute("aria-label"))||e.getAttribute("data-track-label")||e.getAttribute("id")||(e instanceof HTMLButtonElement||e instanceof HTMLAnchorElement?(s=e.innerText)==null?void 0:s.trim().slice(0,60):(l=(r=e.closest("button, a"))==null?void 0:r.textContent)==null?void 0:l.trim().slice(0,60)))||void 0}checkRageClick(t,e,i){let n=Date.now();this.recentClicks=this.recentClicks.filter(r=>n-r.t<o.RAGE_WINDOW_MS),this.recentClicks.push({x:t,y:e,t:n});let s=this.recentClicks.filter(r=>Math.hypot(r.x-t,r.y-e)<=o.RAGE_RADIUS_PX);s.length>=o.RAGE_THRESHOLD&&(this.emit({type:"rageclik",data:{path:i,x:t,y:e,count:s.length,timestamp:n,sessionId:this.sessionId}}),this.recentClicks=[]);}};o.RAGE_THRESHOLD=3,o.RAGE_WINDOW_MS=1e3,o.RAGE_RADIUS_PX=50;var g=o;var c=class{constructor(t){this.prevOnError=null;this.prevOnUnhandledRejection=null;this.initialized=false;try{let e=new URL(t.endpoint),i=e.pathname.replace(/\/$/,"").split("/");i.pop(),e.pathname=i.join("/")||"/",this.endpoint=e.toString().replace(/\/$/,"");}catch(e){this.endpoint=t.endpoint;}this.sessionId=t.sessionId,this.appId=t.appId,this.authHeaders=t.secretKey?{Authorization:`Bearer ${t.secretKey}`}:{};}init(){typeof window=="undefined"||this.initialized||(this.origInfo=console.info.bind(console),this.origWarn=console.warn.bind(console),this.origError=console.error.bind(console),console.info=(...t)=>{this.origInfo(...t),this.send("info",this.format(t));},console.warn=(...t)=>{this.origWarn(...t),this.send("warn",this.format(t));},console.error=(...t)=>{this.origError(...t);let[e]=t,i=e instanceof Error?e.stack:void 0;this.send("error",this.format(t),{stack:i});},this.prevOnError=window.onerror,window.onerror=(t,e,i,n,s)=>(this.send("error",String(t),{stack:s==null?void 0:s.stack,meta:{src:e,line:i,col:n}}),typeof this.prevOnError=="function"?this.prevOnError(t,e,i,n,s):false),this.prevOnUnhandledRejection=t=>{let e=t.reason,i=e instanceof Error?e.message:String(e!=null?e:"Unhandled promise rejection");this.send("error",i,{stack:e instanceof Error?e.stack:void 0});},window.addEventListener("unhandledrejection",this.prevOnUnhandledRejection),this.initialized=true);}destroy(){this.initialized&&(console.info=this.origInfo,console.warn=this.origWarn,console.error=this.origError,window.onerror=this.prevOnError,this.prevOnUnhandledRejection&&window.removeEventListener("unhandledrejection",this.prevOnUnhandledRejection),this.initialized=false);}capture(t,e,i){this.send(t,e,i);}format(t){return t.map(e=>{if(e instanceof Error)return e.message;if(typeof e=="object")try{return JSON.stringify(e)}catch(i){return String(e)}return String(e)}).join(" ")}send(t,e,i){let n={sessionId:this.sessionId,...this.appId?{appId:this.appId}:{},level:t,message:e,url:typeof window!="undefined"?window.location.href:void 0,stack:i==null?void 0:i.stack,meta:i==null?void 0:i.meta,timestamp:Date.now()},s=`${this.endpoint}/logs/ingest`,r=JSON.stringify(n);fetch(s,{method:"POST",headers:{"Content-Type":"application/json",...this.authHeaders},body:r,keepalive:true}).catch(l=>{this.origError&&this.origError("[user-tracker] Failed to send log:",l);});}};var k="https://api.alphana.ir/api/events",I={endpoint:k,trackNavigation:true,trackTime:true,trackHeatmap:true,trackLogs:true,mouseSampleRate:.3,maxHeatmapPoints:2e3,batchSize:20,flushInterval:5e3},m=class{constructor(t={}){this.initialized=false;this.subscribers=new Set;this.queue=[];this.flushTimer=null;this.heartbeatTimer=null;this.userProperties={};this.flags={};this.flagSubscribers=new Set;this.handleVisibilityChange=()=>{document.visibilityState==="hidden"&&(this.queue.length>0&&this.flushBeacon(),this.sendDeactivate());};this.handlePageHide=()=>{this.queue.length>0&&this.flushBeacon(),this.sendDeactivate();};this.handleNavigate=t=>{};var e;this.cfg={...I,...t};try{new URL(this.cfg.endpoint);}catch(i){throw new Error(`[alpha-tracker] Invalid endpoint URL: "${this.cfg.endpoint}"`)}this.session={id:(e=t.sessionId)!=null?e:d(),visitorId:y(),startedAt:Date.now(),pageViews:[],timeSpent:{},heatmap:{}};}init(){if(typeof window=="undefined"||this.initialized)return this;let t=this.emit.bind(this),{id:e}=this.session;return this.cfg.trackNavigation&&(this.navigation=new p({emit:t,sessionId:e}),this.navigation.init()),this.cfg.trackTime&&(this.time=new u({emit:t,sessionId:e}),this.time.init()),this.cfg.trackHeatmap&&(this.heatmap=new g({emit:t,sessionId:e,sampleRate:this.cfg.mouseSampleRate,maxPoints:this.cfg.maxHeatmapPoints,allowedPaths:this.cfg.heatmapPages}),this.heatmap.init()),this.cfg.endpoint&&(this.flushTimer=setInterval(()=>{this.queue.length>0&&this.flushQueue();},this.cfg.flushInterval),window.addEventListener("visibilitychange",this.handleVisibilityChange),window.addEventListener("pagehide",this.handlePageHide),this.heartbeatTimer=setInterval(()=>{document.visibilityState!=="hidden"&&this.sendHeartbeat();},3e4),this.cfg.trackLogs&&(this.logCapture=new c({endpoint:this.cfg.endpoint,sessionId:this.session.id,secretKey:this.cfg.secretKey,appId:this.cfg.appId}),this.logCapture.init())),this.initialized=true,this.fetchFlags(),this}destroy(){var t,e,i,n;this.flushTimer!==null&&(clearInterval(this.flushTimer),this.flushTimer=null),this.heartbeatTimer!==null&&(clearInterval(this.heartbeatTimer),this.heartbeatTimer=null),typeof window!="undefined"&&(window.removeEventListener("visibilitychange",this.handleVisibilityChange),window.removeEventListener("pagehide",this.handlePageHide)),(t=this.navigation)==null||t.destroy(),(e=this.time)==null||e.destroy(),(i=this.heatmap)==null||i.destroy(),(n=this.logCapture)==null||n.destroy(),typeof window!="undefined"&&window.removeEventListener("tracker:navigate",this.handleNavigate),this.queue.length>0&&this.cfg.endpoint&&this.flushBeacon(),this.initialized=false;}async sendHeartbeat(){if(!this.cfg.endpoint)return;let t=`${this.cfg.endpoint}/heartbeat`,e=this.cfg.secretKey?{Authorization:`Bearer ${this.cfg.secretKey}`}:{},i=JSON.stringify({sessionId:this.session.id,visitorId:this.session.visitorId,path:typeof window!="undefined"?window.location.pathname:"/",active:true,...this.cfg.appId?{appId:this.cfg.appId}:{}});try{await fetch(t,{method:"POST",headers:{"Content-Type":"application/json",...e},body:i,keepalive:!0});}catch(n){}}sendDeactivate(){if(!this.cfg.endpoint)return;let t=`${this.cfg.endpoint}/heartbeat`,e=this.cfg.secretKey?{Authorization:`Bearer ${this.cfg.secretKey}`}:{},i=JSON.stringify({sessionId:this.session.id,path:typeof window!="undefined"?window.location.pathname:"/",active:false,...this.cfg.appId?{appId:this.cfg.appId}:{}});typeof navigator!="undefined"&&navigator.sendBeacon?navigator.sendBeacon(t,new Blob([i],{type:"application/json"})):fetch(t,{method:"POST",headers:{"Content-Type":"application/json",...e},body:i,keepalive:true}).catch(()=>{});}emit(t){var e,i,n;switch(t.type){case "pageview":this.session.pageViews.push(t.data);break;case "timespent":{let s=(e=this.session.timeSpent[t.data.path])!=null?e:0;this.session.timeSpent[t.data.path]=s+t.data.duration;break}case "heatmap":{let s=t.data.path;this.session.heatmap[s]||(this.session.heatmap[s]=[]);let r=this.session.heatmap[s];r.length<this.cfg.maxHeatmapPoints&&r.push(t.data);break}}this.subscribers.forEach(s=>s(t)),(n=(i=this.cfg).onEvent)==null||n.call(i,t),this.cfg.endpoint&&(this.queue.push(t),this.queue.length>=this.cfg.batchSize&&this.flushQueue());}subscribe(t){return this.subscribers.add(t),()=>this.subscribers.delete(t)}trackPageView(t){let e=t!=null?t:typeof window!="undefined"?window.location.pathname+window.location.search:"/";this.emit({type:"pageview",data:{path:e,title:typeof document!="undefined"?document.title:"",timestamp:Date.now(),sessionId:this.session.id,referrer:typeof document!="undefined"&&document.referrer||void 0}}),typeof window!="undefined"&&window.dispatchEvent(new CustomEvent("tracker:navigate",{detail:{path:e,title:document.title}}));}identify(t){this.userProperties={...this.userProperties,...t},this.fetchFlags();}getFlags(){return {...this.flags}}isFeatureEnabled(t){return this.flags[t]===true}onFlagsChange(t){return this.flagSubscribers.add(t),()=>this.flagSubscribers.delete(t)}async fetchFlags(){if(!this.cfg.endpoint||!this.cfg.secretKey)return;let t=this.cfg.endpoint.replace(/\/events$/,"")+"/feature-flags/evaluate";try{let e=await fetch(t,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.cfg.secretKey}`},body:JSON.stringify({userProperties:this.userProperties})});if(!e.ok)return;let i=await e.json();this.flags=i,this.flagSubscribers.forEach(n=>n({...this.flags}));}catch(e){}}getSession(){return this.session}getPageViews(){return [...this.session.pageViews]}getTimeSpent(){return {...this.session.timeSpent}}getHeatmapData(t){var e;return t!==void 0?[...(e=this.session.heatmap[t])!=null?e:[]]:Object.entries(this.session.heatmap).reduce((i,[n,s])=>(i[n]=[...s],i),{})}flush(){this.flushQueue();}async flushQueue(){if(this.queue.length===0)return;let t=this.queue.splice(0);await this.sendBatch(t);}flushBeacon(){if(this.queue.length===0)return;let t=this.queue.splice(0),e=`${this.cfg.endpoint}/batch`,i=new Blob([this.buildBatchBody(t)],{type:"application/json"});typeof navigator!="undefined"&&navigator.sendBeacon?navigator.sendBeacon(e,i):this.sendBatch(t);}buildBatchBody(t){return JSON.stringify({...this.cfg.appId?{appId:this.cfg.appId}:{},visitorId:this.session.visitorId,events:t.map(e=>({sessionId:this.session.id,type:e.type,data:e.data}))})}async sendBatch(t){let e=`${this.cfg.endpoint}/batch`,i=this.cfg.secretKey?{Authorization:`Bearer ${this.cfg.secretKey}`}:{};try{await fetch(e,{method:"POST",headers:{"Content-Type":"application/json",...i},body:this.buildBatchBody(t),keepalive:!0});}catch(n){}}};
2
+ exports.DEFAULT_ENDPOINT=k;exports.LogCapture=c;exports.UserTracker=m;//# sourceMappingURL=index.js.map
3
3
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/utils/session.ts","../src/core/navigation.ts","../src/core/time.ts","../src/utils/throttle.ts","../src/core/heatmap.ts","../src/core/logger.ts","../src/tracker.ts"],"names":["VISITOR_STORAGE_KEY","generateSessionId","c","r","getOrCreateVisitorId","existing","id","e","_NavigationPlugin","emit","sessionId","origPush","state","title","url","origReplace","newPath","timeOnPage","path","NavigationPlugin","TimePlugin","duration","throttle","fn","delay","lastCall","args","now","_HeatmapPlugin","sampleRate","maxPoints","allowedPaths","pageWidth","pageHeight","absY","target","touch","vw","scrollX","scrollY","absX","_a","point","_b","_c","_d","el","x","y","nearby","HeatmapPlugin","LogCapture","options","u","parts","first","stack","msg","src","line","col","err","reason","message","level","extra","a","entry","body","DEFAULT_ENDPOINT","DEFAULTS","UserTracker","config","_e","authHeaders","event","prev","key","pts","resolvedPath","properties","flagKey","res","data","acc","k","v","batch","blob","events"],"mappings":"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","file":"index.js","sourcesContent":["const SESSION_STORAGE_KEY = \"__ut_sid__\";\nconst VISITOR_STORAGE_KEY = \"__ut_vid__\";\n\n/** Generate a RFC-4122 v4 UUID using the native crypto API with a fallback. */\nexport function generateSessionId(): string {\n if (\n typeof crypto !== \"undefined\" &&\n typeof crypto.randomUUID === \"function\"\n ) {\n return crypto.randomUUID();\n }\n // Math.random fallback (not cryptographically secure, but sufficient for analytics)\n return \"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx\".replace(/[xy]/g, (c) => {\n const r = (Math.random() * 16) | 0;\n const v = c === \"x\" ? r : (r & 0x3) | 0x8;\n return v.toString(16);\n });\n}\n\n/**\n * Retrieve the session ID from sessionStorage, or create and persist a new one.\n * Falls back to an in-memory ID when sessionStorage is unavailable (e.g. SSR).\n */\nexport function getOrCreateSessionId(): string {\n if (typeof sessionStorage === \"undefined\") return generateSessionId();\n try {\n const existing = sessionStorage.getItem(SESSION_STORAGE_KEY);\n if (existing) return existing;\n const id = generateSessionId();\n sessionStorage.setItem(SESSION_STORAGE_KEY, id);\n return id;\n } catch {\n return generateSessionId();\n }\n}\n\n/**\n * Retrieve the visitor ID from localStorage (persists across sessions/tabs),\n * or create and store a new one. Returns a temporary in-memory ID when\n * localStorage is unavailable.\n */\nexport function getOrCreateVisitorId(): string {\n if (typeof localStorage === \"undefined\") return generateSessionId();\n try {\n const existing = localStorage.getItem(VISITOR_STORAGE_KEY);\n if (existing) return existing;\n const id = generateSessionId();\n localStorage.setItem(VISITOR_STORAGE_KEY, id);\n return id;\n } catch {\n return generateSessionId();\n }\n}\n","import type { TrackerEvent } from \"../types\";\n\ntype EmitFn = (event: TrackerEvent) => void;\n\ninterface NavigationPluginOptions {\n emit: EmitFn;\n sessionId: string;\n}\n\n/**\n * Tracks SPA route changes by monkey-patching history.pushState /\n * history.replaceState and listening to the popstate event.\n *\n * For every navigation it:\n * 1. Emits a `pageview` event.\n * 2. Dispatches the custom DOM event `tracker:navigate` so that other\n * plugins (TimePlugin, HeatmapPlugin) can react without having to\n * duplicate the pushState patching.\n * 3. Detects U-turns: if the user leaves a page in ≤5 s, a `uturn` event\n * is emitted with the time-on-page and destination path.\n *\n * Next.js App Router note:\n * The App Router manages navigation internally; use `usePageView(pathname)`\n * from `user-tracker/react` together with `usePathname()` instead.\n */\nexport class NavigationPlugin {\n private readonly emit: EmitFn;\n private readonly sessionId: string;\n private previousPath = \"\";\n private originalPushState: typeof history.pushState | null = null;\n private originalReplaceState: typeof history.replaceState | null = null;\n private pageEntryTime = 0;\n private static readonly UTURN_THRESHOLD_MS = 5_000;\n\n constructor({ emit, sessionId }: NavigationPluginOptions) {\n this.emit = emit;\n this.sessionId = sessionId;\n }\n\n init(): void {\n // Record the initial page view on load.\n this.recordPageView(window.location.pathname + window.location.search);\n\n window.addEventListener(\"popstate\", this.handlePopState);\n\n // Patch pushState\n this.originalPushState = history.pushState.bind(history);\n const origPush = this.originalPushState;\n history.pushState = (state, title, url): void => {\n origPush(state, title, url);\n this.handleNavigation();\n };\n\n // Patch replaceState\n this.originalReplaceState = history.replaceState.bind(history);\n const origReplace = this.originalReplaceState;\n history.replaceState = (state, title, url): void => {\n origReplace(state, title, url);\n this.handleNavigation();\n };\n }\n\n destroy(): void {\n window.removeEventListener(\"popstate\", this.handlePopState);\n if (this.originalPushState) history.pushState = this.originalPushState;\n if (this.originalReplaceState)\n history.replaceState = this.originalReplaceState;\n }\n\n // Arrow property → always bound to `this`, safe to use as event listener.\n private handlePopState = (): void => {\n this.handleNavigation();\n };\n\n private handleNavigation(): void {\n const newPath = window.location.pathname + window.location.search;\n if (newPath === this.previousPath) return; // hash-only or duplicate call\n\n // U-turn detection: user left the previous page very quickly.\n if (this.previousPath && this.pageEntryTime > 0) {\n const timeOnPage = Date.now() - this.pageEntryTime;\n if (timeOnPage <= NavigationPlugin.UTURN_THRESHOLD_MS) {\n this.emit({\n type: \"uturn\",\n data: {\n fromPath: this.previousPath,\n toPath: newPath,\n timeOnPageMs: timeOnPage,\n timestamp: Date.now(),\n sessionId: this.sessionId,\n },\n });\n }\n }\n\n this.recordPageView(newPath);\n }\n\n private recordPageView(path: string): void {\n this.previousPath = path;\n this.pageEntryTime = Date.now();\n\n this.emit({\n type: \"pageview\",\n data: {\n path,\n title: document.title,\n timestamp: Date.now(),\n sessionId: this.sessionId,\n referrer: document.referrer || undefined,\n },\n });\n\n // Notify other plugins via a custom DOM event (synchronous dispatch).\n window.dispatchEvent(\n new CustomEvent(\"tracker:navigate\", {\n detail: { path, title: document.title },\n }),\n );\n }\n}\n","import type { TrackerEvent } from \"../types\";\n\ntype EmitFn = (event: TrackerEvent) => void;\n\ninterface TimePluginOptions {\n emit: EmitFn;\n sessionId: string;\n}\n\n/**\n * Tracks the time a user spends on each page.\n *\n * - Starts a timer when the page becomes active (init / tab focus).\n * - Stops and emits a `timespent` event when:\n * • The user navigates away (tracker:navigate)\n * • The tab is hidden (visibilitychange)\n * • The page is unloading (beforeunload / pagehide)\n * - Resumes timing when the tab becomes visible again.\n */\nexport class TimePlugin {\n private readonly emit: EmitFn;\n private readonly sessionId: string;\n private currentPath = \"\";\n private startTime = 0;\n private tracking = false;\n\n constructor({ emit, sessionId }: TimePluginOptions) {\n this.emit = emit;\n this.sessionId = sessionId;\n }\n\n init(): void {\n this.currentPath = window.location.pathname + window.location.search;\n this.startTracking();\n\n window.addEventListener(\"tracker:navigate\", this.handleNavigate);\n document.addEventListener(\"visibilitychange\", this.handleVisibilityChange);\n window.addEventListener(\"beforeunload\", this.handleUnload);\n window.addEventListener(\"pagehide\", this.handleUnload);\n }\n\n destroy(): void {\n this.stopTracking();\n window.removeEventListener(\"tracker:navigate\", this.handleNavigate);\n document.removeEventListener(\n \"visibilitychange\",\n this.handleVisibilityChange,\n );\n window.removeEventListener(\"beforeunload\", this.handleUnload);\n window.removeEventListener(\"pagehide\", this.handleUnload);\n }\n\n private startTracking(): void {\n this.startTime = Date.now();\n this.tracking = true;\n }\n\n private stopTracking(): void {\n if (!this.tracking || !this.currentPath) return;\n const duration = Date.now() - this.startTime;\n if (duration < 100) {\n this.tracking = false;\n return; // Ignore sub-100 ms blips (e.g. rapid navigations).\n }\n this.emit({\n type: \"timespent\",\n data: {\n path: this.currentPath,\n duration,\n sessionId: this.sessionId,\n timestamp: Date.now(),\n },\n });\n this.tracking = false;\n }\n\n private handleNavigate = (e: CustomEvent<{ path: string }>): void => {\n this.stopTracking();\n this.currentPath = e.detail.path;\n this.startTracking();\n };\n\n private handleVisibilityChange = (): void => {\n if (document.hidden) {\n this.stopTracking();\n } else {\n this.startTracking();\n }\n };\n\n private handleUnload = (): void => {\n this.stopTracking();\n };\n}\n","/**\n * Returns a function that invokes `fn` at most once every `delay` ms.\n * The first call in a new window is executed immediately.\n */\nexport function throttle<Args extends unknown[]>(\n fn: (...args: Args) => void,\n delay: number,\n): (...args: Args) => void {\n let lastCall = 0;\n return (...args: Args): void => {\n const now = Date.now();\n if (now - lastCall >= delay) {\n lastCall = now;\n fn(...args);\n }\n };\n}\n","import type { TrackerEvent, HeatmapPoint } from \"../types\";\nimport { throttle } from \"../utils/throttle\";\n\ntype EmitFn = (event: TrackerEvent) => void;\n\ninterface HeatmapPluginOptions {\n emit: EmitFn;\n sessionId: string;\n /** Fraction of mousemove / scroll events to sample (0–1). Default: 0.3 */\n sampleRate?: number;\n /** Maximum points stored per page before recording stops. Default: 2000 */\n maxPoints?: number;\n /**\n * If provided, heatmap data will only be collected for paths in this list.\n * An empty array or undefined means all pages are tracked.\n */\n allowedPaths?: string[];\n}\n\n/**\n * Collects mouse-move, click, and scroll positions for heatmap analysis.\n *\n * Also detects rage clicks (≥3 clicks within 1 s in a 50 px radius) and emits\n * a `rageclik` event so the backend can surface problematic UI hotspots.\n *\n * Click events include an optional `target` field containing a human-readable\n * label for the clicked element (aria-label › id › button/link text).\n */\nexport class HeatmapPlugin {\n private readonly emit: EmitFn;\n private readonly sessionId: string;\n private readonly sampleRate: number;\n private readonly maxPoints: number;\n private readonly allowedPaths: Set<string> | null;\n private currentPath = \"\";\n private pointCounts: Record<string, number> = {};\n\n // Rage-click detection state\n private recentClicks: { x: number; y: number; t: number }[] = [];\n private static readonly RAGE_THRESHOLD = 3;\n private static readonly RAGE_WINDOW_MS = 1_000;\n private static readonly RAGE_RADIUS_PX = 50;\n\n private readonly throttledMouseMove: (e: MouseEvent) => void;\n private readonly throttledScroll: () => void;\n\n constructor({\n emit,\n sessionId,\n sampleRate = 0.3,\n maxPoints = 2000,\n allowedPaths,\n }: HeatmapPluginOptions) {\n this.emit = emit;\n this.sessionId = sessionId;\n this.sampleRate = sampleRate;\n this.maxPoints = maxPoints;\n this.allowedPaths =\n allowedPaths && allowedPaths.length > 0 ? new Set(allowedPaths) : null;\n\n this.throttledMouseMove = throttle(this.handleMouseMove, 50);\n this.throttledScroll = throttle(this.handleScroll, 100);\n }\n\n init(): void {\n this.currentPath = window.location.pathname + window.location.search;\n\n document.addEventListener(\"mousemove\", this.throttledMouseMove);\n document.addEventListener(\"click\", this.handleClick);\n document.addEventListener(\"touchend\", this.handleTouchEnd, {\n passive: true,\n });\n window.addEventListener(\"scroll\", this.throttledScroll, { passive: true });\n window.addEventListener(\"tracker:navigate\", this.handleNavigate);\n }\n\n destroy(): void {\n document.removeEventListener(\"mousemove\", this.throttledMouseMove);\n document.removeEventListener(\"click\", this.handleClick);\n document.removeEventListener(\"touchend\", this.handleTouchEnd);\n window.removeEventListener(\"scroll\", this.throttledScroll);\n window.removeEventListener(\"tracker:navigate\", this.handleNavigate);\n }\n\n private canRecord(): boolean {\n if (\n this.allowedPaths !== null &&\n !this.allowedPaths.has(this.currentPath)\n ) {\n return false;\n }\n return (this.pointCounts[this.currentPath] ?? 0) < this.maxPoints;\n }\n\n private recordPoint(\n point: Omit<HeatmapPoint, \"path\" | \"timestamp\" | \"sessionId\">,\n ): void {\n if (!this.canRecord()) return;\n this.pointCounts[this.currentPath] =\n (this.pointCounts[this.currentPath] ?? 0) + 1;\n this.emit({\n type: \"heatmap\",\n data: {\n ...point,\n path: this.currentPath,\n timestamp: Date.now(),\n },\n });\n }\n\n /** Extract a human-readable label for the clicked element. */\n private getClickTarget(e: MouseEvent): string | undefined {\n const el = e.target as HTMLElement | null;\n if (!el) return undefined;\n const label =\n el.getAttribute(\"aria-label\") ||\n el.closest(\"[aria-label]\")?.getAttribute(\"aria-label\") ||\n el.getAttribute(\"data-track-label\") ||\n el.getAttribute(\"id\") ||\n (el instanceof HTMLButtonElement || el instanceof HTMLAnchorElement\n ? el.innerText?.trim().slice(0, 60)\n : el.closest(\"button, a\")?.textContent?.trim().slice(0, 60));\n return label || undefined;\n }\n\n /** Detect rage-click bursts and emit a `rageclik` event when found. */\n private checkRageClick(x: number, y: number, path: string): void {\n const now = Date.now();\n this.recentClicks = this.recentClicks.filter(\n (c) => now - c.t < HeatmapPlugin.RAGE_WINDOW_MS,\n );\n this.recentClicks.push({ x, y, t: now });\n\n const nearby = this.recentClicks.filter(\n (c) => Math.hypot(c.x - x, c.y - y) <= HeatmapPlugin.RAGE_RADIUS_PX,\n );\n\n if (nearby.length >= HeatmapPlugin.RAGE_THRESHOLD) {\n this.emit({\n type: \"rageclik\",\n data: {\n path,\n x,\n y,\n count: nearby.length,\n timestamp: now,\n sessionId: this.sessionId,\n },\n });\n // Reset to avoid continuously re-triggering in the same burst.\n this.recentClicks = [];\n }\n }\n\n private handleMouseMove = (e: MouseEvent): void => {\n if (Math.random() > this.sampleRate) return;\n const pageWidth = document.documentElement.scrollWidth;\n const pageHeight = document.documentElement.scrollHeight;\n const absY = e.clientY + window.scrollY;\n this.recordPoint({\n x: e.clientX,\n y: absY,\n xPct: pageWidth > 0 ? (e.clientX / pageWidth) * 100 : 0,\n yPct: pageHeight > 0 ? (absY / pageHeight) * 100 : 0,\n pw: pageWidth,\n ph: pageHeight,\n type: \"move\",\n });\n };\n\n private handleClick = (e: MouseEvent): void => {\n // Skip if a touchend already recorded this tap (within 500 ms)\n if (Date.now() - this.lastTouchTime < 500) return;\n const pageWidth = document.documentElement.scrollWidth;\n const pageHeight = document.documentElement.scrollHeight;\n const absY = e.clientY + window.scrollY;\n const target = this.getClickTarget(e);\n this.recordPoint({\n x: e.clientX,\n y: absY,\n xPct: pageWidth > 0 ? (e.clientX / pageWidth) * 100 : 0,\n yPct: pageHeight > 0 ? (absY / pageHeight) * 100 : 0,\n pw: pageWidth,\n ph: pageHeight,\n type: \"click\",\n ...(target ? { target } : {}),\n });\n this.checkRageClick(e.clientX, absY, this.currentPath);\n };\n\n /**\n * Records taps on touch devices.\n * Mobile browsers do fire a synthetic `click` after `touchend`, but some\n * frameworks call `preventDefault()` on touch events which suppresses it.\n * Listening to `touchend` directly ensures taps are always captured.\n * We mark the point with a short-lived flag so the subsequent synthetic\n * `click` event (if it fires) is deduplicated.\n */\n private lastTouchTime = 0;\n\n private handleTouchEnd = (e: TouchEvent): void => {\n const touch = e.changedTouches[0];\n if (!touch) return;\n const pageWidth = document.documentElement.scrollWidth;\n const pageHeight = document.documentElement.scrollHeight;\n const absY = touch.clientY + window.scrollY;\n this.lastTouchTime = Date.now();\n this.recordPoint({\n x: touch.clientX,\n y: absY,\n xPct: pageWidth > 0 ? (touch.clientX / pageWidth) * 100 : 0,\n yPct: pageHeight > 0 ? (absY / pageHeight) * 100 : 0,\n pw: pageWidth,\n ph: pageHeight,\n type: \"click\",\n });\n this.checkRageClick(touch.clientX, absY, this.currentPath);\n };\n\n private handleScroll = (): void => {\n if (Math.random() > this.sampleRate) return;\n const pageWidth = document.documentElement.scrollWidth;\n const pageHeight = document.documentElement.scrollHeight;\n const vw = window.innerWidth;\n const scrollX = window.scrollX;\n const scrollY = window.scrollY;\n const absX = scrollX + vw / 2;\n this.recordPoint({\n x: vw / 2,\n y: scrollY,\n xPct: pageWidth > 0 ? (absX / pageWidth) * 100 : 50,\n yPct: pageHeight > 0 ? (scrollY / pageHeight) * 100 : 0,\n pw: pageWidth,\n ph: pageHeight,\n type: \"scroll\",\n });\n };\n\n private handleNavigate = (e: CustomEvent<{ path: string }>): void => {\n this.currentPath = e.detail.path;\n };\n}\n","export type LogLevel = \"info\" | \"warn\" | \"error\";\n\nexport interface LogEntry {\n sessionId?: string;\n appId?: string;\n level: LogLevel;\n message: string;\n url?: string;\n stack?: string;\n meta?: Record<string, unknown>;\n timestamp: number;\n}\n\ntype ConsoleFn = (...args: unknown[]) => void;\n\n/**\n * Automatically captures console.info/warn/error output and unhandled errors,\n * then ships them to the backend `/logs/ingest` endpoint.\n *\n * All console methods are restored exactly in `destroy()`.\n */\nexport class LogCapture {\n private readonly endpoint: string;\n private readonly sessionId: string;\n private readonly appId?: string;\n private readonly authHeaders: Record<string, string>;\n\n // Original console methods preserved so we can restore them.\n private origInfo!: ConsoleFn;\n private origWarn!: ConsoleFn;\n private origError!: ConsoleFn;\n\n private prevOnError: OnErrorEventHandler = null;\n private prevOnUnhandledRejection:\n | ((e: PromiseRejectionEvent) => void)\n | null = null;\n\n private initialized = false;\n\n constructor(options: {\n endpoint: string;\n sessionId: string;\n secretKey?: string;\n appId?: string;\n }) {\n // Derive the API base URL by stripping everything from the last path\n // segment that isn't a versioning prefix. The tracker config `endpoint`\n // is the *events* URL (e.g. http://host/api/events), but logs live at\n // http://host/api/logs/ingest, so we walk up until we reach the common\n // base (i.e. remove the final segment).\n try {\n const u = new URL(options.endpoint);\n // Remove the last non-empty path segment (e.g. \"/api/events\" → \"/api\")\n const parts = u.pathname.replace(/\\/$/, \"\").split(\"/\");\n parts.pop();\n u.pathname = parts.join(\"/\") || \"/\";\n this.endpoint = u.toString().replace(/\\/$/, \"\");\n } catch {\n this.endpoint = options.endpoint;\n }\n this.sessionId = options.sessionId;\n this.appId = options.appId;\n this.authHeaders = options.secretKey\n ? { Authorization: `Bearer ${options.secretKey}` }\n : {};\n }\n\n init(): void {\n if (typeof window === \"undefined\" || this.initialized) return;\n\n this.origInfo = console.info.bind(console);\n this.origWarn = console.warn.bind(console);\n this.origError = console.error.bind(console);\n\n console.info = (...args: unknown[]) => {\n this.origInfo(...args);\n this.send(\"info\", this.format(args));\n };\n\n console.warn = (...args: unknown[]) => {\n this.origWarn(...args);\n this.send(\"warn\", this.format(args));\n };\n\n console.error = (...args: unknown[]) => {\n this.origError(...args);\n const [first] = args;\n const stack = first instanceof Error ? first.stack : undefined;\n this.send(\"error\", this.format(args), { stack });\n };\n\n this.prevOnError = window.onerror;\n window.onerror = (msg, src, line, col, err) => {\n this.send(\"error\", String(msg), {\n stack: err?.stack,\n meta: { src, line, col },\n });\n if (typeof this.prevOnError === \"function\") {\n return this.prevOnError(msg, src, line, col, err);\n }\n return false;\n };\n\n this.prevOnUnhandledRejection = (e: PromiseRejectionEvent) => {\n const reason = e.reason;\n const message =\n reason instanceof Error\n ? reason.message\n : String(reason ?? \"Unhandled promise rejection\");\n this.send(\"error\", message, {\n stack: reason instanceof Error ? reason.stack : undefined,\n });\n };\n window.addEventListener(\n \"unhandledrejection\",\n this.prevOnUnhandledRejection,\n );\n\n this.initialized = true;\n }\n\n destroy(): void {\n if (!this.initialized) return;\n console.info = this.origInfo;\n console.warn = this.origWarn;\n console.error = this.origError;\n\n window.onerror = this.prevOnError;\n if (this.prevOnUnhandledRejection) {\n window.removeEventListener(\n \"unhandledrejection\",\n this.prevOnUnhandledRejection,\n );\n }\n this.initialized = false;\n }\n\n /** Manually capture a log entry (e.g. from try/catch). */\n capture(\n level: LogLevel,\n message: string,\n extra?: { stack?: string; meta?: Record<string, unknown> },\n ): void {\n this.send(level, message, extra);\n }\n\n private format(args: unknown[]): string {\n return args\n .map((a) => {\n if (a instanceof Error) return a.message;\n if (typeof a === \"object\") {\n try {\n return JSON.stringify(a);\n } catch {\n return String(a);\n }\n }\n return String(a);\n })\n .join(\" \");\n }\n\n private send(\n level: LogLevel,\n message: string,\n extra?: { stack?: string; meta?: Record<string, unknown> },\n ): void {\n const entry: LogEntry = {\n sessionId: this.sessionId,\n ...(this.appId ? { appId: this.appId } : {}),\n level,\n message,\n url: typeof window !== \"undefined\" ? window.location.href : undefined,\n stack: extra?.stack,\n meta: extra?.meta,\n timestamp: Date.now(),\n };\n const url = `${this.endpoint}/logs/ingest`;\n const body = JSON.stringify(entry);\n\n // Use fetch with keepalive so the request survives page navigation.\n // Errors are logged to the (original, unpatched) console so they are\n // visible in DevTools without creating an infinite log loop.\n void fetch(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\", ...this.authHeaders },\n body,\n keepalive: true,\n }).catch((err: unknown) => {\n // Use the original (pre-patch) error logger to avoid recursion.\n if (this.origError) {\n this.origError(\"[user-tracker] Failed to send log:\", err);\n }\n });\n }\n}\n","import type {\n TrackerConfig,\n TrackerEvent,\n SessionData,\n PageView,\n HeatmapPoint,\n} from \"./types\";\nimport { generateSessionId, getOrCreateVisitorId } from \"./utils/session\";\nimport { NavigationPlugin } from \"./core/navigation\";\nimport { TimePlugin } from \"./core/time\";\nimport { HeatmapPlugin } from \"./core/heatmap\";\nimport { LogCapture } from \"./core/logger\";\n\nexport const DEFAULT_ENDPOINT = \"https://api.alphana.ir/api/events\";\n\nconst DEFAULTS = {\n endpoint: DEFAULT_ENDPOINT,\n trackNavigation: true,\n trackTime: true,\n trackHeatmap: true,\n trackLogs: true,\n mouseSampleRate: 0.3,\n maxHeatmapPoints: 2000,\n batchSize: 20,\n flushInterval: 5_000,\n} as const;\n\ntype SubscriberFn = (event: TrackerEvent) => void;\ntype FlagSubscriberFn = (flags: Record<string, boolean>) => void;\n\n/**\n * Core tracker class. Framework-agnostic — works in any environment that has\n * a browser DOM (React, Next.js Pages Router, Vite, vanilla JS/TS, etc.).\n *\n * Usage:\n * ```ts\n * const tracker = new UserTracker({ endpoint: 'https://my-api.com/events' });\n * tracker.init(); // call once; safe to call in SSR (no-op server-side)\n * ```\n *\n * Destroy when done (e.g. component unmount):\n * ```ts\n * tracker.destroy();\n * ```\n */\ntype ResolvedConfig = Required<\n Pick<\n TrackerConfig,\n | \"endpoint\"\n | \"trackNavigation\"\n | \"trackTime\"\n | \"trackHeatmap\"\n | \"trackLogs\"\n | \"mouseSampleRate\"\n | \"maxHeatmapPoints\"\n | \"batchSize\"\n | \"flushInterval\"\n >\n> &\n TrackerConfig;\n\nexport class UserTracker {\n private readonly cfg: ResolvedConfig;\n private session: SessionData;\n private navigation?: NavigationPlugin;\n private time?: TimePlugin;\n private heatmap?: HeatmapPlugin;\n /** Public so consumers can call logCapture.capture() for manual log entries. */\n logCapture?: LogCapture;\n private initialized = false;\n private readonly subscribers = new Set<SubscriberFn>();\n\n /** In-memory queue of events waiting to be flushed. */\n private queue: TrackerEvent[] = [];\n private flushTimer: ReturnType<typeof setInterval> | null = null;\n private heartbeatTimer: ReturnType<typeof setInterval> | null = null;\n\n // ─── Feature flags ────────────────────────────────────────────────────────\n private userProperties: Record<string, string> = {};\n private flags: Record<string, boolean> = {};\n private readonly flagSubscribers = new Set<FlagSubscriberFn>();\n\n constructor(config: TrackerConfig = {}) {\n this.cfg = { ...DEFAULTS, ...config } as ResolvedConfig;\n\n // Validate endpoint URL up front so the error is thrown at construction\n // time rather than silently failing during a network request.\n try {\n new URL(this.cfg.endpoint);\n } catch {\n throw new Error(\n `[alpha-tracker] Invalid endpoint URL: \"${this.cfg.endpoint}\"`,\n );\n }\n\n this.session = {\n id: config.sessionId ?? generateSessionId(),\n visitorId: getOrCreateVisitorId(),\n startedAt: Date.now(),\n pageViews: [],\n timeSpent: {},\n heatmap: {},\n };\n }\n\n // ─── Lifecycle ──────────────────────────────────────────────────────────────\n\n /**\n * Attach event listeners and start tracking.\n * Safe to call during SSR — returns `this` immediately if `window` is\n * undefined so it can be chained: `const tracker = new UserTracker(cfg).init()`.\n */\n init(): this {\n if (typeof window === \"undefined\" || this.initialized) return this;\n\n const emit = this.emit.bind(this);\n const { id: sessionId } = this.session;\n\n if (this.cfg.trackNavigation) {\n this.navigation = new NavigationPlugin({ emit, sessionId });\n this.navigation.init();\n }\n\n if (this.cfg.trackTime) {\n this.time = new TimePlugin({ emit, sessionId });\n this.time.init();\n }\n\n if (this.cfg.trackHeatmap) {\n this.heatmap = new HeatmapPlugin({\n emit,\n sessionId,\n sampleRate: this.cfg.mouseSampleRate,\n maxPoints: this.cfg.maxHeatmapPoints,\n allowedPaths: this.cfg.heatmapPages,\n });\n this.heatmap.init();\n }\n\n if (this.cfg.endpoint) {\n // Flush on a regular interval — even if the batch threshold isn't hit.\n this.flushTimer = setInterval(() => {\n if (this.queue.length > 0) void this.flushQueue();\n }, this.cfg.flushInterval);\n\n // Visitor location is resolved server-side from the request IP using\n // an offline GeoIP database — no third-party requests from the SDK.\n\n // Flush remaining queue when the tab is hidden or the page is unloaded.\n window.addEventListener(\"visibilitychange\", this.handleVisibilityChange);\n window.addEventListener(\"pagehide\", this.handlePageHide);\n\n // Periodic keep-alive heartbeat every 30 s so the backend knows the\n // session is still active and doesn't expire it prematurely.\n this.heartbeatTimer = setInterval(() => {\n if (document.visibilityState !== \"hidden\") void this.sendHeartbeat();\n }, 30_000);\n\n // Auto-capture console logs and unhandled errors.\n if (this.cfg.trackLogs) {\n this.logCapture = new LogCapture({\n endpoint: this.cfg.endpoint,\n sessionId: this.session.id,\n secretKey: this.cfg.secretKey,\n appId: this.cfg.appId,\n });\n this.logCapture.init();\n }\n }\n\n this.initialized = true;\n\n // Fetch feature flags in the background on init.\n void this.fetchFlags();\n\n return this;\n }\n\n /** Remove all event listeners, flush remaining queue, and reset state. */\n destroy(): void {\n if (this.flushTimer !== null) {\n clearInterval(this.flushTimer);\n this.flushTimer = null;\n }\n if (this.heartbeatTimer !== null) {\n clearInterval(this.heartbeatTimer);\n this.heartbeatTimer = null;\n }\n\n if (typeof window !== \"undefined\") {\n window.removeEventListener(\n \"visibilitychange\",\n this.handleVisibilityChange,\n );\n window.removeEventListener(\"pagehide\", this.handlePageHide);\n }\n\n this.navigation?.destroy();\n this.time?.destroy();\n this.heatmap?.destroy();\n this.logCapture?.destroy();\n\n if (typeof window !== \"undefined\") {\n window.removeEventListener(\"tracker:navigate\", this.handleNavigate);\n }\n\n // Best-effort flush of any remaining queued events.\n if (this.queue.length > 0 && this.cfg.endpoint) {\n this.flushBeacon();\n }\n\n this.initialized = false;\n }\n\n private handleVisibilityChange = (): void => {\n if (document.visibilityState === \"hidden\") {\n if (this.queue.length > 0) this.flushBeacon();\n this.sendDeactivate();\n }\n };\n\n private handlePageHide = (): void => {\n if (this.queue.length > 0) this.flushBeacon();\n this.sendDeactivate();\n };\n\n private handleNavigate = (_e: Event): void => {\n // Navigation hook kept for potential future use.\n };\n\n /**\n * Send a keep-alive heartbeat so the backend knows this session is still\n * active. Called every 30 s while the tab is visible.\n */\n private async sendHeartbeat(): Promise<void> {\n if (!this.cfg.endpoint) return;\n const url = `${this.cfg.endpoint}/heartbeat`;\n const authHeaders: Record<string, string> = this.cfg.secretKey\n ? { Authorization: `Bearer ${this.cfg.secretKey}` }\n : {};\n const body = JSON.stringify({\n sessionId: this.session.id,\n visitorId: this.session.visitorId,\n path: typeof window !== \"undefined\" ? window.location.pathname : \"/\",\n active: true,\n ...(this.cfg.appId ? { appId: this.cfg.appId } : {}),\n });\n try {\n await fetch(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\", ...authHeaders },\n body,\n keepalive: true,\n });\n } catch {\n // Silent — heartbeat failure should never surface to the user.\n }\n }\n\n /**\n * Send a synchronous beacon to mark this session as inactive.\n * Called on page unload / tab hidden so the dashboard reflects real-time.\n */\n private sendDeactivate(): void {\n if (!this.cfg.endpoint) return;\n const url = `${this.cfg.endpoint}/heartbeat`;\n const authHeaders: Record<string, string> = this.cfg.secretKey\n ? { Authorization: `Bearer ${this.cfg.secretKey}` }\n : {};\n const body = JSON.stringify({\n sessionId: this.session.id,\n path: typeof window !== \"undefined\" ? window.location.pathname : \"/\",\n active: false,\n ...(this.cfg.appId ? { appId: this.cfg.appId } : {}),\n });\n // sendBeacon fires even if the page is being unloaded.\n if (typeof navigator !== \"undefined\" && navigator.sendBeacon) {\n navigator.sendBeacon(url, new Blob([body], { type: \"application/json\" }));\n } else {\n // Fallback for environments without sendBeacon.\n void fetch(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\", ...authHeaders },\n body,\n keepalive: true,\n }).catch(() => undefined);\n }\n }\n\n // ─── Event pipeline ─────────────────────────────────────────────────────────\n\n /** Emit a tracker event. Also used internally by the plugins. */\n emit(event: TrackerEvent): void {\n // 1 – accumulate into session data\n switch (event.type) {\n case \"pageview\":\n this.session.pageViews.push(event.data);\n break;\n\n case \"timespent\": {\n const prev = this.session.timeSpent[event.data.path] ?? 0;\n this.session.timeSpent[event.data.path] = prev + event.data.duration;\n break;\n }\n\n case \"heatmap\": {\n const key = event.data.path;\n if (!this.session.heatmap[key]) this.session.heatmap[key] = [];\n const pts = this.session.heatmap[key];\n if (pts.length < this.cfg.maxHeatmapPoints) pts.push(event.data);\n break;\n }\n\n // rageclik and uturn are forwarded to the backend only; no local state needed.\n case \"rageclik\":\n case \"uturn\":\n break;\n }\n\n // 2 – notify subscribers (used internally by React hooks)\n this.subscribers.forEach((fn) => fn(event));\n\n // 3 – user callback\n this.cfg.onEvent?.(event);\n\n // 4 – enqueue for batched remote sending\n if (this.cfg.endpoint) {\n this.queue.push(event);\n // Auto-flush once the batch size threshold is reached.\n if (this.queue.length >= this.cfg.batchSize) {\n void this.flushQueue();\n }\n }\n }\n\n /**\n * Subscribe to every emitted event. Returns an unsubscribe function.\n *\n * ```ts\n * const unsub = tracker.subscribe(event => console.log(event));\n * // later…\n * unsub();\n * ```\n */\n subscribe(fn: SubscriberFn): () => void {\n this.subscribers.add(fn);\n return () => this.subscribers.delete(fn);\n }\n\n // ─── Manual tracking helpers ────────────────────────────────────────────────\n\n /**\n * Manually record a page view and dispatch `tracker:navigate`.\n * Required for Next.js App Router — call it inside a `useEffect` that\n * depends on `usePathname()`:\n *\n * ```tsx\n * const pathname = usePathname();\n * useEffect(() => { tracker.trackPageView(pathname); }, [pathname]);\n * ```\n */\n trackPageView(path?: string): void {\n const resolvedPath =\n path ??\n (typeof window !== \"undefined\"\n ? window.location.pathname + window.location.search\n : \"/\");\n\n this.emit({\n type: \"pageview\",\n data: {\n path: resolvedPath,\n title: typeof document !== \"undefined\" ? document.title : \"\",\n timestamp: Date.now(),\n sessionId: this.session.id,\n referrer:\n typeof document !== \"undefined\"\n ? document.referrer || undefined\n : undefined,\n },\n });\n\n if (typeof window !== \"undefined\") {\n window.dispatchEvent(\n new CustomEvent(\"tracker:navigate\", {\n detail: { path: resolvedPath, title: document.title },\n }),\n );\n }\n }\n\n // ─── Identity & Feature Flags ───────────────────────────────────────────────\n\n /**\n * Identify the current user with a set of properties.\n * Properties are merged with any previously set ones and used for feature\n * flag targeting (e.g. `email`, `plan`, `country`).\n * Automatically triggers a flag re-evaluation.\n *\n * ```ts\n * tracker.identify({ email: 'user@example.com', plan: 'pro' });\n * ```\n */\n identify(properties: Record<string, string>): void {\n this.userProperties = { ...this.userProperties, ...properties };\n void this.fetchFlags();\n }\n\n /**\n * Returns the latest evaluated feature flags as `{ [key]: boolean }`.\n * Flags are fetched on `init()` and after every `identify()` call.\n */\n getFlags(): Record<string, boolean> {\n return { ...this.flags };\n }\n\n /**\n * Returns `true` if the given flag is currently enabled for this user.\n * Returns `false` for unknown flags or before flags have loaded.\n */\n isFeatureEnabled(flagKey: string): boolean {\n return this.flags[flagKey] === true;\n }\n\n /**\n * Subscribe to flag changes. Called whenever flags are re-evaluated.\n * Returns an unsubscribe function.\n *\n * ```ts\n * const unsub = tracker.onFlagsChange(flags => console.log(flags));\n * ```\n */\n onFlagsChange(fn: FlagSubscriberFn): () => void {\n this.flagSubscribers.add(fn);\n return () => this.flagSubscribers.delete(fn);\n }\n\n /** Fetch (or re-fetch) flags from the backend evaluate endpoint. */\n async fetchFlags(): Promise<void> {\n if (!this.cfg.endpoint || !this.cfg.secretKey) return;\n const url =\n this.cfg.endpoint.replace(/\\/events$/, \"\") + \"/feature-flags/evaluate\";\n try {\n const res = await fetch(url, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${this.cfg.secretKey}`,\n },\n body: JSON.stringify({ userProperties: this.userProperties }),\n });\n if (!res.ok) return;\n const data = (await res.json()) as Record<string, boolean>;\n this.flags = data;\n this.flagSubscribers.forEach((fn) => fn({ ...this.flags }));\n } catch {\n // Silent — flag fetch failure must never surface to the user.\n }\n }\n\n // ─── Data accessors ─────────────────────────────────────────────────────────\n\n /** A read-only snapshot of the current session. */\n getSession(): Readonly<SessionData> {\n return this.session;\n }\n\n /** All page views recorded so far. */\n getPageViews(): PageView[] {\n return [...this.session.pageViews];\n }\n\n /** Cumulative milliseconds spent per path. */\n getTimeSpent(): Record<string, number> {\n return { ...this.session.timeSpent };\n }\n\n /** Heatmap points for a specific path. */\n getHeatmapData(path: string): HeatmapPoint[];\n /** Heatmap points for all tracked paths. */\n getHeatmapData(): Record<string, HeatmapPoint[]>;\n getHeatmapData(\n path?: string,\n ): HeatmapPoint[] | Record<string, HeatmapPoint[]> {\n if (path !== undefined) {\n return [...(this.session.heatmap[path] ?? [])];\n }\n return Object.entries(this.session.heatmap).reduce<\n Record<string, HeatmapPoint[]>\n >((acc, [k, v]) => {\n acc[k] = [...v];\n return acc;\n }, {});\n }\n\n // ─── Network ────────────────────────────────────────────────────────────────\n\n /**\n * Immediately drain the event queue and POST pending events to `/batch`.\n * Fire-and-forget; errors are intentionally swallowed like other analytics calls.\n */\n flush(): void {\n void this.flushQueue();\n }\n\n /** Drain the queue and POST all pending events to the batch endpoint. */\n private async flushQueue(): Promise<void> {\n if (this.queue.length === 0) return;\n // Splice atomically so new events emitted during the async request don't\n // get lost — they stay in the queue for the next flush.\n const batch = this.queue.splice(0);\n await this.sendBatch(batch);\n }\n\n /**\n * Synchronous best-effort flush via `navigator.sendBeacon`.\n * Used on `pagehide` / `visibilitychange:hidden` where async fetch may be\n * cancelled by the browser before it completes.\n */\n private flushBeacon(): void {\n if (this.queue.length === 0) return;\n const batch = this.queue.splice(0);\n const url = `${this.cfg.endpoint!}/batch`;\n const blob = new Blob([this.buildBatchBody(batch)], {\n type: \"application/json\",\n });\n if (typeof navigator !== \"undefined\" && navigator.sendBeacon) {\n navigator.sendBeacon(url, blob);\n } else {\n // Fallback: fire-and-forget fetch (best effort on platforms without sendBeacon)\n void this.sendBatch(batch);\n }\n }\n\n private buildBatchBody(events: TrackerEvent[]): string {\n return JSON.stringify({\n ...(this.cfg.appId ? { appId: this.cfg.appId } : {}),\n visitorId: this.session.visitorId,\n events: events.map((e) => ({\n sessionId: this.session.id,\n type: e.type,\n data: e.data,\n })),\n });\n }\n\n private async sendBatch(events: TrackerEvent[]): Promise<void> {\n const url = `${this.cfg.endpoint!}/batch`;\n const authHeaders: Record<string, string> = this.cfg.secretKey\n ? { Authorization: `Bearer ${this.cfg.secretKey}` }\n : {};\n try {\n await fetch(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\", ...authHeaders },\n body: this.buildBatchBody(events),\n keepalive: true,\n });\n } catch {\n // Intentionally silent — analytics must never surface errors to users.\n }\n }\n}\n"]}
1
+ {"version":3,"sources":["../src/utils/session.ts","../src/core/navigation.ts","../src/core/time.ts","../src/utils/throttle.ts","../src/core/heatmap.ts","../src/core/logger.ts","../src/tracker.ts"],"names":["VISITOR_STORAGE_KEY","generateSessionId","c","r","getOrCreateVisitorId","existing","id","e","SS_LAST_PV_PATH","SS_LAST_PV_ENTRY_MS","safeSessionStorageGet","key","safeSessionStorageSet","value","persistLastPageView","path","pageEntryMs","getNavigationEntryType","nav","legacy","_NavigationPlugin","emit","sessionId","newPath","origPush","state","title","url","origReplace","lastPath","lastMsRaw","lastEntryMs","timeOnPage","fromPath","toPath","timeOnPageMs","NavigationPlugin","TimePlugin","duration","throttle","fn","delay","lastCall","args","now","_HeatmapPlugin","sampleRate","maxPoints","allowedPaths","pageWidth","pageHeight","absY","target","touch","vw","scrollX","scrollY","absX","_a","point","_b","_c","_d","el","x","y","nearby","HeatmapPlugin","LogCapture","options","u","parts","first","stack","msg","src","line","col","err","reason","message","level","extra","a","entry","body","DEFAULT_ENDPOINT","DEFAULTS","UserTracker","config","_e","authHeaders","event","prev","pts","resolvedPath","properties","flagKey","res","data","acc","k","v","batch","blob","events"],"mappings":"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","file":"index.js","sourcesContent":["const SESSION_STORAGE_KEY = \"__ut_sid__\";\nconst VISITOR_STORAGE_KEY = \"__ut_vid__\";\n\n/** Generate a RFC-4122 v4 UUID using the native crypto API with a fallback. */\nexport function generateSessionId(): string {\n if (\n typeof crypto !== \"undefined\" &&\n typeof crypto.randomUUID === \"function\"\n ) {\n return crypto.randomUUID();\n }\n // Math.random fallback (not cryptographically secure, but sufficient for analytics)\n return \"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx\".replace(/[xy]/g, (c) => {\n const r = (Math.random() * 16) | 0;\n const v = c === \"x\" ? r : (r & 0x3) | 0x8;\n return v.toString(16);\n });\n}\n\n/**\n * Retrieve the session ID from sessionStorage, or create and persist a new one.\n * Falls back to an in-memory ID when sessionStorage is unavailable (e.g. SSR).\n */\nexport function getOrCreateSessionId(): string {\n if (typeof sessionStorage === \"undefined\") return generateSessionId();\n try {\n const existing = sessionStorage.getItem(SESSION_STORAGE_KEY);\n if (existing) return existing;\n const id = generateSessionId();\n sessionStorage.setItem(SESSION_STORAGE_KEY, id);\n return id;\n } catch {\n return generateSessionId();\n }\n}\n\n/**\n * Retrieve the visitor ID from localStorage (persists across sessions/tabs),\n * or create and store a new one. Returns a temporary in-memory ID when\n * localStorage is unavailable.\n */\nexport function getOrCreateVisitorId(): string {\n if (typeof localStorage === \"undefined\") return generateSessionId();\n try {\n const existing = localStorage.getItem(VISITOR_STORAGE_KEY);\n if (existing) return existing;\n const id = generateSessionId();\n localStorage.setItem(VISITOR_STORAGE_KEY, id);\n return id;\n } catch {\n return generateSessionId();\n }\n}\n","import type { TrackerEvent } from \"../types\";\n\ntype EmitFn = (event: TrackerEvent) => void;\n\ninterface NavigationPluginOptions {\n emit: EmitFn;\n sessionId: string;\n}\n\n/** Persisted across full page loads (same tab, same origin) for MPA U-turn detection. */\nconst SS_LAST_PV_PATH = \"__ut_last_pv_path__\";\nconst SS_LAST_PV_ENTRY_MS = \"__ut_last_pv_entry_ms__\";\n\nfunction safeSessionStorageGet(key: string): string | null {\n if (typeof sessionStorage === \"undefined\") return null;\n try {\n return sessionStorage.getItem(key);\n } catch {\n return null;\n }\n}\n\nfunction safeSessionStorageSet(key: string, value: string): void {\n if (typeof sessionStorage === \"undefined\") return;\n try {\n sessionStorage.setItem(key, value);\n } catch {\n /* quota / private mode */\n }\n}\n\nfunction persistLastPageView(path: string, pageEntryMs: number): void {\n safeSessionStorageSet(SS_LAST_PV_PATH, path);\n safeSessionStorageSet(SS_LAST_PV_ENTRY_MS, String(pageEntryMs));\n}\n\n/**\n * `navigate` | `reload` | `back_forward` | `prerender`, or legacy back/forward.\n */\nfunction getNavigationEntryType(): string | undefined {\n if (typeof performance === \"undefined\") return undefined;\n const list = performance.getEntriesByType(\n \"navigation\",\n ) as PerformanceNavigationTiming[];\n const nav = list[0];\n if (nav?.type) return nav.type;\n const legacy = (\n performance as unknown as { navigation?: { type?: number } }\n ).navigation;\n if (legacy?.type === 2) return \"back_forward\";\n return undefined;\n}\n\n/**\n * Tracks SPA route changes by monkey-patching history.pushState /\n * history.replaceState and listening to the popstate event.\n *\n * For every navigation it:\n * 1. Emits a `pageview` event.\n * 2. Dispatches the custom DOM event `tracker:navigate` so that other\n * plugins (TimePlugin, HeatmapPlugin) can react without having to\n * duplicate the pushState patching.\n * 3. Detects U-turns: if the user leaves a page in ≤5 s, a `uturn` event\n * is emitted with the time-on-page and destination path.\n *\n * **Multi-page sites (WordPress, etc.):** full reloads do not call\n * `pushState`. On browser Back/Forward, a new document loads; we detect\n * `PerformanceNavigationTiming.type === \"back_forward\"` and compare the\n * previous URL + entry time stored in `sessionStorage` to emit the same\n * `uturn` event as SPAs.\n *\n * Next.js App Router note:\n * The App Router manages navigation internally; use `usePageView(pathname)`\n * from `user-tracker/react` together with `usePathname()` instead.\n */\nexport class NavigationPlugin {\n private readonly emit: EmitFn;\n private readonly sessionId: string;\n private previousPath = \"\";\n private originalPushState: typeof history.pushState | null = null;\n private originalReplaceState: typeof history.replaceState | null = null;\n private pageEntryTime = 0;\n private static readonly UTURN_THRESHOLD_MS = 5_000;\n\n constructor({ emit, sessionId }: NavigationPluginOptions) {\n this.emit = emit;\n this.sessionId = sessionId;\n }\n\n init(): void {\n const newPath = window.location.pathname + window.location.search;\n\n // MPA: user returned via history after a full page load (e.g. WordPress).\n if (getNavigationEntryType() === \"back_forward\") {\n this.tryEmitMpaHistoryTraversalUturn(newPath);\n }\n\n this.recordPageView(newPath);\n\n window.addEventListener(\"popstate\", this.handlePopState);\n\n // Patch pushState\n this.originalPushState = history.pushState.bind(history);\n const origPush = this.originalPushState;\n history.pushState = (state, title, url): void => {\n origPush(state, title, url);\n this.handleNavigation();\n };\n\n // Patch replaceState\n this.originalReplaceState = history.replaceState.bind(history);\n const origReplace = this.originalReplaceState;\n history.replaceState = (state, title, url): void => {\n origReplace(state, title, url);\n this.handleNavigation();\n };\n }\n\n destroy(): void {\n window.removeEventListener(\"popstate\", this.handlePopState);\n if (this.originalPushState) history.pushState = this.originalPushState;\n if (this.originalReplaceState)\n history.replaceState = this.originalReplaceState;\n }\n\n // Arrow property → always bound to `this`, safe to use as event listener.\n private handlePopState = (): void => {\n this.handleNavigation();\n };\n\n /**\n * After a full navigation away from `lastPath`, sessionStorage still holds\n * that page's path and entry time. If this load is a history traversal to a\n * *different* URL and dwell time on the previous page was ≤ threshold, emit\n * `uturn` (same semantics as SPA `handleNavigation`).\n */\n private tryEmitMpaHistoryTraversalUturn(newPath: string): void {\n const lastPath = safeSessionStorageGet(SS_LAST_PV_PATH);\n const lastMsRaw = safeSessionStorageGet(SS_LAST_PV_ENTRY_MS);\n const lastEntryMs = lastMsRaw ? Number(lastMsRaw) : 0;\n if (!lastPath || !Number.isFinite(lastEntryMs) || lastEntryMs <= 0) return;\n if (lastPath === newPath) return;\n\n const timeOnPage = Date.now() - lastEntryMs;\n if (timeOnPage < 0 || timeOnPage > NavigationPlugin.UTURN_THRESHOLD_MS)\n return;\n\n this.emitUturn(lastPath, newPath, timeOnPage);\n }\n\n private handleNavigation(): void {\n const newPath = window.location.pathname + window.location.search;\n if (newPath === this.previousPath) return; // hash-only or duplicate call\n\n // U-turn detection: user left the previous page very quickly.\n if (this.previousPath && this.pageEntryTime > 0) {\n const timeOnPage = Date.now() - this.pageEntryTime;\n if (timeOnPage <= NavigationPlugin.UTURN_THRESHOLD_MS) {\n this.emitUturn(this.previousPath, newPath, timeOnPage);\n }\n }\n\n this.recordPageView(newPath);\n }\n\n private emitUturn(\n fromPath: string,\n toPath: string,\n timeOnPageMs: number,\n ): void {\n this.emit({\n type: \"uturn\",\n data: {\n fromPath,\n toPath,\n timeOnPageMs,\n timestamp: Date.now(),\n sessionId: this.sessionId,\n },\n });\n }\n\n private recordPageView(path: string): void {\n this.previousPath = path;\n this.pageEntryTime = Date.now();\n\n persistLastPageView(path, this.pageEntryTime);\n\n this.emit({\n type: \"pageview\",\n data: {\n path,\n title: document.title,\n timestamp: Date.now(),\n sessionId: this.sessionId,\n referrer: document.referrer || undefined,\n },\n });\n\n // Notify other plugins via a custom DOM event (synchronous dispatch).\n window.dispatchEvent(\n new CustomEvent(\"tracker:navigate\", {\n detail: { path, title: document.title },\n }),\n );\n }\n}\n","import type { TrackerEvent } from \"../types\";\n\ntype EmitFn = (event: TrackerEvent) => void;\n\ninterface TimePluginOptions {\n emit: EmitFn;\n sessionId: string;\n}\n\n/**\n * Tracks the time a user spends on each page.\n *\n * - Starts a timer when the page becomes active (init / tab focus).\n * - Stops and emits a `timespent` event when:\n * • The user navigates away (tracker:navigate)\n * • The tab is hidden (visibilitychange)\n * • The page is unloading (beforeunload / pagehide)\n * - Resumes timing when the tab becomes visible again.\n */\nexport class TimePlugin {\n private readonly emit: EmitFn;\n private readonly sessionId: string;\n private currentPath = \"\";\n private startTime = 0;\n private tracking = false;\n\n constructor({ emit, sessionId }: TimePluginOptions) {\n this.emit = emit;\n this.sessionId = sessionId;\n }\n\n init(): void {\n this.currentPath = window.location.pathname + window.location.search;\n this.startTracking();\n\n window.addEventListener(\"tracker:navigate\", this.handleNavigate);\n document.addEventListener(\"visibilitychange\", this.handleVisibilityChange);\n window.addEventListener(\"beforeunload\", this.handleUnload);\n window.addEventListener(\"pagehide\", this.handleUnload);\n }\n\n destroy(): void {\n this.stopTracking();\n window.removeEventListener(\"tracker:navigate\", this.handleNavigate);\n document.removeEventListener(\n \"visibilitychange\",\n this.handleVisibilityChange,\n );\n window.removeEventListener(\"beforeunload\", this.handleUnload);\n window.removeEventListener(\"pagehide\", this.handleUnload);\n }\n\n private startTracking(): void {\n this.startTime = Date.now();\n this.tracking = true;\n }\n\n private stopTracking(): void {\n if (!this.tracking || !this.currentPath) return;\n const duration = Date.now() - this.startTime;\n if (duration < 100) {\n this.tracking = false;\n return; // Ignore sub-100 ms blips (e.g. rapid navigations).\n }\n this.emit({\n type: \"timespent\",\n data: {\n path: this.currentPath,\n duration,\n sessionId: this.sessionId,\n timestamp: Date.now(),\n },\n });\n this.tracking = false;\n }\n\n private handleNavigate = (e: CustomEvent<{ path: string }>): void => {\n this.stopTracking();\n this.currentPath = e.detail.path;\n this.startTracking();\n };\n\n private handleVisibilityChange = (): void => {\n if (document.hidden) {\n this.stopTracking();\n } else {\n this.startTracking();\n }\n };\n\n private handleUnload = (): void => {\n this.stopTracking();\n };\n}\n","/**\n * Returns a function that invokes `fn` at most once every `delay` ms.\n * The first call in a new window is executed immediately.\n */\nexport function throttle<Args extends unknown[]>(\n fn: (...args: Args) => void,\n delay: number,\n): (...args: Args) => void {\n let lastCall = 0;\n return (...args: Args): void => {\n const now = Date.now();\n if (now - lastCall >= delay) {\n lastCall = now;\n fn(...args);\n }\n };\n}\n","import type { TrackerEvent, HeatmapPoint } from \"../types\";\nimport { throttle } from \"../utils/throttle\";\n\ntype EmitFn = (event: TrackerEvent) => void;\n\ninterface HeatmapPluginOptions {\n emit: EmitFn;\n sessionId: string;\n /** Fraction of mousemove / scroll events to sample (0–1). Default: 0.3 */\n sampleRate?: number;\n /** Maximum points stored per page before recording stops. Default: 2000 */\n maxPoints?: number;\n /**\n * If provided, heatmap data will only be collected for paths in this list.\n * An empty array or undefined means all pages are tracked.\n */\n allowedPaths?: string[];\n}\n\n/**\n * Collects mouse-move, click, and scroll positions for heatmap analysis.\n *\n * Also detects rage clicks (≥3 clicks within 1 s in a 50 px radius) and emits\n * a `rageclik` event so the backend can surface problematic UI hotspots.\n *\n * Click events include an optional `target` field containing a human-readable\n * label for the clicked element (aria-label › id › button/link text).\n */\nexport class HeatmapPlugin {\n private readonly emit: EmitFn;\n private readonly sessionId: string;\n private readonly sampleRate: number;\n private readonly maxPoints: number;\n private readonly allowedPaths: Set<string> | null;\n private currentPath = \"\";\n private pointCounts: Record<string, number> = {};\n\n // Rage-click detection state\n private recentClicks: { x: number; y: number; t: number }[] = [];\n private static readonly RAGE_THRESHOLD = 3;\n private static readonly RAGE_WINDOW_MS = 1_000;\n private static readonly RAGE_RADIUS_PX = 50;\n\n private readonly throttledMouseMove: (e: MouseEvent) => void;\n private readonly throttledScroll: () => void;\n\n constructor({\n emit,\n sessionId,\n sampleRate = 0.3,\n maxPoints = 2000,\n allowedPaths,\n }: HeatmapPluginOptions) {\n this.emit = emit;\n this.sessionId = sessionId;\n this.sampleRate = sampleRate;\n this.maxPoints = maxPoints;\n this.allowedPaths =\n allowedPaths && allowedPaths.length > 0 ? new Set(allowedPaths) : null;\n\n this.throttledMouseMove = throttle(this.handleMouseMove, 50);\n this.throttledScroll = throttle(this.handleScroll, 100);\n }\n\n init(): void {\n this.currentPath = window.location.pathname + window.location.search;\n\n document.addEventListener(\"mousemove\", this.throttledMouseMove);\n document.addEventListener(\"click\", this.handleClick);\n document.addEventListener(\"touchend\", this.handleTouchEnd, {\n passive: true,\n });\n window.addEventListener(\"scroll\", this.throttledScroll, { passive: true });\n window.addEventListener(\"tracker:navigate\", this.handleNavigate);\n }\n\n destroy(): void {\n document.removeEventListener(\"mousemove\", this.throttledMouseMove);\n document.removeEventListener(\"click\", this.handleClick);\n document.removeEventListener(\"touchend\", this.handleTouchEnd);\n window.removeEventListener(\"scroll\", this.throttledScroll);\n window.removeEventListener(\"tracker:navigate\", this.handleNavigate);\n }\n\n private canRecord(): boolean {\n if (\n this.allowedPaths !== null &&\n !this.allowedPaths.has(this.currentPath)\n ) {\n return false;\n }\n return (this.pointCounts[this.currentPath] ?? 0) < this.maxPoints;\n }\n\n private recordPoint(\n point: Omit<HeatmapPoint, \"path\" | \"timestamp\" | \"sessionId\">,\n ): void {\n if (!this.canRecord()) return;\n this.pointCounts[this.currentPath] =\n (this.pointCounts[this.currentPath] ?? 0) + 1;\n this.emit({\n type: \"heatmap\",\n data: {\n ...point,\n path: this.currentPath,\n timestamp: Date.now(),\n },\n });\n }\n\n /** Extract a human-readable label for the clicked element. */\n private getClickTarget(e: MouseEvent): string | undefined {\n const el = e.target as HTMLElement | null;\n if (!el) return undefined;\n const label =\n el.getAttribute(\"aria-label\") ||\n el.closest(\"[aria-label]\")?.getAttribute(\"aria-label\") ||\n el.getAttribute(\"data-track-label\") ||\n el.getAttribute(\"id\") ||\n (el instanceof HTMLButtonElement || el instanceof HTMLAnchorElement\n ? el.innerText?.trim().slice(0, 60)\n : el.closest(\"button, a\")?.textContent?.trim().slice(0, 60));\n return label || undefined;\n }\n\n /** Detect rage-click bursts and emit a `rageclik` event when found. */\n private checkRageClick(x: number, y: number, path: string): void {\n const now = Date.now();\n this.recentClicks = this.recentClicks.filter(\n (c) => now - c.t < HeatmapPlugin.RAGE_WINDOW_MS,\n );\n this.recentClicks.push({ x, y, t: now });\n\n const nearby = this.recentClicks.filter(\n (c) => Math.hypot(c.x - x, c.y - y) <= HeatmapPlugin.RAGE_RADIUS_PX,\n );\n\n if (nearby.length >= HeatmapPlugin.RAGE_THRESHOLD) {\n this.emit({\n type: \"rageclik\",\n data: {\n path,\n x,\n y,\n count: nearby.length,\n timestamp: now,\n sessionId: this.sessionId,\n },\n });\n // Reset to avoid continuously re-triggering in the same burst.\n this.recentClicks = [];\n }\n }\n\n private handleMouseMove = (e: MouseEvent): void => {\n if (Math.random() > this.sampleRate) return;\n const pageWidth = document.documentElement.scrollWidth;\n const pageHeight = document.documentElement.scrollHeight;\n const absY = e.clientY + window.scrollY;\n this.recordPoint({\n x: e.clientX,\n y: absY,\n xPct: pageWidth > 0 ? (e.clientX / pageWidth) * 100 : 0,\n yPct: pageHeight > 0 ? (absY / pageHeight) * 100 : 0,\n pw: pageWidth,\n ph: pageHeight,\n type: \"move\",\n });\n };\n\n private handleClick = (e: MouseEvent): void => {\n // Skip if a touchend already recorded this tap (within 500 ms)\n if (Date.now() - this.lastTouchTime < 500) return;\n const pageWidth = document.documentElement.scrollWidth;\n const pageHeight = document.documentElement.scrollHeight;\n const absY = e.clientY + window.scrollY;\n const target = this.getClickTarget(e);\n this.recordPoint({\n x: e.clientX,\n y: absY,\n xPct: pageWidth > 0 ? (e.clientX / pageWidth) * 100 : 0,\n yPct: pageHeight > 0 ? (absY / pageHeight) * 100 : 0,\n pw: pageWidth,\n ph: pageHeight,\n type: \"click\",\n ...(target ? { target } : {}),\n });\n this.checkRageClick(e.clientX, absY, this.currentPath);\n };\n\n /**\n * Records taps on touch devices.\n * Mobile browsers do fire a synthetic `click` after `touchend`, but some\n * frameworks call `preventDefault()` on touch events which suppresses it.\n * Listening to `touchend` directly ensures taps are always captured.\n * We mark the point with a short-lived flag so the subsequent synthetic\n * `click` event (if it fires) is deduplicated.\n */\n private lastTouchTime = 0;\n\n private handleTouchEnd = (e: TouchEvent): void => {\n const touch = e.changedTouches[0];\n if (!touch) return;\n const pageWidth = document.documentElement.scrollWidth;\n const pageHeight = document.documentElement.scrollHeight;\n const absY = touch.clientY + window.scrollY;\n this.lastTouchTime = Date.now();\n this.recordPoint({\n x: touch.clientX,\n y: absY,\n xPct: pageWidth > 0 ? (touch.clientX / pageWidth) * 100 : 0,\n yPct: pageHeight > 0 ? (absY / pageHeight) * 100 : 0,\n pw: pageWidth,\n ph: pageHeight,\n type: \"click\",\n });\n this.checkRageClick(touch.clientX, absY, this.currentPath);\n };\n\n private handleScroll = (): void => {\n if (Math.random() > this.sampleRate) return;\n const pageWidth = document.documentElement.scrollWidth;\n const pageHeight = document.documentElement.scrollHeight;\n const vw = window.innerWidth;\n const scrollX = window.scrollX;\n const scrollY = window.scrollY;\n const absX = scrollX + vw / 2;\n this.recordPoint({\n x: vw / 2,\n y: scrollY,\n xPct: pageWidth > 0 ? (absX / pageWidth) * 100 : 50,\n yPct: pageHeight > 0 ? (scrollY / pageHeight) * 100 : 0,\n pw: pageWidth,\n ph: pageHeight,\n type: \"scroll\",\n });\n };\n\n private handleNavigate = (e: CustomEvent<{ path: string }>): void => {\n this.currentPath = e.detail.path;\n };\n}\n","export type LogLevel = \"info\" | \"warn\" | \"error\";\n\nexport interface LogEntry {\n sessionId?: string;\n appId?: string;\n level: LogLevel;\n message: string;\n url?: string;\n stack?: string;\n meta?: Record<string, unknown>;\n timestamp: number;\n}\n\ntype ConsoleFn = (...args: unknown[]) => void;\n\n/**\n * Automatically captures console.info/warn/error output and unhandled errors,\n * then ships them to the backend `/logs/ingest` endpoint.\n *\n * All console methods are restored exactly in `destroy()`.\n */\nexport class LogCapture {\n private readonly endpoint: string;\n private readonly sessionId: string;\n private readonly appId?: string;\n private readonly authHeaders: Record<string, string>;\n\n // Original console methods preserved so we can restore them.\n private origInfo!: ConsoleFn;\n private origWarn!: ConsoleFn;\n private origError!: ConsoleFn;\n\n private prevOnError: OnErrorEventHandler = null;\n private prevOnUnhandledRejection:\n | ((e: PromiseRejectionEvent) => void)\n | null = null;\n\n private initialized = false;\n\n constructor(options: {\n endpoint: string;\n sessionId: string;\n secretKey?: string;\n appId?: string;\n }) {\n // Derive the API base URL by stripping everything from the last path\n // segment that isn't a versioning prefix. The tracker config `endpoint`\n // is the *events* URL (e.g. http://host/api/events), but logs live at\n // http://host/api/logs/ingest, so we walk up until we reach the common\n // base (i.e. remove the final segment).\n try {\n const u = new URL(options.endpoint);\n // Remove the last non-empty path segment (e.g. \"/api/events\" → \"/api\")\n const parts = u.pathname.replace(/\\/$/, \"\").split(\"/\");\n parts.pop();\n u.pathname = parts.join(\"/\") || \"/\";\n this.endpoint = u.toString().replace(/\\/$/, \"\");\n } catch {\n this.endpoint = options.endpoint;\n }\n this.sessionId = options.sessionId;\n this.appId = options.appId;\n this.authHeaders = options.secretKey\n ? { Authorization: `Bearer ${options.secretKey}` }\n : {};\n }\n\n init(): void {\n if (typeof window === \"undefined\" || this.initialized) return;\n\n this.origInfo = console.info.bind(console);\n this.origWarn = console.warn.bind(console);\n this.origError = console.error.bind(console);\n\n console.info = (...args: unknown[]) => {\n this.origInfo(...args);\n this.send(\"info\", this.format(args));\n };\n\n console.warn = (...args: unknown[]) => {\n this.origWarn(...args);\n this.send(\"warn\", this.format(args));\n };\n\n console.error = (...args: unknown[]) => {\n this.origError(...args);\n const [first] = args;\n const stack = first instanceof Error ? first.stack : undefined;\n this.send(\"error\", this.format(args), { stack });\n };\n\n this.prevOnError = window.onerror;\n window.onerror = (msg, src, line, col, err) => {\n this.send(\"error\", String(msg), {\n stack: err?.stack,\n meta: { src, line, col },\n });\n if (typeof this.prevOnError === \"function\") {\n return this.prevOnError(msg, src, line, col, err);\n }\n return false;\n };\n\n this.prevOnUnhandledRejection = (e: PromiseRejectionEvent) => {\n const reason = e.reason;\n const message =\n reason instanceof Error\n ? reason.message\n : String(reason ?? \"Unhandled promise rejection\");\n this.send(\"error\", message, {\n stack: reason instanceof Error ? reason.stack : undefined,\n });\n };\n window.addEventListener(\n \"unhandledrejection\",\n this.prevOnUnhandledRejection,\n );\n\n this.initialized = true;\n }\n\n destroy(): void {\n if (!this.initialized) return;\n console.info = this.origInfo;\n console.warn = this.origWarn;\n console.error = this.origError;\n\n window.onerror = this.prevOnError;\n if (this.prevOnUnhandledRejection) {\n window.removeEventListener(\n \"unhandledrejection\",\n this.prevOnUnhandledRejection,\n );\n }\n this.initialized = false;\n }\n\n /** Manually capture a log entry (e.g. from try/catch). */\n capture(\n level: LogLevel,\n message: string,\n extra?: { stack?: string; meta?: Record<string, unknown> },\n ): void {\n this.send(level, message, extra);\n }\n\n private format(args: unknown[]): string {\n return args\n .map((a) => {\n if (a instanceof Error) return a.message;\n if (typeof a === \"object\") {\n try {\n return JSON.stringify(a);\n } catch {\n return String(a);\n }\n }\n return String(a);\n })\n .join(\" \");\n }\n\n private send(\n level: LogLevel,\n message: string,\n extra?: { stack?: string; meta?: Record<string, unknown> },\n ): void {\n const entry: LogEntry = {\n sessionId: this.sessionId,\n ...(this.appId ? { appId: this.appId } : {}),\n level,\n message,\n url: typeof window !== \"undefined\" ? window.location.href : undefined,\n stack: extra?.stack,\n meta: extra?.meta,\n timestamp: Date.now(),\n };\n const url = `${this.endpoint}/logs/ingest`;\n const body = JSON.stringify(entry);\n\n // Use fetch with keepalive so the request survives page navigation.\n // Errors are logged to the (original, unpatched) console so they are\n // visible in DevTools without creating an infinite log loop.\n void fetch(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\", ...this.authHeaders },\n body,\n keepalive: true,\n }).catch((err: unknown) => {\n // Use the original (pre-patch) error logger to avoid recursion.\n if (this.origError) {\n this.origError(\"[user-tracker] Failed to send log:\", err);\n }\n });\n }\n}\n","import type {\n TrackerConfig,\n TrackerEvent,\n SessionData,\n PageView,\n HeatmapPoint,\n} from \"./types\";\nimport { generateSessionId, getOrCreateVisitorId } from \"./utils/session\";\nimport { NavigationPlugin } from \"./core/navigation\";\nimport { TimePlugin } from \"./core/time\";\nimport { HeatmapPlugin } from \"./core/heatmap\";\nimport { LogCapture } from \"./core/logger\";\n\nexport const DEFAULT_ENDPOINT = \"https://api.alphana.ir/api/events\";\n\nconst DEFAULTS = {\n endpoint: DEFAULT_ENDPOINT,\n trackNavigation: true,\n trackTime: true,\n trackHeatmap: true,\n trackLogs: true,\n mouseSampleRate: 0.3,\n maxHeatmapPoints: 2000,\n batchSize: 20,\n flushInterval: 5_000,\n} as const;\n\ntype SubscriberFn = (event: TrackerEvent) => void;\ntype FlagSubscriberFn = (flags: Record<string, boolean>) => void;\n\n/**\n * Core tracker class. Framework-agnostic — works in any environment that has\n * a browser DOM (React, Next.js Pages Router, Vite, vanilla JS/TS, etc.).\n *\n * Usage:\n * ```ts\n * const tracker = new UserTracker({ endpoint: 'https://my-api.com/events' });\n * tracker.init(); // call once; safe to call in SSR (no-op server-side)\n * ```\n *\n * Destroy when done (e.g. component unmount):\n * ```ts\n * tracker.destroy();\n * ```\n */\ntype ResolvedConfig = Required<\n Pick<\n TrackerConfig,\n | \"endpoint\"\n | \"trackNavigation\"\n | \"trackTime\"\n | \"trackHeatmap\"\n | \"trackLogs\"\n | \"mouseSampleRate\"\n | \"maxHeatmapPoints\"\n | \"batchSize\"\n | \"flushInterval\"\n >\n> &\n TrackerConfig;\n\nexport class UserTracker {\n private readonly cfg: ResolvedConfig;\n private session: SessionData;\n private navigation?: NavigationPlugin;\n private time?: TimePlugin;\n private heatmap?: HeatmapPlugin;\n /** Public so consumers can call logCapture.capture() for manual log entries. */\n logCapture?: LogCapture;\n private initialized = false;\n private readonly subscribers = new Set<SubscriberFn>();\n\n /** In-memory queue of events waiting to be flushed. */\n private queue: TrackerEvent[] = [];\n private flushTimer: ReturnType<typeof setInterval> | null = null;\n private heartbeatTimer: ReturnType<typeof setInterval> | null = null;\n\n // ─── Feature flags ────────────────────────────────────────────────────────\n private userProperties: Record<string, string> = {};\n private flags: Record<string, boolean> = {};\n private readonly flagSubscribers = new Set<FlagSubscriberFn>();\n\n constructor(config: TrackerConfig = {}) {\n this.cfg = { ...DEFAULTS, ...config } as ResolvedConfig;\n\n // Validate endpoint URL up front so the error is thrown at construction\n // time rather than silently failing during a network request.\n try {\n new URL(this.cfg.endpoint);\n } catch {\n throw new Error(\n `[alpha-tracker] Invalid endpoint URL: \"${this.cfg.endpoint}\"`,\n );\n }\n\n this.session = {\n id: config.sessionId ?? generateSessionId(),\n visitorId: getOrCreateVisitorId(),\n startedAt: Date.now(),\n pageViews: [],\n timeSpent: {},\n heatmap: {},\n };\n }\n\n // ─── Lifecycle ──────────────────────────────────────────────────────────────\n\n /**\n * Attach event listeners and start tracking.\n * Safe to call during SSR — returns `this` immediately if `window` is\n * undefined so it can be chained: `const tracker = new UserTracker(cfg).init()`.\n */\n init(): this {\n if (typeof window === \"undefined\" || this.initialized) return this;\n\n const emit = this.emit.bind(this);\n const { id: sessionId } = this.session;\n\n if (this.cfg.trackNavigation) {\n this.navigation = new NavigationPlugin({ emit, sessionId });\n this.navigation.init();\n }\n\n if (this.cfg.trackTime) {\n this.time = new TimePlugin({ emit, sessionId });\n this.time.init();\n }\n\n if (this.cfg.trackHeatmap) {\n this.heatmap = new HeatmapPlugin({\n emit,\n sessionId,\n sampleRate: this.cfg.mouseSampleRate,\n maxPoints: this.cfg.maxHeatmapPoints,\n allowedPaths: this.cfg.heatmapPages,\n });\n this.heatmap.init();\n }\n\n if (this.cfg.endpoint) {\n // Flush on a regular interval — even if the batch threshold isn't hit.\n this.flushTimer = setInterval(() => {\n if (this.queue.length > 0) void this.flushQueue();\n }, this.cfg.flushInterval);\n\n // Visitor location is resolved server-side from the request IP using\n // an offline GeoIP database — no third-party requests from the SDK.\n\n // Flush remaining queue when the tab is hidden or the page is unloaded.\n window.addEventListener(\"visibilitychange\", this.handleVisibilityChange);\n window.addEventListener(\"pagehide\", this.handlePageHide);\n\n // Periodic keep-alive heartbeat every 30 s so the backend knows the\n // session is still active and doesn't expire it prematurely.\n this.heartbeatTimer = setInterval(() => {\n if (document.visibilityState !== \"hidden\") void this.sendHeartbeat();\n }, 30_000);\n\n // Auto-capture console logs and unhandled errors.\n if (this.cfg.trackLogs) {\n this.logCapture = new LogCapture({\n endpoint: this.cfg.endpoint,\n sessionId: this.session.id,\n secretKey: this.cfg.secretKey,\n appId: this.cfg.appId,\n });\n this.logCapture.init();\n }\n }\n\n this.initialized = true;\n\n // Fetch feature flags in the background on init.\n void this.fetchFlags();\n\n return this;\n }\n\n /** Remove all event listeners, flush remaining queue, and reset state. */\n destroy(): void {\n if (this.flushTimer !== null) {\n clearInterval(this.flushTimer);\n this.flushTimer = null;\n }\n if (this.heartbeatTimer !== null) {\n clearInterval(this.heartbeatTimer);\n this.heartbeatTimer = null;\n }\n\n if (typeof window !== \"undefined\") {\n window.removeEventListener(\n \"visibilitychange\",\n this.handleVisibilityChange,\n );\n window.removeEventListener(\"pagehide\", this.handlePageHide);\n }\n\n this.navigation?.destroy();\n this.time?.destroy();\n this.heatmap?.destroy();\n this.logCapture?.destroy();\n\n if (typeof window !== \"undefined\") {\n window.removeEventListener(\"tracker:navigate\", this.handleNavigate);\n }\n\n // Best-effort flush of any remaining queued events.\n if (this.queue.length > 0 && this.cfg.endpoint) {\n this.flushBeacon();\n }\n\n this.initialized = false;\n }\n\n private handleVisibilityChange = (): void => {\n if (document.visibilityState === \"hidden\") {\n if (this.queue.length > 0) this.flushBeacon();\n this.sendDeactivate();\n }\n };\n\n private handlePageHide = (): void => {\n if (this.queue.length > 0) this.flushBeacon();\n this.sendDeactivate();\n };\n\n private handleNavigate = (_e: Event): void => {\n // Navigation hook kept for potential future use.\n };\n\n /**\n * Send a keep-alive heartbeat so the backend knows this session is still\n * active. Called every 30 s while the tab is visible.\n */\n private async sendHeartbeat(): Promise<void> {\n if (!this.cfg.endpoint) return;\n const url = `${this.cfg.endpoint}/heartbeat`;\n const authHeaders: Record<string, string> = this.cfg.secretKey\n ? { Authorization: `Bearer ${this.cfg.secretKey}` }\n : {};\n const body = JSON.stringify({\n sessionId: this.session.id,\n visitorId: this.session.visitorId,\n path: typeof window !== \"undefined\" ? window.location.pathname : \"/\",\n active: true,\n ...(this.cfg.appId ? { appId: this.cfg.appId } : {}),\n });\n try {\n await fetch(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\", ...authHeaders },\n body,\n keepalive: true,\n });\n } catch {\n // Silent — heartbeat failure should never surface to the user.\n }\n }\n\n /**\n * Send a synchronous beacon to mark this session as inactive.\n * Called on page unload / tab hidden so the dashboard reflects real-time.\n */\n private sendDeactivate(): void {\n if (!this.cfg.endpoint) return;\n const url = `${this.cfg.endpoint}/heartbeat`;\n const authHeaders: Record<string, string> = this.cfg.secretKey\n ? { Authorization: `Bearer ${this.cfg.secretKey}` }\n : {};\n const body = JSON.stringify({\n sessionId: this.session.id,\n path: typeof window !== \"undefined\" ? window.location.pathname : \"/\",\n active: false,\n ...(this.cfg.appId ? { appId: this.cfg.appId } : {}),\n });\n // sendBeacon fires even if the page is being unloaded.\n if (typeof navigator !== \"undefined\" && navigator.sendBeacon) {\n navigator.sendBeacon(url, new Blob([body], { type: \"application/json\" }));\n } else {\n // Fallback for environments without sendBeacon.\n void fetch(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\", ...authHeaders },\n body,\n keepalive: true,\n }).catch(() => undefined);\n }\n }\n\n // ─── Event pipeline ─────────────────────────────────────────────────────────\n\n /** Emit a tracker event. Also used internally by the plugins. */\n emit(event: TrackerEvent): void {\n // 1 – accumulate into session data\n switch (event.type) {\n case \"pageview\":\n this.session.pageViews.push(event.data);\n break;\n\n case \"timespent\": {\n const prev = this.session.timeSpent[event.data.path] ?? 0;\n this.session.timeSpent[event.data.path] = prev + event.data.duration;\n break;\n }\n\n case \"heatmap\": {\n const key = event.data.path;\n if (!this.session.heatmap[key]) this.session.heatmap[key] = [];\n const pts = this.session.heatmap[key];\n if (pts.length < this.cfg.maxHeatmapPoints) pts.push(event.data);\n break;\n }\n\n // rageclik and uturn are forwarded to the backend only; no local state needed.\n case \"rageclik\":\n case \"uturn\":\n break;\n }\n\n // 2 – notify subscribers (used internally by React hooks)\n this.subscribers.forEach((fn) => fn(event));\n\n // 3 – user callback\n this.cfg.onEvent?.(event);\n\n // 4 – enqueue for batched remote sending\n if (this.cfg.endpoint) {\n this.queue.push(event);\n // Auto-flush once the batch size threshold is reached.\n if (this.queue.length >= this.cfg.batchSize) {\n void this.flushQueue();\n }\n }\n }\n\n /**\n * Subscribe to every emitted event. Returns an unsubscribe function.\n *\n * ```ts\n * const unsub = tracker.subscribe(event => console.log(event));\n * // later…\n * unsub();\n * ```\n */\n subscribe(fn: SubscriberFn): () => void {\n this.subscribers.add(fn);\n return () => this.subscribers.delete(fn);\n }\n\n // ─── Manual tracking helpers ────────────────────────────────────────────────\n\n /**\n * Manually record a page view and dispatch `tracker:navigate`.\n * Required for Next.js App Router — call it inside a `useEffect` that\n * depends on `usePathname()`:\n *\n * ```tsx\n * const pathname = usePathname();\n * useEffect(() => { tracker.trackPageView(pathname); }, [pathname]);\n * ```\n */\n trackPageView(path?: string): void {\n const resolvedPath =\n path ??\n (typeof window !== \"undefined\"\n ? window.location.pathname + window.location.search\n : \"/\");\n\n this.emit({\n type: \"pageview\",\n data: {\n path: resolvedPath,\n title: typeof document !== \"undefined\" ? document.title : \"\",\n timestamp: Date.now(),\n sessionId: this.session.id,\n referrer:\n typeof document !== \"undefined\"\n ? document.referrer || undefined\n : undefined,\n },\n });\n\n if (typeof window !== \"undefined\") {\n window.dispatchEvent(\n new CustomEvent(\"tracker:navigate\", {\n detail: { path: resolvedPath, title: document.title },\n }),\n );\n }\n }\n\n // ─── Identity & Feature Flags ───────────────────────────────────────────────\n\n /**\n * Identify the current user with a set of properties.\n * Properties are merged with any previously set ones and used for feature\n * flag targeting (e.g. `email`, `plan`, `country`).\n * Automatically triggers a flag re-evaluation.\n *\n * ```ts\n * tracker.identify({ email: 'user@example.com', plan: 'pro' });\n * ```\n */\n identify(properties: Record<string, string>): void {\n this.userProperties = { ...this.userProperties, ...properties };\n void this.fetchFlags();\n }\n\n /**\n * Returns the latest evaluated feature flags as `{ [key]: boolean }`.\n * Flags are fetched on `init()` and after every `identify()` call.\n */\n getFlags(): Record<string, boolean> {\n return { ...this.flags };\n }\n\n /**\n * Returns `true` if the given flag is currently enabled for this user.\n * Returns `false` for unknown flags or before flags have loaded.\n */\n isFeatureEnabled(flagKey: string): boolean {\n return this.flags[flagKey] === true;\n }\n\n /**\n * Subscribe to flag changes. Called whenever flags are re-evaluated.\n * Returns an unsubscribe function.\n *\n * ```ts\n * const unsub = tracker.onFlagsChange(flags => console.log(flags));\n * ```\n */\n onFlagsChange(fn: FlagSubscriberFn): () => void {\n this.flagSubscribers.add(fn);\n return () => this.flagSubscribers.delete(fn);\n }\n\n /** Fetch (or re-fetch) flags from the backend evaluate endpoint. */\n async fetchFlags(): Promise<void> {\n if (!this.cfg.endpoint || !this.cfg.secretKey) return;\n const url =\n this.cfg.endpoint.replace(/\\/events$/, \"\") + \"/feature-flags/evaluate\";\n try {\n const res = await fetch(url, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${this.cfg.secretKey}`,\n },\n body: JSON.stringify({ userProperties: this.userProperties }),\n });\n if (!res.ok) return;\n const data = (await res.json()) as Record<string, boolean>;\n this.flags = data;\n this.flagSubscribers.forEach((fn) => fn({ ...this.flags }));\n } catch {\n // Silent — flag fetch failure must never surface to the user.\n }\n }\n\n // ─── Data accessors ─────────────────────────────────────────────────────────\n\n /** A read-only snapshot of the current session. */\n getSession(): Readonly<SessionData> {\n return this.session;\n }\n\n /** All page views recorded so far. */\n getPageViews(): PageView[] {\n return [...this.session.pageViews];\n }\n\n /** Cumulative milliseconds spent per path. */\n getTimeSpent(): Record<string, number> {\n return { ...this.session.timeSpent };\n }\n\n /** Heatmap points for a specific path. */\n getHeatmapData(path: string): HeatmapPoint[];\n /** Heatmap points for all tracked paths. */\n getHeatmapData(): Record<string, HeatmapPoint[]>;\n getHeatmapData(\n path?: string,\n ): HeatmapPoint[] | Record<string, HeatmapPoint[]> {\n if (path !== undefined) {\n return [...(this.session.heatmap[path] ?? [])];\n }\n return Object.entries(this.session.heatmap).reduce<\n Record<string, HeatmapPoint[]>\n >((acc, [k, v]) => {\n acc[k] = [...v];\n return acc;\n }, {});\n }\n\n // ─── Network ────────────────────────────────────────────────────────────────\n\n /**\n * Immediately drain the event queue and POST pending events to `/batch`.\n * Fire-and-forget; errors are intentionally swallowed like other analytics calls.\n */\n flush(): void {\n void this.flushQueue();\n }\n\n /** Drain the queue and POST all pending events to the batch endpoint. */\n private async flushQueue(): Promise<void> {\n if (this.queue.length === 0) return;\n // Splice atomically so new events emitted during the async request don't\n // get lost — they stay in the queue for the next flush.\n const batch = this.queue.splice(0);\n await this.sendBatch(batch);\n }\n\n /**\n * Synchronous best-effort flush via `navigator.sendBeacon`.\n * Used on `pagehide` / `visibilitychange:hidden` where async fetch may be\n * cancelled by the browser before it completes.\n */\n private flushBeacon(): void {\n if (this.queue.length === 0) return;\n const batch = this.queue.splice(0);\n const url = `${this.cfg.endpoint!}/batch`;\n const blob = new Blob([this.buildBatchBody(batch)], {\n type: \"application/json\",\n });\n if (typeof navigator !== \"undefined\" && navigator.sendBeacon) {\n navigator.sendBeacon(url, blob);\n } else {\n // Fallback: fire-and-forget fetch (best effort on platforms without sendBeacon)\n void this.sendBatch(batch);\n }\n }\n\n private buildBatchBody(events: TrackerEvent[]): string {\n return JSON.stringify({\n ...(this.cfg.appId ? { appId: this.cfg.appId } : {}),\n visitorId: this.session.visitorId,\n events: events.map((e) => ({\n sessionId: this.session.id,\n type: e.type,\n data: e.data,\n })),\n });\n }\n\n private async sendBatch(events: TrackerEvent[]): Promise<void> {\n const url = `${this.cfg.endpoint!}/batch`;\n const authHeaders: Record<string, string> = this.cfg.secretKey\n ? { Authorization: `Bearer ${this.cfg.secretKey}` }\n : {};\n try {\n await fetch(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\", ...authHeaders },\n body: this.buildBatchBody(events),\n keepalive: true,\n });\n } catch {\n // Intentionally silent — analytics must never surface errors to users.\n }\n }\n}\n"]}