@marlinjai/analytics-tracker 1.3.0 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts
CHANGED
|
@@ -19,6 +19,7 @@ interface TrackerEvent {
|
|
|
19
19
|
userAgent?: string;
|
|
20
20
|
experimentId?: string;
|
|
21
21
|
variant?: string;
|
|
22
|
+
pageHash?: string;
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
/** Internal config with coreOnly flag — not part of public API. */
|
|
@@ -70,6 +71,12 @@ declare class AnalyticsTracker {
|
|
|
70
71
|
/** Whether session replay is currently active. */
|
|
71
72
|
get isReplayActive(): boolean;
|
|
72
73
|
track(partial: Omit<TrackerEvent, 'projectId' | 'sessionId' | 'timestamp'>): void;
|
|
74
|
+
/**
|
|
75
|
+
* Force the event batch to flush now. Used by session replay to drain its
|
|
76
|
+
* buffered chunks before the page goes away, independent of the batcher's
|
|
77
|
+
* own pagehide listener (which may have already fired).
|
|
78
|
+
*/
|
|
79
|
+
flush(useBeacon?: boolean): Promise<void>;
|
|
73
80
|
destroy(): void;
|
|
74
81
|
/** Remove the window.__lumitra global. */
|
|
75
82
|
cleanupGlobal(): void;
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import {a}from'./chunk-5XGN7UAV.js';var v="ap_session_id",f="ap_last_activity";function b(){let n=Date.now(),e=sessionStorage.getItem(v),t=Number(sessionStorage.getItem(f)||"0");if(e&&n-t<18e5)return sessionStorage.setItem(f,String(n)),{sessionId:e,isNew:false};let r=crypto.randomUUID();return sessionStorage.setItem(v,r),sessionStorage.setItem(f,String(n)),{sessionId:r,isNew:true}}function w(){sessionStorage.setItem(f,String(Date.now()));}async function x(n){if(n.length<5e4||typeof CompressionStream>"u")return {body:n,compressed:false};try{let e=new Blob([n]).stream().pipeThrough(new CompressionStream("gzip"));return {body:await new Response(e).blob(),compressed:!0}}catch{return {body:n,compressed:false}}}var E=3,P=1e3,h=class{constructor(e,t,r,i=false){a(this,"queue",[]);a(this,"timer",null);a(this,"endpoint");a(this,"apiKey");a(this,"debug");this.endpoint=e,this.apiKey=t,this.debug=i,this.timer=setInterval(()=>this.flush(),r??5e3),typeof window<"u"&&(window.addEventListener("visibilitychange",()=>{document.visibilityState==="hidden"&&this.flush(true);}),window.addEventListener("pagehide",()=>this.flush(true)));}add(e){this.queue.push(e),this.debug&&console.log("[analytics] queued:",e.type,e),this.queue.length>=50&&this.flush();}async flush(e=false){if(this.queue.length===0)return;let t=this.queue.splice(0,50),r=JSON.stringify(t);if(e&&r.length<=64e3&&typeof navigator<"u"&&navigator.sendBeacon){let i=new Blob([r],{type:"application/json"});if(navigator.sendBeacon(this.endpoint,i)){this.debug&&console.log("[analytics] beacon sent:",t.length,"events");return}}await this.fetchWithRetry(r,t);}async fetchWithRetry(e,t){let{body:r,compressed:i}=await x(e),s={"X-API-Key":this.apiKey};i?(s["Content-Type"]="application/json",s["Content-Encoding"]="gzip"):s["Content-Type"]="application/json";for(let o=0;o<=E;o++){try{let a=await fetch(this.endpoint,{method:"POST",headers:s,body:r,keepalive:!i,credentials:"omit"});if(a.ok){this.debug&&console.log("[analytics] flushed:",t.length,"events",i?"(gzip)":"");return}if(a.status>=400&&a.status<500){this.debug&&console.warn("[analytics] flush rejected:",a.status);return}}catch{}if(o<E){let a=P*Math.pow(2,o);await new Promise(l=>setTimeout(l,a));}}this.debug&&console.warn("[analytics] flush failed after retries, dropping",t.length,"events");}destroy(){this.timer&&(clearInterval(this.timer),this.timer=null),this.flush(true);}get pending(){return this.queue.length}};function k(){let n=navigator.userAgent;return "userAgentData"in navigator&&navigator.userAgentData?.mobile===true?/Mobile/.test(n)?"mobile":"tablet":/Macintosh/.test(n)&&navigator.maxTouchPoints>1?"tablet":/iPhone|iPod/.test(n)?"mobile":/Android/.test(n)?/Mobile/.test(n)?"mobile":"tablet":"desktop"}function A(){return {screenWidth:window.screen.width,screenHeight:window.screen.height}}var M=[/^[\w-]+_[\w-]+__[a-zA-Z0-9]{5,}$/,/^sc-[a-zA-Z]{5,}$/,/^css-[a-zA-Z0-9]+$/,/^e[a-z0-9]{6,}$/],D=/[-_][0-9a-f]{4,}$|^:r\d|^react-|^ember|^__next|^radix-/i,$=["data-testid","data-analytics","data-id"];function L(n){return M.some(e=>e.test(n))}function j(n){let e=[],t=n;for(;t&&t!==document.body&&e.length<4;){let r=O(t);if(e.unshift(r.selector),r.unique)break;t=t.parentElement;}return e.join(" > ").slice(0,256)}function O(n){let e=n.tagName.toLowerCase();for(let o of $){let a=n.getAttribute(o);if(a)return {selector:`${e}[${o}="${a}"]`,unique:true}}if(n.id&&!D.test(n.id))return {selector:`${e}#${n.id}`,unique:true};let t=n.getAttribute("role");if(t)return {selector:`${e}[role="${t}"]`,unique:false};let r=n.getAttribute("aria-label");if(r){let o=r.slice(0,50).replace(/"/g,'\\"');return {selector:`${e}[aria-label="${o}"]`,unique:false}}if(e==="input"||e==="select"||e==="textarea"){let o=n.getAttribute("name");if(o)return {selector:`${e}[name="${o}"]`,unique:false};let a=n.getAttribute("type");if(a)return {selector:`${e}[type="${a}"]`,unique:false}}if(e==="a"){let o=n.getAttribute("href");if(o&&!o.startsWith("javascript:"))try{return {selector:`a[href="${new URL(o,location.origin).pathname.slice(0,80)}"]`,unique:!1}}catch{}}let i=e;if(n.className&&typeof n.className=="string"){let o=n.className.trim().split(/\s+/).filter(a=>a&&!L(a)).slice(0,2);o.length>0&&(i+="."+o.join("."));}let s=n.parentElement;if(s){let o=Array.from(s.children).filter(a=>a.tagName===n.tagName);if(o.length>1){let a=o.indexOf(n)+1;i+=`:nth-of-type(${a})`;}}return {selector:i,unique:false}}function q(){let n=document.body;return !n||n.children.length>3?false:n.querySelectorAll(":scope > :not(canvas):not(script):not(style):not(link)").length===0&&n.querySelectorAll("canvas").length>=1}function N(n){let e=["utm_source","utm_medium","utm_campaign","utm_term","utm_content"],t=new URL(n).searchParams,r={};for(let i of e){let s=t.get(i);s&&(r[i]=s);}return r}function T(n){let e=()=>{let i=N(location.href);n({type:"pageview",url:location.href,referrer:document.referrer,title:document.title,...Object.keys(i).length>0&&{properties:i}});},t=history.pushState.bind(history),r=history.replaceState.bind(history);return history.pushState=function(...i){t(...i),e();},history.replaceState=function(...i){r(...i),e();},window.addEventListener("popstate",e),e(),()=>{history.pushState=t,history.replaceState=r,window.removeEventListener("popstate",e);}}function S(n){let e=typeof PointerEvent<"u",t=i=>{if(i.button!==0)return;let s=i.target;if(!s)return;let o=s.tagName.toLowerCase();if(o==="html"||o==="body"||s.closest?.("#lumitra-widget-host, #lumitra-overlay-host, #lumitra-heatmap-container, #lumitra-heatmap-fixed"))return;let a=q();if(!a){let d=document.elementFromPoint(i.clientX,i.clientY);d&&d!==s&&(s=d);}let l=s.getBoundingClientRect(),p="pointerType"in i?i.pointerType:void 0;n({type:"click",url:location.href,x:i.pageX,y:i.pageY,selector:a?"":j(s),...p&&{inputType:p},...l.width>0&&!a&&{properties:{ox:Math.round(i.clientX-l.left),oy:Math.round(i.clientY-l.top),ew:Math.round(l.width),eh:Math.round(l.height),...p&&{pt:p}}}});},r=e?"pointerup":"click";return document.addEventListener(r,t,{capture:true}),()=>document.removeEventListener(r,t,{capture:true})}function I(n){let e=0,t=false,r=()=>{t||(t=true,requestAnimationFrame(()=>{let i=window.scrollY,s=document.documentElement.scrollHeight-window.innerHeight,o=s>0?Math.round(i/s*100):0;o>e&&(e=o,n({type:"scroll",url:location.href,scrollDepth:e})),t=false;}));};return window.addEventListener("scroll",r,{passive:true}),()=>{window.removeEventListener("scroll",r);}}function y(n,e=0){let t=e;for(let r=0;r<n.length;r++){let i=Math.imul(n.charCodeAt(r),3432918353);t^=Math.imul(i<<15|i>>>17,461845907),t=Math.imul(t<<13|t>>>19,5)+3864292196;}return t^=n.length,t^=t>>>16,t=Math.imul(t,2246822507),t^=t>>>13,t=Math.imul(t,3266489909),t^=t>>>16,t>>>0}var R="ap_exp_";function F(n,e,t){let i=y(`${n}:${e}`)%1e4,s=0;for(let o of t)if(s+=o.weight*100,i<s)return o.key;return t[0]?.key??"control"}function B(n){try{return sessionStorage.getItem(`${R}${n}`)}catch{return null}}function C(n,e){try{sessionStorage.setItem(`${R}${n}`,e);}catch{}}var g=class{constructor(e){a(this,"experiments",[]);a(this,"flags",[]);a(this,"identityId");a(this,"assignments",new Map);this.identityId=e;}setDefinitions(e,t){this.experiments=e,this.flags=t,this.resolveAllAssignments();}identify(e){e!==this.identityId&&(this.identityId=e,this.assignments.clear(),this.resolveAllAssignments());}getVariant(e){return this.assignments.get(e)??null}getFlag(e){let t=this.flags.find(r=>r.key===e);return !(!t||!t.enabled||t.rolloutPercentage<100&&y(`${e}:${this.identityId}`)%1e4>=t.rolloutPercentage*100)}getActiveExperiments(){let e={};for(let t of this.experiments){let r=this.assignments.get(t.key);r&&(e[t.id]=r);}return e}getAllAssignments(){let e={};for(let[t,r]of this.assignments)e[t]=r;return e}getAllFlags(){let e={};for(let t of this.flags)e[t.key]=this.getFlag(t.key);return e}setVariant(e,t){this.assignments.set(e,t),C(e,t);}resolveAllAssignments(){for(let e of this.experiments)this.resolveAssignment(e);}resolveAssignment(e){let t=B(e.key);if(t&&e.variants.some(i=>i.key===t)){this.assignments.set(e.key,t);return}let r=F(e.key,this.identityId,e.variants);this.assignments.set(e.key,r),C(e.key,r);}};var V=3e3,m=class{constructor(e){a(this,"config");a(this,"sessionId");a(this,"batcher");a(this,"cleanups",[]);a(this,"experimentManager");a(this,"remoteConfig",null);a(this,"readyPromise");a(this,"resolveReady");a(this,"replayActive",false);a(this,"trackingEnabled",false);this.config=e;let{sessionId:t,isNew:r}=b();this.sessionId=t,this.experimentManager=new g(t),this.readyPromise=new Promise(i=>{this.resolveReady=i;}),this.batcher=new h(e.endpoint,e.apiKey,e.flushInterval,e.debug),r&&this.track({type:"session_start",url:location.href,properties:{maxTouchPoints:navigator.maxTouchPoints??0,pointerType:window.matchMedia("(pointer: coarse)").matches?"coarse":"fine",dpr:window.devicePixelRatio??1,viewportWidth:window.innerWidth,viewportHeight:window.innerHeight}}),this.cleanups.push(T(i=>this.track(i))),e.coreOnly||this.attachBehavioralListeners(),this.updateGlobal(),this.fetchRemoteConfig();}enableTracking(){this.trackingEnabled||(this.attachBehavioralListeners(),this.config.debug&&console.log("[analytics] behavioral tracking enabled (post-consent)"));}get isTrackingEnabled(){return this.trackingEnabled}attachBehavioralListeners(){this.trackingEnabled||(this.trackingEnabled=true,this.config.heatmap!==false&&this.cleanups.push(S(e=>this.track(e))),this.config.scrollDepth!==false&&this.cleanups.push(I(e=>this.track(e))));}ready(){return this.readyPromise}getVariant(e){return this.experimentManager.getVariant(e)}setVariant(e,t){this.experimentManager.setVariant(e,t),this.updateGlobal(),typeof window<"u"&&window.dispatchEvent(new CustomEvent("lumitra:variant-changed",{detail:{key:e,variant:t}}));}getFlag(e){return this.experimentManager.getFlag(e)}identify(e){this.experimentManager.identify(e),this.updateGlobal();}get projectId(){return this.config.projectId}enableReplay(){this.replayActive||(this.replayActive=true,import('./replay-S33L5QID.js').then(e=>e.initReplay(this,this.config.replayPrivacy)).catch(()=>{this.replayActive=false,this.config.debug&&console.warn("[analytics] rrweb not available, replay disabled");}));}disableReplay(){this.replayActive&&(this.replayActive=false,import('./replay-S33L5QID.js').then(e=>e.stopReplay()).catch(()=>{}));}get isReplayActive(){return this.replayActive}track(e){w();let{screenWidth:t,screenHeight:r}=A(),i=this.experimentManager.getActiveExperiments(),s=Object.keys(i),o,a,l=e.properties;if(s.length>0){let d=s[0];o=d,a=i[d],s.length>1&&(l={...l,_experiments:i});}let p={...e,projectId:this.config.projectId,sessionId:this.sessionId,timestamp:Date.now(),screenWidth:t,screenHeight:r,deviceType:k(),userAgent:navigator.userAgent,...o&&{experimentId:o},...a&&{variant:a},...l&&{properties:l}};this.batcher.add(p);}destroy(){for(let e of this.cleanups)e();this.cleanups=[],this.batcher.destroy(),this.cleanupGlobal();}cleanupGlobal(){typeof window<"u"&&delete window.__lumitra;}updateGlobal(){typeof window>"u"||(window.__lumitra={projectId:this.config.projectId,experiments:this.experimentManager.getAllAssignments(),flags:this.experimentManager.getAllFlags(),ready:this.readyPromise});}fetchRemoteConfig(){let t=`${this.config.endpoint.replace(/\/api\/collect\/?$/,"")}/api/projects/${this.config.projectId}/config`,r=new AbortController,i=setTimeout(()=>r.abort(),V);fetch(t,{method:"GET",signal:r.signal,credentials:"omit"}).then(s=>{if(!s.ok)throw new Error(`Config fetch failed: ${s.status}`);return s.json()}).then(s=>{this.remoteConfig=s,this.experimentManager.setDefinitions(s.experiments??[],s.flags??[]),this.updateGlobal(),typeof window<"u"&&window.dispatchEvent(new CustomEvent("lumitra:ready",{detail:{projectId:this.config.projectId,experiments:this.experimentManager.getAllAssignments(),flags:this.experimentManager.getAllFlags()}})),this.config.debug&&console.log("[analytics] remote config loaded:",s);}).catch(s=>{this.config.debug&&console.warn("[analytics] remote config fetch failed, using defaults:",s);}).finally(()=>{clearTimeout(i),this.resolveReady();});}};var u=null;function de(n){return u?(n.debug&&console.warn("[analytics] already initialized, returning existing instance"),u):(u=new m({...n,coreOnly:true}),u)}function ge(){u?.enableTracking();}function fe(){return u}function he(){u?.destroy(),u=null;}function me(){u?.enableReplay();}function ye(){u?.disableReplay();}export{m as AnalyticsTracker,g as ExperimentManager,he as destroy,ye as disableReplay,me as enableReplay,ge as enableTracking,fe as getTracker,de as init};//# sourceMappingURL=index.js.map
|
|
1
|
+
import {a}from'./chunk-5XGN7UAV.js';var E="ap_session_id",m="ap_last_activity";function k(){let n=Date.now(),e=sessionStorage.getItem(E),t=Number(sessionStorage.getItem(m)||"0");if(e&&n-t<18e5)return sessionStorage.setItem(m,String(n)),{sessionId:e,isNew:false};let i=crypto.randomUUID();return sessionStorage.setItem(E,i),sessionStorage.setItem(m,String(n)),{sessionId:i,isNew:true}}function A(){sessionStorage.setItem(m,String(Date.now()));}async function S(n){if(n.length<5e4||typeof CompressionStream>"u")return {body:n,compressed:false};try{let e=new Blob([n]).stream().pipeThrough(new CompressionStream("gzip"));return {body:await new Response(e).blob(),compressed:!0}}catch{return {body:n,compressed:false}}}var T=3,j=1e3,y=class{constructor(e,t,i,r=false){a(this,"queue",[]);a(this,"timer",null);a(this,"endpoint");a(this,"apiKey");a(this,"debug");this.endpoint=e,this.apiKey=t,this.debug=r,this.timer=setInterval(()=>this.flush(),i??5e3),typeof window<"u"&&(window.addEventListener("visibilitychange",()=>{document.visibilityState==="hidden"&&this.flush(true);}),window.addEventListener("pagehide",()=>this.flush(true)));}add(e){this.queue.push(e),this.debug&&console.log("[analytics] queued:",e.type,e),this.queue.length>=50&&this.flush();}async flush(e=false){if(this.queue.length===0)return;let t=this.queue.splice(0,50),i=JSON.stringify(t);if(e&&i.length<=64e3&&typeof navigator<"u"&&navigator.sendBeacon){let r=new Blob([i],{type:"application/json"});if(navigator.sendBeacon(this.endpoint,r)){this.debug&&console.log("[analytics] beacon sent:",t.length,"events");return}}await this.fetchWithRetry(i,t);}async fetchWithRetry(e,t){let{body:i,compressed:r}=await S(e),s={"X-API-Key":this.apiKey};r?(s["Content-Type"]="application/json",s["Content-Encoding"]="gzip"):s["Content-Type"]="application/json";for(let o=0;o<=T;o++){try{let a=await fetch(this.endpoint,{method:"POST",headers:s,body:i,keepalive:!r,credentials:"omit"});if(a.ok){this.debug&&console.log("[analytics] flushed:",t.length,"events",r?"(gzip)":"");return}if(a.status>=400&&a.status<500){this.debug&&console.warn("[analytics] flush rejected:",a.status);return}}catch{}if(o<T){let a=j*Math.pow(2,o);await new Promise(l=>setTimeout(l,a));}}this.debug&&console.warn("[analytics] flush failed after retries, dropping",t.length,"events");}destroy(){this.timer&&(clearInterval(this.timer),this.timer=null),this.flush(true);}get pending(){return this.queue.length}};function I(){let n=navigator.userAgent;return "userAgentData"in navigator&&navigator.userAgentData?.mobile===true?/Mobile/.test(n)?"mobile":"tablet":/Macintosh/.test(n)&&navigator.maxTouchPoints>1?"tablet":/iPhone|iPod/.test(n)?"mobile":/Android/.test(n)?/Mobile/.test(n)?"mobile":"tablet":"desktop"}function C(){return {screenWidth:window.screen.width,screenHeight:window.screen.height}}function g(n,e=0){let t=e;for(let i=0;i<n.length;i++){let r=Math.imul(n.charCodeAt(i),3432918353);t^=Math.imul(r<<15|r>>>17,461845907),t=Math.imul(t<<13|t>>>19,5)+3864292196;}return t^=n.length,t^=t>>>16,t=Math.imul(t,2246822507),t^=t>>>13,t=Math.imul(t,3266489909),t^=t>>>16,t>>>0}var v="",P=/^(__analytics|lumitra|rrweb)/,q=["id","class","role","data-testid","data-analytics","data-id","type","name"];function O(n){let e=0;for(let t=0;t<n.children.length;t++){let i=n.children[t];(!i.id||!P.test(i.id))&&e++;}return e}function h(){if(typeof document>"u")return "";let n=[],e=document.createTreeWalker(document.body,NodeFilter.SHOW_ELEMENT),t=e.currentNode;for(;t;){let r=t;if(r.id&&P.test(r.id)){let l=e.nextSibling();for(;!l&&e.parentNode();)l=e.nextSibling();t=l;continue}let s=r.tagName,o=O(r),a=[];for(let l of q){let u=r.getAttribute(l);u&&a.push(`${l}=${u}`);}n.push(`${s}:${o}${a.length?":"+a.join(","):""}`),t=e.nextNode();}let i=n.join("|");return v=(g(i)>>>0).toString(16).padStart(8,"0"),v}function R(){return v}function b(){v="";}var F=[/^[\w-]+_[\w-]+__[a-zA-Z0-9]{5,}$/,/^sc-[a-zA-Z]{5,}$/,/^css-[a-zA-Z0-9]+$/,/^e[a-z0-9]{6,}$/],H=/[-_][0-9a-f]{4,}$|^:r\d|^react-|^ember|^__next|^radix-/i,B=["data-testid","data-analytics","data-id"];function U(n){return F.some(e=>e.test(n))}function V(n){let e=[],t=n;for(;t&&t!==document.body&&e.length<4;){let i=Y(t);if(e.unshift(i.selector),i.unique)break;t=t.parentElement;}return e.join(" > ").slice(0,256)}function Y(n){let e=n.tagName.toLowerCase();for(let o of B){let a=n.getAttribute(o);if(a)return {selector:`${e}[${o}="${a}"]`,unique:true}}if(n.id&&!H.test(n.id))return {selector:`${e}#${n.id}`,unique:true};let t=n.getAttribute("role");if(t)return {selector:`${e}[role="${t}"]`,unique:false};let i=n.getAttribute("aria-label");if(i){let o=i.slice(0,50).replace(/"/g,'\\"');return {selector:`${e}[aria-label="${o}"]`,unique:false}}if(e==="input"||e==="select"||e==="textarea"){let o=n.getAttribute("name");if(o)return {selector:`${e}[name="${o}"]`,unique:false};let a=n.getAttribute("type");if(a)return {selector:`${e}[type="${a}"]`,unique:false}}if(e==="a"){let o=n.getAttribute("href");if(o&&!o.startsWith("javascript:"))try{return {selector:`a[href="${new URL(o,location.origin).pathname.slice(0,80)}"]`,unique:!1}}catch{}}let r=e;if(n.className&&typeof n.className=="string"){let o=n.className.trim().split(/\s+/).filter(a=>a&&!U(a)).slice(0,2);o.length>0&&(r+="."+o.join("."));}let s=n.parentElement;if(s){let o=Array.from(s.children).filter(a=>a.tagName===n.tagName);if(o.length>1){let a=o.indexOf(n)+1;r+=`:nth-of-type(${a})`;}}return {selector:r,unique:false}}function G(){let n=document.body;return !n||n.children.length>3?false:n.querySelectorAll(":scope > :not(canvas):not(script):not(style):not(link)").length===0&&n.querySelectorAll("canvas").length>=1}function K(n){let e=["utm_source","utm_medium","utm_campaign","utm_term","utm_content"],t=new URL(n).searchParams,i={};for(let r of e){let s=t.get(r);s&&(i[r]=s);}return i}function _(n){let e=()=>{let s=K(location.href);n({type:"pageview",url:location.href,referrer:document.referrer,title:document.title,...Object.keys(s).length>0&&{properties:s}});},t=history.pushState.bind(history),i=history.replaceState.bind(history);history.pushState=function(...s){t(...s),e(),b(),requestAnimationFrame(()=>h());},history.replaceState=function(...s){i(...s),e(),b(),requestAnimationFrame(()=>h());};let r=()=>{e(),b(),requestAnimationFrame(()=>h());};return window.addEventListener("popstate",r),e(),requestAnimationFrame(()=>h()),()=>{history.pushState=t,history.replaceState=i,window.removeEventListener("popstate",r);}}function M(n){let e=typeof PointerEvent<"u",t=r=>{if(r.button!==0)return;let s=r.target;if(!s)return;let o=s.tagName.toLowerCase();if(o==="html"||o==="body"||s.closest?.("#lumitra-widget-host, #lumitra-overlay-host, #lumitra-heatmap-container, #lumitra-heatmap-fixed"))return;let a=G();if(!a){let p=document.elementFromPoint(r.clientX,r.clientY);p&&p!==s&&(s=p);}let l=s.getBoundingClientRect(),u="pointerType"in r?r.pointerType:void 0;n({type:"click",url:location.href,x:r.pageX,y:r.pageY,selector:a?"":V(s),...u&&{inputType:u},...l.width>0&&!a&&{properties:{ox:Math.round(r.clientX-l.left),oy:Math.round(r.clientY-l.top),ew:Math.round(l.width),eh:Math.round(l.height),...u&&{pt:u}}}});},i=e?"pointerup":"click";return document.addEventListener(i,t,{capture:true}),()=>document.removeEventListener(i,t,{capture:true})}function $(n){let e=0,t=false,i=()=>{t||(t=true,requestAnimationFrame(()=>{let r=window.scrollY,s=document.documentElement.scrollHeight-window.innerHeight,o=s>0?Math.round(r/s*100):0;o>e&&(e=o,n({type:"scroll",url:location.href,scrollDepth:e})),t=false;}));};return window.addEventListener("scroll",i,{passive:true}),()=>{window.removeEventListener("scroll",i);}}var L="ap_exp_";function W(n,e,t){let r=g(`${n}:${e}`)%1e4,s=0;for(let o of t)if(s+=o.weight*100,r<s)return o.key;return t[0]?.key??"control"}function X(n){try{return sessionStorage.getItem(`${L}${n}`)}catch{return null}}function D(n,e){try{sessionStorage.setItem(`${L}${n}`,e);}catch{}}var f=class{constructor(e){a(this,"experiments",[]);a(this,"flags",[]);a(this,"identityId");a(this,"assignments",new Map);this.identityId=e;}setDefinitions(e,t){this.experiments=e,this.flags=t,this.resolveAllAssignments();}identify(e){e!==this.identityId&&(this.identityId=e,this.assignments.clear(),this.resolveAllAssignments());}getVariant(e){return this.assignments.get(e)??null}getFlag(e){let t=this.flags.find(i=>i.key===e);return !(!t||!t.enabled||t.rolloutPercentage<100&&g(`${e}:${this.identityId}`)%1e4>=t.rolloutPercentage*100)}getActiveExperiments(){let e={};for(let t of this.experiments){let i=this.assignments.get(t.key);i&&(e[t.id]=i);}return e}getAllAssignments(){let e={};for(let[t,i]of this.assignments)e[t]=i;return e}getAllFlags(){let e={};for(let t of this.flags)e[t.key]=this.getFlag(t.key);return e}setVariant(e,t){this.assignments.set(e,t),D(e,t);}resolveAllAssignments(){for(let e of this.experiments)this.resolveAssignment(e);}resolveAssignment(e){let t=X(e.key);if(t&&e.variants.some(r=>r.key===t)){this.assignments.set(e.key,t);return}let i=W(e.key,this.identityId,e.variants);this.assignments.set(e.key,i),D(e.key,i);}};var z=3e3,w=class{constructor(e){a(this,"config");a(this,"sessionId");a(this,"batcher");a(this,"cleanups",[]);a(this,"experimentManager");a(this,"remoteConfig",null);a(this,"readyPromise");a(this,"resolveReady");a(this,"replayActive",false);a(this,"trackingEnabled",false);this.config=e;let{sessionId:t,isNew:i}=k();this.sessionId=t,this.experimentManager=new f(t),this.readyPromise=new Promise(r=>{this.resolveReady=r;}),this.batcher=new y(e.endpoint,e.apiKey,e.flushInterval,e.debug),i&&this.track({type:"session_start",url:location.href,properties:{maxTouchPoints:navigator.maxTouchPoints??0,pointerType:window.matchMedia("(pointer: coarse)").matches?"coarse":"fine",dpr:window.devicePixelRatio??1,viewportWidth:window.innerWidth,viewportHeight:window.innerHeight}}),this.cleanups.push(_(r=>this.track(r))),e.coreOnly||this.attachBehavioralListeners(),this.updateGlobal(),this.fetchRemoteConfig();}enableTracking(){this.trackingEnabled||(this.attachBehavioralListeners(),this.config.debug&&console.log("[analytics] behavioral tracking enabled (post-consent)"));}get isTrackingEnabled(){return this.trackingEnabled}attachBehavioralListeners(){this.trackingEnabled||(this.trackingEnabled=true,this.config.heatmap!==false&&this.cleanups.push(M(e=>this.track(e))),this.config.scrollDepth!==false&&this.cleanups.push($(e=>this.track(e))));}ready(){return this.readyPromise}getVariant(e){return this.experimentManager.getVariant(e)}setVariant(e,t){this.experimentManager.setVariant(e,t),this.updateGlobal(),typeof window<"u"&&window.dispatchEvent(new CustomEvent("lumitra:variant-changed",{detail:{key:e,variant:t}}));}getFlag(e){return this.experimentManager.getFlag(e)}identify(e){this.experimentManager.identify(e),this.updateGlobal();}get projectId(){return this.config.projectId}enableReplay(){this.replayActive||(this.replayActive=true,import('./replay-SX42SUVY.js').then(e=>e.initReplay(this,this.config.replayPrivacy)).catch(()=>{this.replayActive=false,this.config.debug&&console.warn("[analytics] rrweb not available, replay disabled");}));}disableReplay(){this.replayActive&&(this.replayActive=false,import('./replay-SX42SUVY.js').then(e=>e.stopReplay()).catch(()=>{}));}get isReplayActive(){return this.replayActive}track(e){A();let{screenWidth:t,screenHeight:i}=C(),r=this.experimentManager.getActiveExperiments(),s=Object.keys(r),o,a,l=e.properties;if(s.length>0){let x=s[0];o=x,a=r[x],s.length>1&&(l={...l,_experiments:r});}let u=R()||void 0,p={...e,projectId:this.config.projectId,sessionId:this.sessionId,timestamp:Date.now(),screenWidth:t,screenHeight:i,deviceType:I(),userAgent:navigator.userAgent,...o&&{experimentId:o},...a&&{variant:a},...l&&{properties:l},...u&&{pageHash:u}};this.batcher.add(p);}flush(e=false){return this.batcher.flush(e)}destroy(){for(let e of this.cleanups)e();this.cleanups=[],this.batcher.destroy(),this.cleanupGlobal();}cleanupGlobal(){typeof window<"u"&&delete window.__lumitra;}updateGlobal(){typeof window>"u"||(window.__lumitra={projectId:this.config.projectId,experiments:this.experimentManager.getAllAssignments(),flags:this.experimentManager.getAllFlags(),ready:this.readyPromise});}fetchRemoteConfig(){let t=`${this.config.endpoint.replace(/\/api\/collect\/?$/,"")}/api/projects/${this.config.projectId}/config`,i=new AbortController,r=setTimeout(()=>i.abort(),z);fetch(t,{method:"GET",signal:i.signal,credentials:"omit"}).then(s=>{if(!s.ok)throw new Error(`Config fetch failed: ${s.status}`);return s.json()}).then(s=>{this.remoteConfig=s,this.experimentManager.setDefinitions(s.experiments??[],s.flags??[]),this.updateGlobal(),typeof window<"u"&&window.dispatchEvent(new CustomEvent("lumitra:ready",{detail:{projectId:this.config.projectId,experiments:this.experimentManager.getAllAssignments(),flags:this.experimentManager.getAllFlags()}})),this.config.debug&&console.log("[analytics] remote config loaded:",s);}).catch(s=>{this.config.debug&&console.warn("[analytics] remote config fetch failed, using defaults:",s);}).finally(()=>{clearTimeout(r),this.resolveReady();});}};var d=null;function Ae(n){return d?(n.debug&&console.warn("[analytics] already initialized, returning existing instance"),d):(d=new w({...n,coreOnly:true}),d)}function Se(){d?.enableTracking();}function Te(){return d}function Ie(){d?.destroy(),d=null;}function Ce(){d?.enableReplay();}function Pe(){d?.disableReplay();}export{w as AnalyticsTracker,f as ExperimentManager,Ie as destroy,Pe as disableReplay,Ce as enableReplay,Se as enableTracking,Te as getTracker,Ae as init};//# sourceMappingURL=index.js.map
|
|
2
2
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/session.ts","../src/compress.ts","../src/batch.ts","../src/device.ts","../src/listeners.ts","../src/hash.ts","../src/experiment.ts","../src/tracker.ts","../src/index.ts"],"names":["SESSION_KEY","LAST_ACTIVITY_KEY","getOrCreateSession","now","stored","lastActivity","sessionId","touchSession","compressPayload","json","stream","MAX_RETRIES","BASE_DELAY_MS","EventBatcher","endpoint","apiKey","flushInterval","debug","__publicField","event","useBeacon","batch","body","blob","payload","compressed","headers","attempt","res","delay","r","getDeviceType","ua","getScreenDimensions","DYNAMIC_CLASS_RE","AUTO_ID_RE","DATA_ATTRS","isDynamicClass","cls","re","getStableSelector","el","parts","current","segment","buildSegment","tag","attr","val","role","ariaLabel","safe","name","type","href","selector","stable","c","parent","siblings","s","index","isCanvasOnlyPage","parseUtmParams","url","UTM_KEYS","params","utm","key","value","attachPageviewListener","cb","trackPageview","utmParams","origPushState","origReplaceState","args","attachClickListener","usePointer","handler","e","target","canvasOnly","deepest","rect","pointerType","eventName","attachScrollListener","maxDepth","ticking","scrollTop","docHeight","depth","murmurhash3","seed","h","i","k","EXP_STORAGE_PREFIX","assignVariant","experimentKey","userId","variants","bucket","cumulative","variant","readStoredAssignment","storeAssignment","ExperimentManager","experiments","flags","flag","f","active","exp","result","v","CONFIG_TIMEOUT_MS","AnalyticsTracker","config","isNew","resolve","mod","partial","screenWidth","screenHeight","activeExperiments","experimentIds","experimentId","properties","firstId","cleanup","controller","timeout","data","err","instance","init","enableTracking","getTracker","destroy","enableReplay","disableReplay"],"mappings":"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","file":"index.js","sourcesContent":["import { SESSION_TIMEOUT_MS } from './constants';\n\nconst SESSION_KEY = 'ap_session_id';\nconst LAST_ACTIVITY_KEY = 'ap_last_activity';\n\nexport function getOrCreateSession(): { sessionId: string; isNew: boolean } {\n const now = Date.now();\n const stored = sessionStorage.getItem(SESSION_KEY);\n const lastActivity = Number(sessionStorage.getItem(LAST_ACTIVITY_KEY) || '0');\n\n // Existing session that hasn't timed out\n if (stored && now - lastActivity < SESSION_TIMEOUT_MS) {\n sessionStorage.setItem(LAST_ACTIVITY_KEY, String(now));\n return { sessionId: stored, isNew: false };\n }\n\n // New session\n const sessionId = crypto.randomUUID();\n sessionStorage.setItem(SESSION_KEY, sessionId);\n sessionStorage.setItem(LAST_ACTIVITY_KEY, String(now));\n return { sessionId, isNew: true };\n}\n\nexport function touchSession(): void {\n sessionStorage.setItem(LAST_ACTIVITY_KEY, String(Date.now()));\n}\n\nexport function getSessionId(): string | null {\n return sessionStorage.getItem(SESSION_KEY);\n}\n","import { COMPRESS_THRESHOLD_BYTES } from './constants';\n\nexport interface CompressedPayload {\n body: BodyInit;\n compressed: boolean;\n}\n\n/**\n * Compress a JSON string with gzip if the browser supports CompressionStream\n * and the payload exceeds the threshold. Falls back to uncompressed otherwise.\n */\nexport async function compressPayload(json: string): Promise<CompressedPayload> {\n if (\n json.length < COMPRESS_THRESHOLD_BYTES ||\n typeof CompressionStream === 'undefined'\n ) {\n return { body: json, compressed: false };\n }\n\n try {\n const stream = new Blob([json])\n .stream()\n .pipeThrough(new CompressionStream('gzip'));\n\n const compressed = await new Response(stream).blob();\n return { body: compressed, compressed: true };\n } catch {\n // Compression failed — send uncompressed\n return { body: json, compressed: false };\n }\n}\n","import type { TrackerEvent } from './constants';\nimport { FLUSH_INTERVAL_MS, MAX_BATCH_SIZE, BEACON_MAX_BYTES } from './constants';\nimport { compressPayload } from './compress.js';\n\nconst MAX_RETRIES = 3;\nconst BASE_DELAY_MS = 1000;\n\nexport class EventBatcher {\n private queue: TrackerEvent[] = [];\n private timer: ReturnType<typeof setInterval> | null = null;\n private endpoint: string;\n private apiKey: string;\n private debug: boolean;\n\n constructor(endpoint: string, apiKey: string, flushInterval?: number, debug = false) {\n this.endpoint = endpoint;\n this.apiKey = apiKey;\n this.debug = debug;\n this.timer = setInterval(() => this.flush(), flushInterval ?? FLUSH_INTERVAL_MS);\n\n if (typeof window !== 'undefined') {\n window.addEventListener('visibilitychange', () => {\n if (document.visibilityState === 'hidden') this.flush(true);\n });\n window.addEventListener('pagehide', () => this.flush(true));\n }\n }\n\n add(event: TrackerEvent): void {\n this.queue.push(event);\n if (this.debug) console.log('[analytics] queued:', event.type, event);\n if (this.queue.length >= MAX_BATCH_SIZE) this.flush();\n }\n\n async flush(useBeacon = false): Promise<void> {\n if (this.queue.length === 0) return;\n\n const batch = this.queue.splice(0, MAX_BATCH_SIZE);\n const body = JSON.stringify(batch);\n\n // Only use beacon for small payloads — large ones (replay chunks) need\n // compression which requires custom headers that beacon doesn't support\n if (useBeacon && body.length <= BEACON_MAX_BYTES && typeof navigator !== 'undefined' && navigator.sendBeacon) {\n const blob = new Blob([body], { type: 'application/json' });\n const sent = navigator.sendBeacon(this.endpoint, blob);\n if (sent) {\n if (this.debug) console.log('[analytics] beacon sent:', batch.length, 'events');\n return;\n }\n // Beacon failed, fall through to fetch\n }\n\n await this.fetchWithRetry(body, batch);\n }\n\n private async fetchWithRetry(body: string, batch: TrackerEvent[]): Promise<void> {\n // Compress large payloads (replay FullSnapshots with inlined CSS can be 2-5MB)\n const { body: payload, compressed } = await compressPayload(body);\n\n const headers: Record<string, string> = {\n 'X-API-Key': this.apiKey,\n };\n\n if (compressed) {\n headers['Content-Type'] = 'application/json';\n headers['Content-Encoding'] = 'gzip';\n } else {\n headers['Content-Type'] = 'application/json';\n }\n\n for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {\n try {\n const res = await fetch(this.endpoint, {\n method: 'POST',\n headers,\n body: payload,\n keepalive: !compressed, // keepalive has 64KB limit, skip for compressed blobs\n credentials: 'omit',\n });\n\n if (res.ok) {\n if (this.debug) console.log('[analytics] flushed:', batch.length, 'events', compressed ? '(gzip)' : '');\n return;\n }\n\n // Don't retry 4xx errors\n if (res.status >= 400 && res.status < 500) {\n if (this.debug) console.warn('[analytics] flush rejected:', res.status);\n return;\n }\n } catch {\n // Network error, retry\n }\n\n if (attempt < MAX_RETRIES) {\n const delay = BASE_DELAY_MS * Math.pow(2, attempt);\n await new Promise((r) => setTimeout(r, delay));\n }\n }\n\n if (this.debug) console.warn('[analytics] flush failed after retries, dropping', batch.length, 'events');\n }\n\n destroy(): void {\n if (this.timer) {\n clearInterval(this.timer);\n this.timer = null;\n }\n this.flush(true);\n }\n\n get pending(): number {\n return this.queue.length;\n }\n}\n","import type { DeviceType } from './constants';\n\nexport function getDeviceType(): DeviceType {\n const ua = navigator.userAgent;\n\n // Signal 1: Client Hints API (Chromium only, most reliable)\n if ('userAgentData' in navigator) {\n const uad = (navigator as any).userAgentData;\n if (uad?.mobile === true) {\n return /Mobile/.test(ua) ? 'mobile' : 'tablet';\n }\n }\n\n // Signal 2: iPad detection (iPadOS 13+ reports as Mac)\n if (/Macintosh/.test(ua) && navigator.maxTouchPoints > 1) {\n return 'tablet';\n }\n\n // Signal 3: iOS devices (iPhone/iPod)\n if (/iPhone|iPod/.test(ua)) return 'mobile';\n\n // Signal 4: Android devices\n if (/Android/.test(ua)) {\n return /Mobile/.test(ua) ? 'mobile' : 'tablet';\n }\n\n // Signal 5: Windows/Mac/Linux/CrOS -> desktop\n return 'desktop';\n}\n\nexport function getScreenDimensions() {\n return {\n screenWidth: window.screen.width,\n screenHeight: window.screen.height,\n };\n}\n","import type { TrackerEvent } from './constants';\n\ntype EventCallback = (event: Omit<TrackerEvent, 'projectId' | 'sessionId' | 'timestamp'>) => void;\n\n// ── Dynamic class patterns (CSS Modules, styled-components, Emotion) ─────────\nconst DYNAMIC_CLASS_RE = [\n /^[\\w-]+_[\\w-]+__[a-zA-Z0-9]{5,}$/, // CSS Modules\n /^sc-[a-zA-Z]{5,}$/, // styled-components\n /^css-[a-zA-Z0-9]+$/, // Emotion\n /^e[a-z0-9]{6,}$/, // Emotion (short)\n];\n\n// Auto-generated IDs to skip\nconst AUTO_ID_RE = /[-_][0-9a-f]{4,}$|^:r\\d|^react-|^ember|^__next|^radix-/i;\n\n// Stable data attributes (priority order)\nconst DATA_ATTRS = ['data-testid', 'data-analytics', 'data-id'] as const;\n\nfunction isDynamicClass(cls: string): boolean {\n return DYNAMIC_CLASS_RE.some((re) => re.test(cls));\n}\n\nfunction getStableSelector(el: Element): string {\n const parts: string[] = [];\n let current: Element | null = el;\n\n while (current && current !== document.body && parts.length < 4) {\n const segment = buildSegment(current);\n parts.unshift(segment.selector);\n if (segment.unique) break;\n current = current.parentElement;\n }\n\n return parts.join(' > ').slice(0, 256);\n}\n\nfunction buildSegment(el: Element): { selector: string; unique: boolean } {\n const tag = el.tagName.toLowerCase();\n\n // 1. Data attributes (most stable)\n for (const attr of DATA_ATTRS) {\n const val = el.getAttribute(attr);\n if (val) return { selector: `${tag}[${attr}=\"${val}\"]`, unique: true };\n }\n\n // 2. Element id (skip auto-generated)\n if (el.id && !AUTO_ID_RE.test(el.id)) {\n return { selector: `${tag}#${el.id}`, unique: true };\n }\n\n // 3. Semantic attributes\n const role = el.getAttribute('role');\n if (role) return { selector: `${tag}[role=\"${role}\"]`, unique: false };\n\n const ariaLabel = el.getAttribute('aria-label');\n if (ariaLabel) {\n const safe = ariaLabel.slice(0, 50).replace(/\"/g, '\\\\\"');\n return { selector: `${tag}[aria-label=\"${safe}\"]`, unique: false };\n }\n\n if (tag === 'input' || tag === 'select' || tag === 'textarea') {\n const name = el.getAttribute('name');\n if (name) return { selector: `${tag}[name=\"${name}\"]`, unique: false };\n const type = el.getAttribute('type');\n if (type) return { selector: `${tag}[type=\"${type}\"]`, unique: false };\n }\n\n if (tag === 'a') {\n const href = el.getAttribute('href');\n if (href && !href.startsWith('javascript:')) {\n try {\n const path = new URL(href, location.origin).pathname.slice(0, 80);\n return { selector: `a[href=\"${path}\"]`, unique: false };\n } catch { /* invalid URL, skip */ }\n }\n }\n\n // 4. Tag + stable classes\n let selector = tag;\n if (el.className && typeof el.className === 'string') {\n const stable = el.className\n .trim()\n .split(/\\s+/)\n .filter((c) => c && !isDynamicClass(c))\n .slice(0, 2);\n if (stable.length > 0) selector += '.' + stable.join('.');\n }\n\n // 5. nth-of-type disambiguation\n const parent = el.parentElement;\n if (parent) {\n const siblings = Array.from(parent.children).filter(\n (s) => s.tagName === el.tagName\n );\n if (siblings.length > 1) {\n const index = siblings.indexOf(el) + 1;\n selector += `:nth-of-type(${index})`;\n }\n }\n\n return { selector, unique: false };\n}\n\nfunction isCanvasOnlyPage(): boolean {\n const body = document.body;\n if (!body) return false;\n const children = body.children;\n if (children.length > 3) return false;\n const nonCanvas = body.querySelectorAll(':scope > :not(canvas):not(script):not(style):not(link)');\n return nonCanvas.length === 0 && body.querySelectorAll('canvas').length >= 1;\n}\n\nfunction parseUtmParams(url: string): Record<string, string> {\n const UTM_KEYS = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content'] as const;\n const params = new URL(url).searchParams;\n const utm: Record<string, string> = {};\n for (const key of UTM_KEYS) {\n const value = params.get(key);\n if (value) utm[key] = value;\n }\n return utm;\n}\n\nexport function attachPageviewListener(cb: EventCallback): () => void {\n const trackPageview = () => {\n const utmParams = parseUtmParams(location.href);\n cb({\n type: 'pageview',\n url: location.href,\n referrer: document.referrer,\n title: document.title,\n ...(Object.keys(utmParams).length > 0 && { properties: utmParams }),\n });\n };\n\n // Monkey-patch history methods\n const origPushState = history.pushState.bind(history);\n const origReplaceState = history.replaceState.bind(history);\n\n history.pushState = function (...args) {\n origPushState(...args);\n trackPageview();\n };\n\n history.replaceState = function (...args) {\n origReplaceState(...args);\n trackPageview();\n };\n\n window.addEventListener('popstate', trackPageview);\n\n // Track initial pageview\n trackPageview();\n\n return () => {\n history.pushState = origPushState;\n history.replaceState = origReplaceState;\n window.removeEventListener('popstate', trackPageview);\n };\n}\n\nexport function attachClickListener(cb: EventCallback): () => void {\n const usePointer = typeof PointerEvent !== 'undefined';\n\n const handler = (e: PointerEvent | MouseEvent) => {\n // Only track primary button (left click / tap)\n if (e.button !== 0) return;\n\n let target = e.target as Element | null;\n if (!target) return;\n\n // Skip clicks on the Lumitra extension widget and bare html/body\n const tag = target.tagName.toLowerCase();\n if (tag === 'html' || tag === 'body') return;\n if ((target as HTMLElement).closest?.('#lumitra-widget-host, #lumitra-overlay-host, #lumitra-heatmap-container, #lumitra-heatmap-fixed')) return;\n\n const canvasOnly = isCanvasOnlyPage();\n\n // Always resolve to the deepest element at the click coordinates.\n // event.target can be a container if the click landed on padding/background.\n // elementFromPoint returns the topmost visible element at that exact pixel,\n // which is typically the deepest leaf in the DOM tree.\n if (!canvasOnly) {\n const deepest = document.elementFromPoint(e.clientX, e.clientY);\n if (deepest && deepest !== target) {\n target = deepest;\n }\n }\n\n const rect = target.getBoundingClientRect();\n const pointerType = 'pointerType' in e ? (e as PointerEvent).pointerType : undefined;\n\n cb({\n type: 'click',\n url: location.href,\n x: e.pageX,\n y: e.pageY,\n selector: canvasOnly ? '' : getStableSelector(target),\n ...(pointerType && { inputType: pointerType }),\n ...(rect.width > 0 && !canvasOnly && {\n properties: {\n ox: Math.round(e.clientX - rect.left),\n oy: Math.round(e.clientY - rect.top),\n ew: Math.round(rect.width),\n eh: Math.round(rect.height),\n ...(pointerType && { pt: pointerType }),\n },\n }),\n });\n };\n\n const eventName = usePointer ? 'pointerup' : 'click';\n document.addEventListener(eventName, handler as EventListener, { capture: true });\n return () => document.removeEventListener(eventName, handler as EventListener, { capture: true });\n}\n\nexport function attachScrollListener(cb: EventCallback): () => void {\n let maxDepth = 0;\n let ticking = false;\n\n const handler = () => {\n if (ticking) return;\n ticking = true;\n\n requestAnimationFrame(() => {\n const scrollTop = window.scrollY;\n const docHeight = document.documentElement.scrollHeight - window.innerHeight;\n const depth = docHeight > 0 ? Math.round((scrollTop / docHeight) * 100) : 0;\n\n if (depth > maxDepth) {\n maxDepth = depth;\n cb({\n type: 'scroll',\n url: location.href,\n scrollDepth: maxDepth,\n });\n }\n ticking = false;\n });\n };\n\n window.addEventListener('scroll', handler, { passive: true });\n\n return () => {\n window.removeEventListener('scroll', handler);\n };\n}\n","/**\n * MurmurHash3 (32-bit) — fast, deterministic, non-cryptographic hash.\n * Used for experiment variant assignment and feature flag rollout bucketing.\n * Zero dependencies, ~200 bytes minified.\n */\nexport function murmurhash3(key: string, seed: number = 0): number {\n let h = seed;\n for (let i = 0; i < key.length; i++) {\n const k = Math.imul(key.charCodeAt(i), 0xcc9e2d51);\n h ^= Math.imul(k << 15 | k >>> 17, 0x1b873593);\n h = Math.imul(h << 13 | h >>> 19, 5) + 0xe6546b64;\n }\n h ^= key.length;\n h ^= h >>> 16;\n h = Math.imul(h, 0x85ebca6b);\n h ^= h >>> 13;\n h = Math.imul(h, 0xc2b2ae35);\n h ^= h >>> 16;\n return h >>> 0;\n}\n","import { murmurhash3 } from './hash.js';\n\n// ── Types ────────────────────────────────────────────────────────────────────\n\nexport interface ExperimentVariant {\n key: string;\n weight: number;\n}\n\nexport interface ExperimentDefinition {\n id: string;\n key: string;\n variants: ExperimentVariant[];\n}\n\nexport interface FlagDefinition {\n key: string;\n enabled: boolean;\n rolloutPercentage: number;\n variants: ExperimentVariant[] | null;\n}\n\nexport interface RemoteConfig {\n config: Record<string, unknown>;\n experiments: ExperimentDefinition[];\n flags: FlagDefinition[];\n}\n\n// ── Helpers ──────────────────────────────────────────────────────────────────\n\nconst EXP_STORAGE_PREFIX = 'ap_exp_';\n\nfunction assignVariant(\n experimentKey: string,\n userId: string,\n variants: ExperimentVariant[],\n): string {\n const hash = murmurhash3(`${experimentKey}:${userId}`);\n const bucket = hash % 10000; // 0.01% granularity\n let cumulative = 0;\n for (const variant of variants) {\n cumulative += variant.weight * 100; // weight 50 -> 5000\n if (bucket < cumulative) return variant.key;\n }\n return variants[0]?.key ?? 'control';\n}\n\nfunction readStoredAssignment(key: string): string | null {\n try {\n return sessionStorage.getItem(`${EXP_STORAGE_PREFIX}${key}`);\n } catch {\n return null;\n }\n}\n\nfunction storeAssignment(key: string, variant: string): void {\n try {\n sessionStorage.setItem(`${EXP_STORAGE_PREFIX}${key}`, variant);\n } catch {\n // sessionStorage unavailable (e.g. incognito quota exceeded)\n }\n}\n\n// ── ExperimentManager ────────────────────────────────────────────────────────\n\nexport class ExperimentManager {\n private experiments: ExperimentDefinition[] = [];\n private flags: FlagDefinition[] = [];\n private identityId: string;\n private assignments: Map<string, string> = new Map();\n\n constructor(sessionId: string) {\n this.identityId = sessionId;\n }\n\n /** Load experiment & flag definitions from remote config. */\n setDefinitions(experiments: ExperimentDefinition[], flags: FlagDefinition[]): void {\n this.experiments = experiments;\n this.flags = flags;\n this.resolveAllAssignments();\n }\n\n /** Switch from session-based to user-based assignment. Re-resolves all variants. */\n identify(userId: string): void {\n if (userId === this.identityId) return;\n this.identityId = userId;\n this.assignments.clear();\n this.resolveAllAssignments();\n }\n\n /** Get the assigned variant for an experiment, or null if experiment not found. */\n getVariant(key: string): string | null {\n return this.assignments.get(key) ?? null;\n }\n\n /** Evaluate a feature flag. Returns false if flag not found or disabled. */\n getFlag(key: string): boolean {\n const flag = this.flags.find((f) => f.key === key);\n if (!flag || !flag.enabled) return false;\n\n // Deterministic rollout check\n if (flag.rolloutPercentage < 100) {\n const hash = murmurhash3(`${key}:${this.identityId}`);\n const bucket = hash % 10000;\n if (bucket >= flag.rolloutPercentage * 100) return false;\n }\n\n return true;\n }\n\n /** Return all active experiment assignments as { experimentId: variantKey }. */\n getActiveExperiments(): Record<string, string> {\n const active: Record<string, string> = {};\n for (const exp of this.experiments) {\n const variant = this.assignments.get(exp.key);\n if (variant) active[exp.id] = variant;\n }\n return active;\n }\n\n /** Return all assignments as { experimentKey: variantKey }. */\n getAllAssignments(): Record<string, string> {\n const result: Record<string, string> = {};\n for (const [key, variant] of this.assignments) {\n result[key] = variant;\n }\n return result;\n }\n\n /** Return all flag evaluations as { flagKey: boolean }. */\n getAllFlags(): Record<string, boolean> {\n const result: Record<string, boolean> = {};\n for (const flag of this.flags) {\n result[flag.key] = this.getFlag(flag.key);\n }\n return result;\n }\n\n /** Override the variant for an experiment. Persists to sessionStorage. */\n setVariant(key: string, variant: string): void {\n this.assignments.set(key, variant);\n storeAssignment(key, variant);\n }\n\n // ── Internal ───────────────────────────────────────────────────────────────\n\n private resolveAllAssignments(): void {\n for (const exp of this.experiments) {\n this.resolveAssignment(exp);\n }\n }\n\n private resolveAssignment(exp: ExperimentDefinition): void {\n // Check sessionStorage for a sticky assignment\n const stored = readStoredAssignment(exp.key);\n if (stored && exp.variants.some((v) => v.key === stored)) {\n this.assignments.set(exp.key, stored);\n return;\n }\n\n // Deterministic assignment\n const variant = assignVariant(exp.key, this.identityId, exp.variants);\n this.assignments.set(exp.key, variant);\n storeAssignment(exp.key, variant);\n }\n}\n","import type { TrackerEvent } from './constants';\nimport type { TrackerConfig } from './index.js';\n\n/** Internal config with coreOnly flag — not part of public API. */\ntype InternalConfig = TrackerConfig & { coreOnly?: boolean };\nimport { getOrCreateSession, touchSession } from './session.js';\nimport { EventBatcher } from './batch.js';\nimport { getDeviceType, getScreenDimensions } from './device.js';\nimport { attachPageviewListener, attachClickListener, attachScrollListener } from './listeners.js';\nimport { ExperimentManager } from './experiment.js';\nimport type { RemoteConfig } from './experiment.js';\n\nconst CONFIG_TIMEOUT_MS = 3000;\n\nexport class AnalyticsTracker {\n private config: Required<Pick<TrackerConfig, 'projectId' | 'endpoint'>> & InternalConfig;\n private sessionId: string;\n private batcher: EventBatcher;\n private cleanups: (() => void)[] = [];\n private experimentManager: ExperimentManager;\n private remoteConfig: RemoteConfig | null = null;\n private readyPromise: Promise<void>;\n private resolveReady!: () => void;\n private replayActive = false;\n\n private trackingEnabled = false;\n\n constructor(config: InternalConfig) {\n this.config = config;\n const { sessionId, isNew } = getOrCreateSession();\n this.sessionId = sessionId;\n\n this.experimentManager = new ExperimentManager(sessionId);\n\n this.readyPromise = new Promise<void>((resolve) => {\n this.resolveReady = resolve;\n });\n\n this.batcher = new EventBatcher(\n config.endpoint,\n config.apiKey,\n config.flushInterval,\n config.debug\n );\n\n // Fire session_start if new\n if (isNew) {\n this.track({\n type: 'session_start',\n url: location.href,\n properties: {\n maxTouchPoints: navigator.maxTouchPoints ?? 0,\n pointerType: window.matchMedia('(pointer: coarse)').matches ? 'coarse' : 'fine',\n dpr: window.devicePixelRatio ?? 1,\n viewportWidth: window.innerWidth,\n viewportHeight: window.innerHeight,\n },\n });\n }\n\n // Pageview listener (always on — no consent needed for aggregate pageviews)\n this.cleanups.push(\n attachPageviewListener((e) => this.track(e))\n );\n\n // Click, scroll, and replay require consent — only attach via enableTracking()\n if (!config.coreOnly) {\n this.attachBehavioralListeners();\n }\n\n // Set initial global (experiments not yet loaded)\n this.updateGlobal();\n\n // Non-blocking remote config fetch (flags + experiments — technically necessary)\n this.fetchRemoteConfig();\n }\n\n /**\n * Enable behavioral tracking (clicks, scroll, heatmaps) after user consent.\n * Call this from your cookie consent callback.\n * Safe to call multiple times — only attaches listeners once.\n */\n enableTracking(): void {\n if (this.trackingEnabled) return;\n this.attachBehavioralListeners();\n if (this.config.debug) console.log('[analytics] behavioral tracking enabled (post-consent)');\n }\n\n /** Whether behavioral tracking (clicks, scroll) is active. */\n get isTrackingEnabled(): boolean {\n return this.trackingEnabled;\n }\n\n private attachBehavioralListeners(): void {\n if (this.trackingEnabled) return;\n this.trackingEnabled = true;\n\n if (this.config.heatmap !== false) {\n this.cleanups.push(\n attachClickListener((e) => this.track(e))\n );\n }\n\n if (this.config.scrollDepth !== false) {\n this.cleanups.push(\n attachScrollListener((e) => this.track(e))\n );\n }\n }\n\n /** Resolves when remote config has been fetched (or after timeout / error). */\n ready(): Promise<void> {\n return this.readyPromise;\n }\n\n /** Get the assigned variant for an experiment, or null if not found. */\n getVariant(key: string): string | null {\n return this.experimentManager.getVariant(key);\n }\n\n /** Override the variant for an experiment and notify listeners. */\n setVariant(key: string, variant: string): void {\n this.experimentManager.setVariant(key, variant);\n this.updateGlobal();\n if (typeof window !== 'undefined') {\n window.dispatchEvent(\n new CustomEvent('lumitra:variant-changed', {\n detail: { key, variant },\n }),\n );\n }\n }\n\n /** Evaluate a feature flag. Returns false if not found or disabled. */\n getFlag(key: string): boolean {\n return this.experimentManager.getFlag(key);\n }\n\n /** Switch from session-based to user-based experiment assignment. */\n identify(userId: string): void {\n this.experimentManager.identify(userId);\n this.updateGlobal();\n }\n\n /** Get the project ID this tracker was initialized with. */\n get projectId(): string {\n return this.config.projectId;\n }\n\n /**\n * Enable session replay after user consent.\n * Call this from your cookie consent callback.\n */\n enableReplay(): void {\n if (this.replayActive) return;\n this.replayActive = true;\n import('./replay.js')\n .then((mod) => mod.initReplay(this, this.config.replayPrivacy))\n .catch(() => {\n this.replayActive = false;\n if (this.config.debug) console.warn('[analytics] rrweb not available, replay disabled');\n });\n }\n\n /**\n * Disable session replay (e.g. user revoked consent).\n */\n disableReplay(): void {\n if (!this.replayActive) return;\n this.replayActive = false;\n import('./replay.js')\n .then((mod) => mod.stopReplay())\n .catch(() => {});\n }\n\n /** Whether session replay is currently active. */\n get isReplayActive(): boolean {\n return this.replayActive;\n }\n\n track(partial: Omit<TrackerEvent, 'projectId' | 'sessionId' | 'timestamp'>): void {\n touchSession();\n const { screenWidth, screenHeight } = getScreenDimensions();\n\n // Attach experiment context\n const activeExperiments = this.experimentManager.getActiveExperiments();\n const experimentIds = Object.keys(activeExperiments);\n\n let experimentId: string | undefined;\n let variant: string | undefined;\n let properties = partial.properties;\n\n if (experimentIds.length > 0) {\n // Set top-level fields from the first active experiment\n const firstId = experimentIds[0]!;\n experimentId = firstId;\n variant = activeExperiments[firstId];\n\n // If multiple experiments, attach all as _experiments property\n if (experimentIds.length > 1) {\n properties = { ...properties, _experiments: activeExperiments };\n }\n }\n\n const event: TrackerEvent = {\n ...partial,\n projectId: this.config.projectId,\n sessionId: this.sessionId,\n timestamp: Date.now(),\n screenWidth,\n screenHeight,\n deviceType: getDeviceType(),\n userAgent: navigator.userAgent,\n ...(experimentId && { experimentId }),\n ...(variant && { variant }),\n ...(properties && { properties }),\n };\n this.batcher.add(event);\n }\n\n destroy(): void {\n for (const cleanup of this.cleanups) cleanup();\n this.cleanups = [];\n this.batcher.destroy();\n this.cleanupGlobal();\n }\n\n /** Remove the window.__lumitra global. */\n cleanupGlobal(): void {\n if (typeof window !== 'undefined') {\n delete (window as unknown as Record<string, unknown>).__lumitra;\n }\n }\n\n // ── Internal ───────────────────────────────────────────────────────────────\n\n private updateGlobal(): void {\n if (typeof window === 'undefined') return;\n (window as unknown as Record<string, unknown>).__lumitra = {\n projectId: this.config.projectId,\n experiments: this.experimentManager.getAllAssignments(),\n flags: this.experimentManager.getAllFlags(),\n ready: this.readyPromise,\n };\n }\n\n private fetchRemoteConfig(): void {\n const baseUrl = this.config.endpoint.replace(/\\/api\\/collect\\/?$/, '');\n const url = `${baseUrl}/api/projects/${this.config.projectId}/config`;\n\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), CONFIG_TIMEOUT_MS);\n\n fetch(url, {\n method: 'GET',\n signal: controller.signal,\n credentials: 'omit',\n })\n .then((res) => {\n if (!res.ok) throw new Error(`Config fetch failed: ${res.status}`);\n return res.json() as Promise<RemoteConfig>;\n })\n .then((data) => {\n this.remoteConfig = data;\n this.experimentManager.setDefinitions(\n data.experiments ?? [],\n data.flags ?? [],\n );\n this.updateGlobal();\n if (typeof window !== 'undefined') {\n window.dispatchEvent(new CustomEvent('lumitra:ready', {\n detail: {\n projectId: this.config.projectId,\n experiments: this.experimentManager.getAllAssignments(),\n flags: this.experimentManager.getAllFlags(),\n },\n }));\n }\n if (this.config.debug) {\n console.log('[analytics] remote config loaded:', data);\n }\n })\n .catch((err) => {\n if (this.config.debug) {\n console.warn('[analytics] remote config fetch failed, using defaults:', err);\n }\n })\n .finally(() => {\n clearTimeout(timeout);\n this.resolveReady();\n });\n }\n}\n","import type { TrackerEvent } from './constants';\nimport { AnalyticsTracker } from './tracker.js';\n\nexport interface ReplayPrivacy {\n /** Mask all input field values in replay. Default: true. */\n maskAllInputs?: boolean;\n /** Mask all text content in replay. Default: false. */\n maskAllText?: boolean;\n /** CSS selector for elements to block entirely from replay. */\n blockSelector?: string;\n /** CSS selector for elements whose text should be masked. */\n maskTextSelector?: string;\n}\n\nexport interface TrackerConfig {\n /** Project ID (UUID). */\n projectId: string;\n /** Ingestion endpoint URL. */\n endpoint: string;\n /** API key (ap_live_... or ap_test_...). */\n apiKey: string;\n /** Enable click heatmap tracking. Default: true. Requires enableTracking(). */\n heatmap?: boolean;\n /** Enable scroll depth tracking. Default: true. Requires enableTracking(). */\n scrollDepth?: boolean;\n /** Batch flush interval in ms. Default: 5000. */\n flushInterval?: number;\n /** Debug mode — logs events to console. Default: false. */\n debug?: boolean;\n /** Privacy options for session replay. */\n replayPrivacy?: ReplayPrivacy;\n}\n\nlet instance: AnalyticsTracker | null = null;\n\n/**\n * Initialize the analytics tracker. Creates a singleton.\n *\n * Tracks: pageviews, sessions, visitors (aggregate). No consent required.\n * Loads: feature flags + A/B experiment assignments (technically necessary).\n *\n * Does NOT track clicks, scroll, or replay — call enableTracking() after consent.\n */\nexport function init(config: TrackerConfig): AnalyticsTracker {\n if (instance) {\n if (config.debug) console.warn('[analytics] already initialized, returning existing instance');\n return instance;\n }\n\n instance = new AnalyticsTracker({ ...config, coreOnly: true });\n return instance;\n}\n\n/**\n * Enable behavioral tracking (clicks, scroll, heatmaps) after user consent.\n * Safe to call multiple times — only attaches listeners once.\n */\nexport function enableTracking(): void {\n instance?.enableTracking();\n}\n\n/**\n * Get the current tracker instance, or null if not initialized.\n */\nexport function getTracker(): AnalyticsTracker | null {\n return instance;\n}\n\n/**\n * Destroy the tracker and clean up listeners.\n */\nexport function destroy(): void {\n instance?.destroy();\n instance = null;\n}\n\n/**\n * Enable replay on the current tracker instance (call after cookie consent).\n */\nexport function enableReplay(): void {\n instance?.enableReplay();\n}\n\n/**\n * Disable replay on the current tracker instance (call if user revokes consent).\n */\nexport function disableReplay(): void {\n instance?.disableReplay();\n}\n\nexport type { TrackerEvent, TrackerConfig as AnalyticsConfig };\nexport { AnalyticsTracker };\nexport { ExperimentManager } from './experiment.js';\nexport type { ExperimentDefinition, FlagDefinition, RemoteConfig } from './experiment.js';\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/session.ts","../src/compress.ts","../src/batch.ts","../src/device.ts","../src/hash.ts","../src/dom-hash.ts","../src/listeners.ts","../src/experiment.ts","../src/tracker.ts","../src/index.ts"],"names":["SESSION_KEY","LAST_ACTIVITY_KEY","getOrCreateSession","now","stored","lastActivity","sessionId","touchSession","compressPayload","json","stream","MAX_RETRIES","BASE_DELAY_MS","EventBatcher","endpoint","apiKey","flushInterval","debug","__publicField","event","useBeacon","batch","body","blob","payload","compressed","headers","attempt","res","delay","r","getDeviceType","ua","getScreenDimensions","murmurhash3","key","seed","h","k","cachedHash","SKIP_ID_RE","STRUCTURAL_ATTRS","countVisibleChildren","el","count","i","child","computePageHash","parts","walker","node","next","tag","childCount","attrs","attr","val","skeleton","getCachedPageHash","clearPageHashCache","DYNAMIC_CLASS_RE","AUTO_ID_RE","DATA_ATTRS","isDynamicClass","cls","re","getStableSelector","current","segment","buildSegment","role","ariaLabel","safe","name","type","href","selector","stable","c","parent","siblings","s","index","isCanvasOnlyPage","parseUtmParams","url","UTM_KEYS","params","utm","value","attachPageviewListener","cb","trackPageview","utmParams","origPushState","origReplaceState","args","onPopState","attachClickListener","usePointer","handler","e","target","canvasOnly","deepest","rect","pointerType","eventName","attachScrollListener","maxDepth","ticking","scrollTop","docHeight","depth","EXP_STORAGE_PREFIX","assignVariant","experimentKey","userId","variants","bucket","cumulative","variant","readStoredAssignment","storeAssignment","ExperimentManager","experiments","flags","flag","f","active","exp","result","v","CONFIG_TIMEOUT_MS","AnalyticsTracker","config","isNew","resolve","mod","partial","screenWidth","screenHeight","activeExperiments","experimentIds","experimentId","properties","firstId","pageHash","cleanup","controller","timeout","data","err","instance","init","enableTracking","getTracker","destroy","enableReplay","disableReplay"],"mappings":"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","file":"index.js","sourcesContent":["import { SESSION_TIMEOUT_MS } from './constants';\n\nconst SESSION_KEY = 'ap_session_id';\nconst LAST_ACTIVITY_KEY = 'ap_last_activity';\n\nexport function getOrCreateSession(): { sessionId: string; isNew: boolean } {\n const now = Date.now();\n const stored = sessionStorage.getItem(SESSION_KEY);\n const lastActivity = Number(sessionStorage.getItem(LAST_ACTIVITY_KEY) || '0');\n\n // Existing session that hasn't timed out\n if (stored && now - lastActivity < SESSION_TIMEOUT_MS) {\n sessionStorage.setItem(LAST_ACTIVITY_KEY, String(now));\n return { sessionId: stored, isNew: false };\n }\n\n // New session\n const sessionId = crypto.randomUUID();\n sessionStorage.setItem(SESSION_KEY, sessionId);\n sessionStorage.setItem(LAST_ACTIVITY_KEY, String(now));\n return { sessionId, isNew: true };\n}\n\nexport function touchSession(): void {\n sessionStorage.setItem(LAST_ACTIVITY_KEY, String(Date.now()));\n}\n\nexport function getSessionId(): string | null {\n return sessionStorage.getItem(SESSION_KEY);\n}\n","import { COMPRESS_THRESHOLD_BYTES } from './constants';\n\nexport interface CompressedPayload {\n body: BodyInit;\n compressed: boolean;\n}\n\n/**\n * Compress a JSON string with gzip if the browser supports CompressionStream\n * and the payload exceeds the threshold. Falls back to uncompressed otherwise.\n */\nexport async function compressPayload(json: string): Promise<CompressedPayload> {\n if (\n json.length < COMPRESS_THRESHOLD_BYTES ||\n typeof CompressionStream === 'undefined'\n ) {\n return { body: json, compressed: false };\n }\n\n try {\n const stream = new Blob([json])\n .stream()\n .pipeThrough(new CompressionStream('gzip'));\n\n const compressed = await new Response(stream).blob();\n return { body: compressed, compressed: true };\n } catch {\n // Compression failed — send uncompressed\n return { body: json, compressed: false };\n }\n}\n","import type { TrackerEvent } from './constants';\nimport { FLUSH_INTERVAL_MS, MAX_BATCH_SIZE, BEACON_MAX_BYTES } from './constants';\nimport { compressPayload } from './compress.js';\n\nconst MAX_RETRIES = 3;\nconst BASE_DELAY_MS = 1000;\n\nexport class EventBatcher {\n private queue: TrackerEvent[] = [];\n private timer: ReturnType<typeof setInterval> | null = null;\n private endpoint: string;\n private apiKey: string;\n private debug: boolean;\n\n constructor(endpoint: string, apiKey: string, flushInterval?: number, debug = false) {\n this.endpoint = endpoint;\n this.apiKey = apiKey;\n this.debug = debug;\n this.timer = setInterval(() => this.flush(), flushInterval ?? FLUSH_INTERVAL_MS);\n\n if (typeof window !== 'undefined') {\n window.addEventListener('visibilitychange', () => {\n if (document.visibilityState === 'hidden') this.flush(true);\n });\n window.addEventListener('pagehide', () => this.flush(true));\n }\n }\n\n add(event: TrackerEvent): void {\n this.queue.push(event);\n if (this.debug) console.log('[analytics] queued:', event.type, event);\n if (this.queue.length >= MAX_BATCH_SIZE) this.flush();\n }\n\n async flush(useBeacon = false): Promise<void> {\n if (this.queue.length === 0) return;\n\n const batch = this.queue.splice(0, MAX_BATCH_SIZE);\n const body = JSON.stringify(batch);\n\n // Only use beacon for small payloads — large ones (replay chunks) need\n // compression which requires custom headers that beacon doesn't support\n if (useBeacon && body.length <= BEACON_MAX_BYTES && typeof navigator !== 'undefined' && navigator.sendBeacon) {\n const blob = new Blob([body], { type: 'application/json' });\n const sent = navigator.sendBeacon(this.endpoint, blob);\n if (sent) {\n if (this.debug) console.log('[analytics] beacon sent:', batch.length, 'events');\n return;\n }\n // Beacon failed, fall through to fetch\n }\n\n await this.fetchWithRetry(body, batch);\n }\n\n private async fetchWithRetry(body: string, batch: TrackerEvent[]): Promise<void> {\n // Compress large payloads (replay FullSnapshots with inlined CSS can be 2-5MB)\n const { body: payload, compressed } = await compressPayload(body);\n\n const headers: Record<string, string> = {\n 'X-API-Key': this.apiKey,\n };\n\n if (compressed) {\n headers['Content-Type'] = 'application/json';\n headers['Content-Encoding'] = 'gzip';\n } else {\n headers['Content-Type'] = 'application/json';\n }\n\n for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {\n try {\n const res = await fetch(this.endpoint, {\n method: 'POST',\n headers,\n body: payload,\n keepalive: !compressed, // keepalive has 64KB limit, skip for compressed blobs\n credentials: 'omit',\n });\n\n if (res.ok) {\n if (this.debug) console.log('[analytics] flushed:', batch.length, 'events', compressed ? '(gzip)' : '');\n return;\n }\n\n // Don't retry 4xx errors\n if (res.status >= 400 && res.status < 500) {\n if (this.debug) console.warn('[analytics] flush rejected:', res.status);\n return;\n }\n } catch {\n // Network error, retry\n }\n\n if (attempt < MAX_RETRIES) {\n const delay = BASE_DELAY_MS * Math.pow(2, attempt);\n await new Promise((r) => setTimeout(r, delay));\n }\n }\n\n if (this.debug) console.warn('[analytics] flush failed after retries, dropping', batch.length, 'events');\n }\n\n destroy(): void {\n if (this.timer) {\n clearInterval(this.timer);\n this.timer = null;\n }\n this.flush(true);\n }\n\n get pending(): number {\n return this.queue.length;\n }\n}\n","import type { DeviceType } from './constants';\n\nexport function getDeviceType(): DeviceType {\n const ua = navigator.userAgent;\n\n // Signal 1: Client Hints API (Chromium only, most reliable)\n if ('userAgentData' in navigator) {\n const uad = (navigator as any).userAgentData;\n if (uad?.mobile === true) {\n return /Mobile/.test(ua) ? 'mobile' : 'tablet';\n }\n }\n\n // Signal 2: iPad detection (iPadOS 13+ reports as Mac)\n if (/Macintosh/.test(ua) && navigator.maxTouchPoints > 1) {\n return 'tablet';\n }\n\n // Signal 3: iOS devices (iPhone/iPod)\n if (/iPhone|iPod/.test(ua)) return 'mobile';\n\n // Signal 4: Android devices\n if (/Android/.test(ua)) {\n return /Mobile/.test(ua) ? 'mobile' : 'tablet';\n }\n\n // Signal 5: Windows/Mac/Linux/CrOS -> desktop\n return 'desktop';\n}\n\nexport function getScreenDimensions() {\n return {\n screenWidth: window.screen.width,\n screenHeight: window.screen.height,\n };\n}\n","/**\n * MurmurHash3 (32-bit) — fast, deterministic, non-cryptographic hash.\n * Used for experiment variant assignment and feature flag rollout bucketing.\n * Zero dependencies, ~200 bytes minified.\n */\nexport function murmurhash3(key: string, seed: number = 0): number {\n let h = seed;\n for (let i = 0; i < key.length; i++) {\n const k = Math.imul(key.charCodeAt(i), 0xcc9e2d51);\n h ^= Math.imul(k << 15 | k >>> 17, 0x1b873593);\n h = Math.imul(h << 13 | h >>> 19, 5) + 0xe6546b64;\n }\n h ^= key.length;\n h ^= h >>> 16;\n h = Math.imul(h, 0x85ebca6b);\n h ^= h >>> 13;\n h = Math.imul(h, 0xc2b2ae35);\n h ^= h >>> 16;\n return h >>> 0;\n}\n","import { murmurhash3 } from './hash';\n\nlet cachedHash = '';\n\n/** IDs of elements injected by analytics/rrweb tooling — skip these in hashing */\nconst SKIP_ID_RE = /^(__analytics|lumitra|rrweb)/;\n\n/** Attributes that contribute to structural identity */\nconst STRUCTURAL_ATTRS = ['id', 'class', 'role', 'data-testid', 'data-analytics', 'data-id', 'type', 'name'];\n\n/** Count direct children excluding skipped elements */\nfunction countVisibleChildren(el: Element): number {\n let count = 0;\n for (let i = 0; i < el.children.length; i++) {\n const child = el.children[i]!;\n if (!child.id || !SKIP_ID_RE.test(child.id)) count++;\n }\n return count;\n}\n\nexport function computePageHash(): string {\n if (typeof document === 'undefined') return '';\n\n const parts: string[] = [];\n const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_ELEMENT);\n\n let node: Node | null = walker.currentNode;\n while (node) {\n const el = node as Element;\n\n // Skip analytics/rrweb injected nodes and their subtrees\n if (el.id && SKIP_ID_RE.test(el.id)) {\n // Move to next sibling, walking up ancestors if needed, to skip the subtree\n let next: Node | null = walker.nextSibling();\n while (!next && walker.parentNode()) {\n next = walker.nextSibling();\n }\n node = next;\n continue;\n }\n\n const tag = el.tagName;\n const childCount = countVisibleChildren(el);\n\n // Collect stable attributes (sorted for determinism)\n const attrs: string[] = [];\n for (const attr of STRUCTURAL_ATTRS) {\n const val = el.getAttribute(attr);\n if (val) attrs.push(`${attr}=${val}`);\n }\n\n parts.push(`${tag}:${childCount}${attrs.length ? ':' + attrs.join(',') : ''}`);\n\n node = walker.nextNode();\n }\n\n const skeleton = parts.join('|');\n cachedHash = (murmurhash3(skeleton) >>> 0).toString(16).padStart(8, '0');\n return cachedHash;\n}\n\nexport function getCachedPageHash(): string {\n return cachedHash;\n}\n\nexport function clearPageHashCache(): void {\n cachedHash = '';\n}\n","import type { TrackerEvent } from './constants';\nimport { computePageHash, clearPageHashCache } from './dom-hash';\n\ntype EventCallback = (event: Omit<TrackerEvent, 'projectId' | 'sessionId' | 'timestamp'>) => void;\n\n// ── Dynamic class patterns (CSS Modules, styled-components, Emotion) ─────────\nconst DYNAMIC_CLASS_RE = [\n /^[\\w-]+_[\\w-]+__[a-zA-Z0-9]{5,}$/, // CSS Modules\n /^sc-[a-zA-Z]{5,}$/, // styled-components\n /^css-[a-zA-Z0-9]+$/, // Emotion\n /^e[a-z0-9]{6,}$/, // Emotion (short)\n];\n\n// Auto-generated IDs to skip\nconst AUTO_ID_RE = /[-_][0-9a-f]{4,}$|^:r\\d|^react-|^ember|^__next|^radix-/i;\n\n// Stable data attributes (priority order)\nconst DATA_ATTRS = ['data-testid', 'data-analytics', 'data-id'] as const;\n\nfunction isDynamicClass(cls: string): boolean {\n return DYNAMIC_CLASS_RE.some((re) => re.test(cls));\n}\n\nfunction getStableSelector(el: Element): string {\n const parts: string[] = [];\n let current: Element | null = el;\n\n while (current && current !== document.body && parts.length < 4) {\n const segment = buildSegment(current);\n parts.unshift(segment.selector);\n if (segment.unique) break;\n current = current.parentElement;\n }\n\n return parts.join(' > ').slice(0, 256);\n}\n\nfunction buildSegment(el: Element): { selector: string; unique: boolean } {\n const tag = el.tagName.toLowerCase();\n\n // 1. Data attributes (most stable)\n for (const attr of DATA_ATTRS) {\n const val = el.getAttribute(attr);\n if (val) return { selector: `${tag}[${attr}=\"${val}\"]`, unique: true };\n }\n\n // 2. Element id (skip auto-generated)\n if (el.id && !AUTO_ID_RE.test(el.id)) {\n return { selector: `${tag}#${el.id}`, unique: true };\n }\n\n // 3. Semantic attributes\n const role = el.getAttribute('role');\n if (role) return { selector: `${tag}[role=\"${role}\"]`, unique: false };\n\n const ariaLabel = el.getAttribute('aria-label');\n if (ariaLabel) {\n const safe = ariaLabel.slice(0, 50).replace(/\"/g, '\\\\\"');\n return { selector: `${tag}[aria-label=\"${safe}\"]`, unique: false };\n }\n\n if (tag === 'input' || tag === 'select' || tag === 'textarea') {\n const name = el.getAttribute('name');\n if (name) return { selector: `${tag}[name=\"${name}\"]`, unique: false };\n const type = el.getAttribute('type');\n if (type) return { selector: `${tag}[type=\"${type}\"]`, unique: false };\n }\n\n if (tag === 'a') {\n const href = el.getAttribute('href');\n if (href && !href.startsWith('javascript:')) {\n try {\n const path = new URL(href, location.origin).pathname.slice(0, 80);\n return { selector: `a[href=\"${path}\"]`, unique: false };\n } catch { /* invalid URL, skip */ }\n }\n }\n\n // 4. Tag + stable classes\n let selector = tag;\n if (el.className && typeof el.className === 'string') {\n const stable = el.className\n .trim()\n .split(/\\s+/)\n .filter((c) => c && !isDynamicClass(c))\n .slice(0, 2);\n if (stable.length > 0) selector += '.' + stable.join('.');\n }\n\n // 5. nth-of-type disambiguation\n const parent = el.parentElement;\n if (parent) {\n const siblings = Array.from(parent.children).filter(\n (s) => s.tagName === el.tagName\n );\n if (siblings.length > 1) {\n const index = siblings.indexOf(el) + 1;\n selector += `:nth-of-type(${index})`;\n }\n }\n\n return { selector, unique: false };\n}\n\nfunction isCanvasOnlyPage(): boolean {\n const body = document.body;\n if (!body) return false;\n const children = body.children;\n if (children.length > 3) return false;\n const nonCanvas = body.querySelectorAll(':scope > :not(canvas):not(script):not(style):not(link)');\n return nonCanvas.length === 0 && body.querySelectorAll('canvas').length >= 1;\n}\n\nfunction parseUtmParams(url: string): Record<string, string> {\n const UTM_KEYS = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content'] as const;\n const params = new URL(url).searchParams;\n const utm: Record<string, string> = {};\n for (const key of UTM_KEYS) {\n const value = params.get(key);\n if (value) utm[key] = value;\n }\n return utm;\n}\n\nexport function attachPageviewListener(cb: EventCallback): () => void {\n const trackPageview = () => {\n const utmParams = parseUtmParams(location.href);\n cb({\n type: 'pageview',\n url: location.href,\n referrer: document.referrer,\n title: document.title,\n ...(Object.keys(utmParams).length > 0 && { properties: utmParams }),\n });\n };\n\n // Monkey-patch history methods\n const origPushState = history.pushState.bind(history);\n const origReplaceState = history.replaceState.bind(history);\n\n history.pushState = function (...args) {\n origPushState(...args);\n trackPageview();\n clearPageHashCache();\n requestAnimationFrame(() => computePageHash());\n };\n\n history.replaceState = function (...args) {\n origReplaceState(...args);\n trackPageview();\n clearPageHashCache();\n requestAnimationFrame(() => computePageHash());\n };\n\n const onPopState = () => {\n trackPageview();\n clearPageHashCache();\n requestAnimationFrame(() => computePageHash());\n };\n\n window.addEventListener('popstate', onPopState);\n\n // Track initial pageview\n trackPageview();\n requestAnimationFrame(() => computePageHash());\n\n return () => {\n history.pushState = origPushState;\n history.replaceState = origReplaceState;\n window.removeEventListener('popstate', onPopState);\n };\n}\n\nexport function attachClickListener(cb: EventCallback): () => void {\n const usePointer = typeof PointerEvent !== 'undefined';\n\n const handler = (e: PointerEvent | MouseEvent) => {\n // Only track primary button (left click / tap)\n if (e.button !== 0) return;\n\n let target = e.target as Element | null;\n if (!target) return;\n\n // Skip clicks on the Lumitra extension widget and bare html/body\n const tag = target.tagName.toLowerCase();\n if (tag === 'html' || tag === 'body') return;\n if ((target as HTMLElement).closest?.('#lumitra-widget-host, #lumitra-overlay-host, #lumitra-heatmap-container, #lumitra-heatmap-fixed')) return;\n\n const canvasOnly = isCanvasOnlyPage();\n\n // Always resolve to the deepest element at the click coordinates.\n // event.target can be a container if the click landed on padding/background.\n // elementFromPoint returns the topmost visible element at that exact pixel,\n // which is typically the deepest leaf in the DOM tree.\n if (!canvasOnly) {\n const deepest = document.elementFromPoint(e.clientX, e.clientY);\n if (deepest && deepest !== target) {\n target = deepest;\n }\n }\n\n const rect = target.getBoundingClientRect();\n const pointerType = 'pointerType' in e ? (e as PointerEvent).pointerType : undefined;\n\n cb({\n type: 'click',\n url: location.href,\n x: e.pageX,\n y: e.pageY,\n selector: canvasOnly ? '' : getStableSelector(target),\n ...(pointerType && { inputType: pointerType }),\n ...(rect.width > 0 && !canvasOnly && {\n properties: {\n ox: Math.round(e.clientX - rect.left),\n oy: Math.round(e.clientY - rect.top),\n ew: Math.round(rect.width),\n eh: Math.round(rect.height),\n ...(pointerType && { pt: pointerType }),\n },\n }),\n });\n };\n\n const eventName = usePointer ? 'pointerup' : 'click';\n document.addEventListener(eventName, handler as EventListener, { capture: true });\n return () => document.removeEventListener(eventName, handler as EventListener, { capture: true });\n}\n\nexport function attachScrollListener(cb: EventCallback): () => void {\n let maxDepth = 0;\n let ticking = false;\n\n const handler = () => {\n if (ticking) return;\n ticking = true;\n\n requestAnimationFrame(() => {\n const scrollTop = window.scrollY;\n const docHeight = document.documentElement.scrollHeight - window.innerHeight;\n const depth = docHeight > 0 ? Math.round((scrollTop / docHeight) * 100) : 0;\n\n if (depth > maxDepth) {\n maxDepth = depth;\n cb({\n type: 'scroll',\n url: location.href,\n scrollDepth: maxDepth,\n });\n }\n ticking = false;\n });\n };\n\n window.addEventListener('scroll', handler, { passive: true });\n\n return () => {\n window.removeEventListener('scroll', handler);\n };\n}\n","import { murmurhash3 } from './hash.js';\n\n// ── Types ────────────────────────────────────────────────────────────────────\n\nexport interface ExperimentVariant {\n key: string;\n weight: number;\n}\n\nexport interface ExperimentDefinition {\n id: string;\n key: string;\n variants: ExperimentVariant[];\n}\n\nexport interface FlagDefinition {\n key: string;\n enabled: boolean;\n rolloutPercentage: number;\n variants: ExperimentVariant[] | null;\n}\n\nexport interface RemoteConfig {\n config: Record<string, unknown>;\n experiments: ExperimentDefinition[];\n flags: FlagDefinition[];\n}\n\n// ── Helpers ──────────────────────────────────────────────────────────────────\n\nconst EXP_STORAGE_PREFIX = 'ap_exp_';\n\nfunction assignVariant(\n experimentKey: string,\n userId: string,\n variants: ExperimentVariant[],\n): string {\n const hash = murmurhash3(`${experimentKey}:${userId}`);\n const bucket = hash % 10000; // 0.01% granularity\n let cumulative = 0;\n for (const variant of variants) {\n cumulative += variant.weight * 100; // weight 50 -> 5000\n if (bucket < cumulative) return variant.key;\n }\n return variants[0]?.key ?? 'control';\n}\n\nfunction readStoredAssignment(key: string): string | null {\n try {\n return sessionStorage.getItem(`${EXP_STORAGE_PREFIX}${key}`);\n } catch {\n return null;\n }\n}\n\nfunction storeAssignment(key: string, variant: string): void {\n try {\n sessionStorage.setItem(`${EXP_STORAGE_PREFIX}${key}`, variant);\n } catch {\n // sessionStorage unavailable (e.g. incognito quota exceeded)\n }\n}\n\n// ── ExperimentManager ────────────────────────────────────────────────────────\n\nexport class ExperimentManager {\n private experiments: ExperimentDefinition[] = [];\n private flags: FlagDefinition[] = [];\n private identityId: string;\n private assignments: Map<string, string> = new Map();\n\n constructor(sessionId: string) {\n this.identityId = sessionId;\n }\n\n /** Load experiment & flag definitions from remote config. */\n setDefinitions(experiments: ExperimentDefinition[], flags: FlagDefinition[]): void {\n this.experiments = experiments;\n this.flags = flags;\n this.resolveAllAssignments();\n }\n\n /** Switch from session-based to user-based assignment. Re-resolves all variants. */\n identify(userId: string): void {\n if (userId === this.identityId) return;\n this.identityId = userId;\n this.assignments.clear();\n this.resolveAllAssignments();\n }\n\n /** Get the assigned variant for an experiment, or null if experiment not found. */\n getVariant(key: string): string | null {\n return this.assignments.get(key) ?? null;\n }\n\n /** Evaluate a feature flag. Returns false if flag not found or disabled. */\n getFlag(key: string): boolean {\n const flag = this.flags.find((f) => f.key === key);\n if (!flag || !flag.enabled) return false;\n\n // Deterministic rollout check\n if (flag.rolloutPercentage < 100) {\n const hash = murmurhash3(`${key}:${this.identityId}`);\n const bucket = hash % 10000;\n if (bucket >= flag.rolloutPercentage * 100) return false;\n }\n\n return true;\n }\n\n /** Return all active experiment assignments as { experimentId: variantKey }. */\n getActiveExperiments(): Record<string, string> {\n const active: Record<string, string> = {};\n for (const exp of this.experiments) {\n const variant = this.assignments.get(exp.key);\n if (variant) active[exp.id] = variant;\n }\n return active;\n }\n\n /** Return all assignments as { experimentKey: variantKey }. */\n getAllAssignments(): Record<string, string> {\n const result: Record<string, string> = {};\n for (const [key, variant] of this.assignments) {\n result[key] = variant;\n }\n return result;\n }\n\n /** Return all flag evaluations as { flagKey: boolean }. */\n getAllFlags(): Record<string, boolean> {\n const result: Record<string, boolean> = {};\n for (const flag of this.flags) {\n result[flag.key] = this.getFlag(flag.key);\n }\n return result;\n }\n\n /** Override the variant for an experiment. Persists to sessionStorage. */\n setVariant(key: string, variant: string): void {\n this.assignments.set(key, variant);\n storeAssignment(key, variant);\n }\n\n // ── Internal ───────────────────────────────────────────────────────────────\n\n private resolveAllAssignments(): void {\n for (const exp of this.experiments) {\n this.resolveAssignment(exp);\n }\n }\n\n private resolveAssignment(exp: ExperimentDefinition): void {\n // Check sessionStorage for a sticky assignment\n const stored = readStoredAssignment(exp.key);\n if (stored && exp.variants.some((v) => v.key === stored)) {\n this.assignments.set(exp.key, stored);\n return;\n }\n\n // Deterministic assignment\n const variant = assignVariant(exp.key, this.identityId, exp.variants);\n this.assignments.set(exp.key, variant);\n storeAssignment(exp.key, variant);\n }\n}\n","import type { TrackerEvent } from './constants';\nimport type { TrackerConfig } from './index.js';\n\n/** Internal config with coreOnly flag — not part of public API. */\ntype InternalConfig = TrackerConfig & { coreOnly?: boolean };\nimport { getOrCreateSession, touchSession } from './session.js';\nimport { EventBatcher } from './batch.js';\nimport { getDeviceType, getScreenDimensions } from './device.js';\nimport { attachPageviewListener, attachClickListener, attachScrollListener } from './listeners.js';\nimport { getCachedPageHash } from './dom-hash.js';\nimport { ExperimentManager } from './experiment.js';\nimport type { RemoteConfig } from './experiment.js';\n\nconst CONFIG_TIMEOUT_MS = 3000;\n\nexport class AnalyticsTracker {\n private config: Required<Pick<TrackerConfig, 'projectId' | 'endpoint'>> & InternalConfig;\n private sessionId: string;\n private batcher: EventBatcher;\n private cleanups: (() => void)[] = [];\n private experimentManager: ExperimentManager;\n private remoteConfig: RemoteConfig | null = null;\n private readyPromise: Promise<void>;\n private resolveReady!: () => void;\n private replayActive = false;\n\n private trackingEnabled = false;\n\n constructor(config: InternalConfig) {\n this.config = config;\n const { sessionId, isNew } = getOrCreateSession();\n this.sessionId = sessionId;\n\n this.experimentManager = new ExperimentManager(sessionId);\n\n this.readyPromise = new Promise<void>((resolve) => {\n this.resolveReady = resolve;\n });\n\n this.batcher = new EventBatcher(\n config.endpoint,\n config.apiKey,\n config.flushInterval,\n config.debug\n );\n\n // Fire session_start if new\n if (isNew) {\n this.track({\n type: 'session_start',\n url: location.href,\n properties: {\n maxTouchPoints: navigator.maxTouchPoints ?? 0,\n pointerType: window.matchMedia('(pointer: coarse)').matches ? 'coarse' : 'fine',\n dpr: window.devicePixelRatio ?? 1,\n viewportWidth: window.innerWidth,\n viewportHeight: window.innerHeight,\n },\n });\n }\n\n // Pageview listener (always on — no consent needed for aggregate pageviews)\n this.cleanups.push(\n attachPageviewListener((e) => this.track(e))\n );\n\n // Click, scroll, and replay require consent — only attach via enableTracking()\n if (!config.coreOnly) {\n this.attachBehavioralListeners();\n }\n\n // Set initial global (experiments not yet loaded)\n this.updateGlobal();\n\n // Non-blocking remote config fetch (flags + experiments — technically necessary)\n this.fetchRemoteConfig();\n }\n\n /**\n * Enable behavioral tracking (clicks, scroll, heatmaps) after user consent.\n * Call this from your cookie consent callback.\n * Safe to call multiple times — only attaches listeners once.\n */\n enableTracking(): void {\n if (this.trackingEnabled) return;\n this.attachBehavioralListeners();\n if (this.config.debug) console.log('[analytics] behavioral tracking enabled (post-consent)');\n }\n\n /** Whether behavioral tracking (clicks, scroll) is active. */\n get isTrackingEnabled(): boolean {\n return this.trackingEnabled;\n }\n\n private attachBehavioralListeners(): void {\n if (this.trackingEnabled) return;\n this.trackingEnabled = true;\n\n if (this.config.heatmap !== false) {\n this.cleanups.push(\n attachClickListener((e) => this.track(e))\n );\n }\n\n if (this.config.scrollDepth !== false) {\n this.cleanups.push(\n attachScrollListener((e) => this.track(e))\n );\n }\n }\n\n /** Resolves when remote config has been fetched (or after timeout / error). */\n ready(): Promise<void> {\n return this.readyPromise;\n }\n\n /** Get the assigned variant for an experiment, or null if not found. */\n getVariant(key: string): string | null {\n return this.experimentManager.getVariant(key);\n }\n\n /** Override the variant for an experiment and notify listeners. */\n setVariant(key: string, variant: string): void {\n this.experimentManager.setVariant(key, variant);\n this.updateGlobal();\n if (typeof window !== 'undefined') {\n window.dispatchEvent(\n new CustomEvent('lumitra:variant-changed', {\n detail: { key, variant },\n }),\n );\n }\n }\n\n /** Evaluate a feature flag. Returns false if not found or disabled. */\n getFlag(key: string): boolean {\n return this.experimentManager.getFlag(key);\n }\n\n /** Switch from session-based to user-based experiment assignment. */\n identify(userId: string): void {\n this.experimentManager.identify(userId);\n this.updateGlobal();\n }\n\n /** Get the project ID this tracker was initialized with. */\n get projectId(): string {\n return this.config.projectId;\n }\n\n /**\n * Enable session replay after user consent.\n * Call this from your cookie consent callback.\n */\n enableReplay(): void {\n if (this.replayActive) return;\n this.replayActive = true;\n import('./replay.js')\n .then((mod) => mod.initReplay(this, this.config.replayPrivacy))\n .catch(() => {\n this.replayActive = false;\n if (this.config.debug) console.warn('[analytics] rrweb not available, replay disabled');\n });\n }\n\n /**\n * Disable session replay (e.g. user revoked consent).\n */\n disableReplay(): void {\n if (!this.replayActive) return;\n this.replayActive = false;\n import('./replay.js')\n .then((mod) => mod.stopReplay())\n .catch(() => {});\n }\n\n /** Whether session replay is currently active. */\n get isReplayActive(): boolean {\n return this.replayActive;\n }\n\n track(partial: Omit<TrackerEvent, 'projectId' | 'sessionId' | 'timestamp'>): void {\n touchSession();\n const { screenWidth, screenHeight } = getScreenDimensions();\n\n // Attach experiment context\n const activeExperiments = this.experimentManager.getActiveExperiments();\n const experimentIds = Object.keys(activeExperiments);\n\n let experimentId: string | undefined;\n let variant: string | undefined;\n let properties = partial.properties;\n\n if (experimentIds.length > 0) {\n // Set top-level fields from the first active experiment\n const firstId = experimentIds[0]!;\n experimentId = firstId;\n variant = activeExperiments[firstId];\n\n // If multiple experiments, attach all as _experiments property\n if (experimentIds.length > 1) {\n properties = { ...properties, _experiments: activeExperiments };\n }\n }\n\n const pageHash = getCachedPageHash() || undefined;\n\n const event: TrackerEvent = {\n ...partial,\n projectId: this.config.projectId,\n sessionId: this.sessionId,\n timestamp: Date.now(),\n screenWidth,\n screenHeight,\n deviceType: getDeviceType(),\n userAgent: navigator.userAgent,\n ...(experimentId && { experimentId }),\n ...(variant && { variant }),\n ...(properties && { properties }),\n ...(pageHash && { pageHash }),\n };\n this.batcher.add(event);\n }\n\n /**\n * Force the event batch to flush now. Used by session replay to drain its\n * buffered chunks before the page goes away, independent of the batcher's\n * own pagehide listener (which may have already fired).\n */\n flush(useBeacon = false): Promise<void> {\n return this.batcher.flush(useBeacon);\n }\n\n destroy(): void {\n for (const cleanup of this.cleanups) cleanup();\n this.cleanups = [];\n this.batcher.destroy();\n this.cleanupGlobal();\n }\n\n /** Remove the window.__lumitra global. */\n cleanupGlobal(): void {\n if (typeof window !== 'undefined') {\n delete (window as unknown as Record<string, unknown>).__lumitra;\n }\n }\n\n // ── Internal ───────────────────────────────────────────────────────────────\n\n private updateGlobal(): void {\n if (typeof window === 'undefined') return;\n (window as unknown as Record<string, unknown>).__lumitra = {\n projectId: this.config.projectId,\n experiments: this.experimentManager.getAllAssignments(),\n flags: this.experimentManager.getAllFlags(),\n ready: this.readyPromise,\n };\n }\n\n private fetchRemoteConfig(): void {\n const baseUrl = this.config.endpoint.replace(/\\/api\\/collect\\/?$/, '');\n const url = `${baseUrl}/api/projects/${this.config.projectId}/config`;\n\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), CONFIG_TIMEOUT_MS);\n\n fetch(url, {\n method: 'GET',\n signal: controller.signal,\n credentials: 'omit',\n })\n .then((res) => {\n if (!res.ok) throw new Error(`Config fetch failed: ${res.status}`);\n return res.json() as Promise<RemoteConfig>;\n })\n .then((data) => {\n this.remoteConfig = data;\n this.experimentManager.setDefinitions(\n data.experiments ?? [],\n data.flags ?? [],\n );\n this.updateGlobal();\n if (typeof window !== 'undefined') {\n window.dispatchEvent(new CustomEvent('lumitra:ready', {\n detail: {\n projectId: this.config.projectId,\n experiments: this.experimentManager.getAllAssignments(),\n flags: this.experimentManager.getAllFlags(),\n },\n }));\n }\n if (this.config.debug) {\n console.log('[analytics] remote config loaded:', data);\n }\n })\n .catch((err) => {\n if (this.config.debug) {\n console.warn('[analytics] remote config fetch failed, using defaults:', err);\n }\n })\n .finally(() => {\n clearTimeout(timeout);\n this.resolveReady();\n });\n }\n}\n","import type { TrackerEvent } from './constants';\nimport { AnalyticsTracker } from './tracker.js';\n\nexport interface ReplayPrivacy {\n /** Mask all input field values in replay. Default: true. */\n maskAllInputs?: boolean;\n /** Mask all text content in replay. Default: false. */\n maskAllText?: boolean;\n /** CSS selector for elements to block entirely from replay. */\n blockSelector?: string;\n /** CSS selector for elements whose text should be masked. */\n maskTextSelector?: string;\n}\n\nexport interface TrackerConfig {\n /** Project ID (UUID). */\n projectId: string;\n /** Ingestion endpoint URL. */\n endpoint: string;\n /** API key (ap_live_... or ap_test_...). */\n apiKey: string;\n /** Enable click heatmap tracking. Default: true. Requires enableTracking(). */\n heatmap?: boolean;\n /** Enable scroll depth tracking. Default: true. Requires enableTracking(). */\n scrollDepth?: boolean;\n /** Batch flush interval in ms. Default: 5000. */\n flushInterval?: number;\n /** Debug mode — logs events to console. Default: false. */\n debug?: boolean;\n /** Privacy options for session replay. */\n replayPrivacy?: ReplayPrivacy;\n}\n\nlet instance: AnalyticsTracker | null = null;\n\n/**\n * Initialize the analytics tracker. Creates a singleton.\n *\n * Tracks: pageviews, sessions, visitors (aggregate). No consent required.\n * Loads: feature flags + A/B experiment assignments (technically necessary).\n *\n * Does NOT track clicks, scroll, or replay — call enableTracking() after consent.\n */\nexport function init(config: TrackerConfig): AnalyticsTracker {\n if (instance) {\n if (config.debug) console.warn('[analytics] already initialized, returning existing instance');\n return instance;\n }\n\n instance = new AnalyticsTracker({ ...config, coreOnly: true });\n return instance;\n}\n\n/**\n * Enable behavioral tracking (clicks, scroll, heatmaps) after user consent.\n * Safe to call multiple times — only attaches listeners once.\n */\nexport function enableTracking(): void {\n instance?.enableTracking();\n}\n\n/**\n * Get the current tracker instance, or null if not initialized.\n */\nexport function getTracker(): AnalyticsTracker | null {\n return instance;\n}\n\n/**\n * Destroy the tracker and clean up listeners.\n */\nexport function destroy(): void {\n instance?.destroy();\n instance = null;\n}\n\n/**\n * Enable replay on the current tracker instance (call after cookie consent).\n */\nexport function enableReplay(): void {\n instance?.enableReplay();\n}\n\n/**\n * Disable replay on the current tracker instance (call if user revokes consent).\n */\nexport function disableReplay(): void {\n instance?.disableReplay();\n}\n\nexport type { TrackerEvent, TrackerConfig as AnalyticsConfig };\nexport { AnalyticsTracker };\nexport { ExperimentManager } from './experiment.js';\nexport type { ExperimentDefinition, FlagDefinition, RemoteConfig } from './experiment.js';\n"]}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import'./chunk-5XGN7UAV.js';var f=1e4,o=null,i=[],r=null,s=null,l=null,n=null;async function y(e,t){let a;try{a=await import('rrweb');}catch{return}r=a.record({maskAllInputs:t?.maskAllInputs!==false,maskAllText:t?.maskAllText??false,...t?.blockSelector&&{blockSelector:t.blockSelector},...t?.maskTextSelector&&{maskTextSelector:t.maskTextSelector},blockClass:"ap-block",maskInputOptions:{password:true,email:true,tel:true},inlineStylesheet:true,collectFonts:true,inlineImages:true,emit(d){i.push(d),new Blob([JSON.stringify(i)]).size>=524288&&u(e);}})??null,o=setInterval(()=>u(e),f),s=e,typeof window<"u"&&(l=()=>c(e),window.addEventListener("pagehide",l)),typeof document<"u"&&(n=()=>{document.visibilityState==="hidden"&&c(e);},document.addEventListener("visibilitychange",n));}function u(e){if(i.length===0)return;let t=i.splice(0);e.track({type:"replay_chunk",url:location.href,replayChunk:t});}function c(e){u(e),e.flush(true);}function k(){r&&(r(),r=null),o&&(clearInterval(o),o=null),typeof window<"u"&&l&&window.removeEventListener("pagehide",l),typeof document<"u"&&n&&document.removeEventListener("visibilitychange",n),l=null,n=null,s&&u(s),s=null,i=[];}export{y as initReplay,k as stopReplay};//# sourceMappingURL=replay-SX42SUVY.js.map
|
|
2
|
+
//# sourceMappingURL=replay-SX42SUVY.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/replay.ts"],"names":["CHUNK_FLUSH_INTERVAL","replayTimer","chunkBuffer","stopRecording","activeTracker","onPageHide","onVisibilityChange","initReplay","tracker","privacy","rrweb","event","flushChunk","flushAndSend","chunk","stopReplay"],"mappings":"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","file":"replay-SX42SUVY.js","sourcesContent":["import type { AnalyticsTracker } from './tracker.js';\nimport type { ReplayPrivacy } from './index.js';\nimport { MAX_REPLAY_CHUNK_BYTES } from './constants';\n\nconst CHUNK_FLUSH_INTERVAL = 10_000; // 10 seconds\n\nlet replayTimer: ReturnType<typeof setInterval> | null = null;\nlet chunkBuffer: unknown[] = [];\nlet stopRecording: (() => void) | null = null;\nlet activeTracker: AnalyticsTracker | null = null;\nlet onPageHide: (() => void) | null = null;\nlet onVisibilityChange: (() => void) | null = null;\n\nexport async function initReplay(\n tracker: AnalyticsTracker,\n privacy?: ReplayPrivacy,\n): Promise<void> {\n let rrweb: typeof import('rrweb');\n try {\n rrweb = await import('rrweb');\n } catch {\n return; // rrweb not installed, graceful no-op\n }\n\n // rrweb 2.0-alpha ships inconsistent option types across its sub-packages:\n // `maskAllText` is a real, supported record() option at runtime but is\n // missing from the `recordOptions` generic that this build resolves, so the\n // literal needs a cast to the actual parameter type record() expects.\n stopRecording = rrweb.record({\n // Privacy defaults: always mask inputs unless explicitly disabled\n maskAllInputs: privacy?.maskAllInputs !== false,\n maskAllText: privacy?.maskAllText ?? false,\n ...(privacy?.blockSelector && { blockSelector: privacy.blockSelector }),\n ...(privacy?.maskTextSelector && { maskTextSelector: privacy.maskTextSelector }),\n // Block password fields and sensitive attributes by default\n blockClass: 'ap-block',\n maskInputOptions: {\n password: true,\n email: true,\n tel: true,\n },\n inlineStylesheet: true, // Inline all CSS into snapshot (fixes cross-origin stylesheet issue)\n collectFonts: true, // Capture web fonts\n inlineImages: true, // Inline images as data URIs\n emit(event) {\n chunkBuffer.push(event);\n\n // Flush if chunk is too large\n const size = new Blob([JSON.stringify(chunkBuffer)]).size;\n if (size >= MAX_REPLAY_CHUNK_BYTES) {\n flushChunk(tracker);\n }\n },\n } as Parameters<typeof rrweb.record>[0]) ?? null;\n\n replayTimer = setInterval(() => flushChunk(tracker), CHUNK_FLUSH_INTERVAL);\n activeTracker = tracker;\n\n // Flush buffered replay events before the page goes away. Without this,\n // sessions shorter than CHUNK_FLUSH_INTERVAL never hit the timer tick or the\n // size cap, so their rrweb events are discarded and the session never shows\n // up in the Replay tab. We move the buffer into the batch and then force a\n // send, rather than relying on the batcher's own pagehide listener — that one\n // is registered first (in the tracker constructor) and would otherwise have\n // already flushed an empty queue before this chunk was enqueued.\n if (typeof window !== 'undefined') {\n onPageHide = () => flushAndSend(tracker);\n window.addEventListener('pagehide', onPageHide);\n }\n if (typeof document !== 'undefined') {\n onVisibilityChange = () => {\n if (document.visibilityState === 'hidden') flushAndSend(tracker);\n };\n document.addEventListener('visibilitychange', onVisibilityChange);\n }\n}\n\nfunction flushChunk(tracker: AnalyticsTracker): void {\n if (chunkBuffer.length === 0) return;\n\n const chunk = chunkBuffer.splice(0);\n tracker.track({\n type: 'replay_chunk',\n url: location.href,\n replayChunk: chunk,\n });\n}\n\n/** Move buffered replay events into the batch and force an immediate send. */\nfunction flushAndSend(tracker: AnalyticsTracker): void {\n flushChunk(tracker);\n void tracker.flush(true);\n}\n\nexport function stopReplay(): void {\n if (stopRecording) {\n stopRecording();\n stopRecording = null;\n }\n if (replayTimer) {\n clearInterval(replayTimer);\n replayTimer = null;\n }\n if (typeof window !== 'undefined' && onPageHide) {\n window.removeEventListener('pagehide', onPageHide);\n }\n if (typeof document !== 'undefined' && onVisibilityChange) {\n document.removeEventListener('visibilitychange', onVisibilityChange);\n }\n onPageHide = null;\n onVisibilityChange = null;\n // Flush-then-clear: don't silently discard buffered events on stop. The\n // batcher's timer (or its own hide listener) drains the queue from here.\n if (activeTracker) flushChunk(activeTracker);\n activeTracker = null;\n chunkBuffer = [];\n}\n"]}
|
package/package.json
CHANGED
package/dist/replay-S33L5QID.js
DELETED
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import'./chunk-5XGN7UAV.js';var a=1e4,r=null,t=[],n=null;async function p(l,e){let o;try{o=await import('rrweb');}catch{return}n=o.record({maskAllInputs:e?.maskAllInputs!==false,maskAllText:e?.maskAllText??false,...e?.blockSelector&&{blockSelector:e.blockSelector},...e?.maskTextSelector&&{maskTextSelector:e.maskTextSelector},blockClass:"ap-block",maskInputOptions:{password:true,email:true,tel:true},inlineStylesheet:true,collectFonts:true,inlineImages:true,emit(i){t.push(i),new Blob([JSON.stringify(t)]).size>=524288&&s(l);}})??null,r=setInterval(()=>s(l),a);}function s(l){if(t.length===0)return;let e=t.splice(0);l.track({type:"replay_chunk",url:location.href,replayChunk:e});}function k(){n&&(n(),n=null),r&&(clearInterval(r),r=null),t=[];}export{p as initReplay,k as stopReplay};//# sourceMappingURL=replay-S33L5QID.js.map
|
|
2
|
-
//# sourceMappingURL=replay-S33L5QID.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/replay.ts"],"names":["CHUNK_FLUSH_INTERVAL","replayTimer","chunkBuffer","stopRecording","initReplay","tracker","privacy","rrweb","event","flushChunk","chunk","stopReplay"],"mappings":"4BAIA,IAAMA,CAAAA,CAAuB,GAAA,CAEzBC,CAAAA,CAAqD,IAAA,CACrDC,EAAyB,EAAC,CAC1BC,CAAAA,CAAqC,IAAA,CAEzC,eAAsBC,CAAAA,CACpBC,CAAAA,CACAC,CAAAA,CACe,CACf,IAAIC,CAAAA,CACJ,GAAI,CACFA,CAAAA,CAAQ,MAAM,OAAO,OAAO,EAC9B,MAAQ,CACN,MACF,CAEAJ,CAAAA,CAAgBI,EAAM,MAAA,CAAO,CAE3B,aAAA,CAAeD,CAAAA,EAAS,gBAAkB,KAAA,CAC1C,WAAA,CAAaA,CAAAA,EAAS,WAAA,EAAe,KAAA,CACrC,GAAIA,CAAAA,EAAS,aAAA,EAAiB,CAAE,aAAA,CAAeA,CAAAA,CAAQ,aAAc,CAAA,CACrE,GAAIA,CAAAA,EAAS,gBAAA,EAAoB,CAAE,gBAAA,CAAkBA,EAAQ,gBAAiB,CAAA,CAE9E,UAAA,CAAY,UAAA,CACZ,iBAAkB,CAChB,QAAA,CAAU,IAAA,CACV,KAAA,CAAO,KACP,GAAA,CAAK,IACP,CAAA,CACA,gBAAA,CAAkB,KAClB,YAAA,CAAc,IAAA,CACd,YAAA,CAAc,IAAA,CACd,KAAKE,CAAAA,CAAO,CACVN,CAAAA,CAAY,IAAA,CAAKM,CAAK,CAAA,CAGT,IAAI,IAAA,CAAK,CAAC,IAAA,CAAK,SAAA,CAAUN,CAAW,CAAC,CAAC,CAAA,CAAE,IAAA,EACzC,MAAA,EACVO,CAAAA,CAAWJ,CAAO,EAEtB,CACF,CAAC,CAAA,EAAK,IAAA,CAENJ,CAAAA,CAAc,WAAA,CAAY,IAAMQ,EAAWJ,CAAO,CAAA,CAAGL,CAAoB,EAC3E,CAEA,SAASS,CAAAA,CAAWJ,CAAAA,CAAiC,CACnD,GAAIH,CAAAA,CAAY,MAAA,GAAW,CAAA,CAAG,OAE9B,IAAMQ,CAAAA,CAAQR,CAAAA,CAAY,MAAA,CAAO,CAAC,CAAA,CAClCG,CAAAA,CAAQ,KAAA,CAAM,CACZ,KAAM,cAAA,CACN,GAAA,CAAK,QAAA,CAAS,IAAA,CACd,YAAaK,CACf,CAAC,EACH,CAEO,SAASC,CAAAA,EAAmB,CAC7BR,CAAAA,GACFA,CAAAA,GACAA,CAAAA,CAAgB,IAAA,CAAA,CAEdF,CAAAA,GACF,aAAA,CAAcA,CAAW,CAAA,CACzBA,CAAAA,CAAc,IAAA,CAAA,CAEhBC,CAAAA,CAAc,GAChB","file":"replay-S33L5QID.js","sourcesContent":["import type { AnalyticsTracker } from './tracker.js';\nimport type { ReplayPrivacy } from './index.js';\nimport { MAX_REPLAY_CHUNK_BYTES } from './constants';\n\nconst CHUNK_FLUSH_INTERVAL = 10_000; // 10 seconds\n\nlet replayTimer: ReturnType<typeof setInterval> | null = null;\nlet chunkBuffer: unknown[] = [];\nlet stopRecording: (() => void) | null = null;\n\nexport async function initReplay(\n tracker: AnalyticsTracker,\n privacy?: ReplayPrivacy,\n): Promise<void> {\n let rrweb: typeof import('rrweb');\n try {\n rrweb = await import('rrweb');\n } catch {\n return; // rrweb not installed, graceful no-op\n }\n\n stopRecording = rrweb.record({\n // Privacy defaults: always mask inputs unless explicitly disabled\n maskAllInputs: privacy?.maskAllInputs !== false,\n maskAllText: privacy?.maskAllText ?? false,\n ...(privacy?.blockSelector && { blockSelector: privacy.blockSelector }),\n ...(privacy?.maskTextSelector && { maskTextSelector: privacy.maskTextSelector }),\n // Block password fields and sensitive attributes by default\n blockClass: 'ap-block',\n maskInputOptions: {\n password: true,\n email: true,\n tel: true,\n },\n inlineStylesheet: true, // Inline all CSS into snapshot (fixes cross-origin stylesheet issue)\n collectFonts: true, // Capture web fonts\n inlineImages: true, // Inline images as data URIs\n emit(event) {\n chunkBuffer.push(event);\n\n // Flush if chunk is too large\n const size = new Blob([JSON.stringify(chunkBuffer)]).size;\n if (size >= MAX_REPLAY_CHUNK_BYTES) {\n flushChunk(tracker);\n }\n },\n }) ?? null;\n\n replayTimer = setInterval(() => flushChunk(tracker), CHUNK_FLUSH_INTERVAL);\n}\n\nfunction flushChunk(tracker: AnalyticsTracker): void {\n if (chunkBuffer.length === 0) return;\n\n const chunk = chunkBuffer.splice(0);\n tracker.track({\n type: 'replay_chunk',\n url: location.href,\n replayChunk: chunk,\n });\n}\n\nexport function stopReplay(): void {\n if (stopRecording) {\n stopRecording();\n stopRecording = null;\n }\n if (replayTimer) {\n clearInterval(replayTimer);\n replayTimer = null;\n }\n chunkBuffer = [];\n}\n"]}
|