@grainql/analytics-web 3.0.1 → 3.0.2
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.global.dev.js +1 -1
- package/dist/index.global.js +1 -1
- package/package.json +1 -1
package/dist/index.global.dev.js
CHANGED
package/dist/index.global.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* Grain Analytics Web SDK v3.0.
|
|
1
|
+
/* Grain Analytics Web SDK v3.0.2 | MIT License */
|
|
2
2
|
"use strict";var Grain=(()=>{var _=Object.defineProperty;var ae=Object.getOwnPropertyDescriptor;var oe=Object.getOwnPropertyNames;var se=Object.prototype.hasOwnProperty;var g=(s,e)=>()=>(s&&(e=s(s=0)),e);var b=(s,e)=>{for(var t in e)_(s,t,{get:e[t],enumerable:!0})},ce=(s,e,t,i)=>{if(e&&typeof e=="object"||typeof e=="function")for(let r of oe(e))!se.call(s,r)&&r!==t&&_(s,r,{get:()=>e[r],enumerable:!(i=ae(e,r))||i.enumerable});return s};var le=s=>ce(_({},"__esModule",{value:!0}),s);var be,f,U=g(()=>{"use strict";be={maxSectionDuration:9e3,minScrollDistance:100,idleThreshold:3e4},f=class{constructor(e,t={}){this.isDestroyed=!1;this.isPageVisible=!0;this.visibilityChangeHandler=null;this.sectionStates=new Map;this.lastFilterReason=null;this.activityDetector=e,this.options={...be,...t,debug:t.debug??!1},this.setupPageVisibilityTracking()}setupPageVisibilityTracking(){typeof document>"u"||(this.isPageVisible=document.visibilityState==="visible",this.visibilityChangeHandler=()=>{let e=this.isPageVisible;this.isPageVisible=document.visibilityState==="visible",!this.isPageVisible&&e?this.log("Page hidden - tracking paused"):this.isPageVisible&&!e&&(this.log("Page visible - tracking resumed"),this.resetAllSections())},document.addEventListener("visibilitychange",this.visibilityChangeHandler))}shouldTrack(){return this.isPageVisible?this.activityDetector.isActive(this.options.idleThreshold)?(this.lastFilterReason=null,!0):(this.lastFilterReason="user_idle",!1):(this.lastFilterReason="page_hidden",!1)}shouldTrackSection(e,t){if(!this.shouldTrack())return{shouldTrack:!1,reason:this.lastFilterReason||"global_policy"};let i=this.sectionStates.get(e);i||(i={sectionName:e,currentDuration:0,lastScrollPosition:t,lastResetTime:Date.now()},this.sectionStates.set(e,i));let r=Math.abs(t-i.lastScrollPosition);return r>=this.options.minScrollDistance?(this.log(`Section "${e}": Attention reset due to ${Math.round(r)}px scroll`),i.currentDuration=0,i.lastScrollPosition=t,i.lastResetTime=Date.now(),{shouldTrack:!0,resetAttention:!0}):i.currentDuration>=this.options.maxSectionDuration?{shouldTrack:!1,reason:"max_duration_reached"}:{shouldTrack:!0}}updateSectionDuration(e,t){let i=this.sectionStates.get(e);i&&(i.currentDuration+=t,i.currentDuration>=this.options.maxSectionDuration&&this.log(`Section "${e}": Max duration cap reached (${i.currentDuration}ms)`))}resetSection(e){let t=this.sectionStates.get(e);t&&(this.log(`Section "${e}": Attention reset (section exit)`),t.currentDuration=0,t.lastResetTime=Date.now())}resetAllSections(){this.log("Resetting all section attention states");for(let e of this.sectionStates.values())e.currentDuration=0,e.lastResetTime=Date.now()}getSectionState(e){return this.sectionStates.get(e)}getLastFilterReason(){return this.lastFilterReason}shouldTrackScroll(e,t){return this.shouldTrack()?Math.abs(t-e)<10?{shouldTrack:!1,reason:"scroll_too_small"}:{shouldTrack:!0}:{shouldTrack:!1,reason:this.lastFilterReason||"global_policy"}}getPolicies(){return{maxSectionDuration:this.options.maxSectionDuration,minScrollDistance:this.options.minScrollDistance,idleThreshold:this.options.idleThreshold}}getTrackingState(){return{isPageVisible:this.isPageVisible,isUserActive:this.activityDetector.isActive(this.options.idleThreshold),timeSinceLastActivity:this.activityDetector.getTimeSinceLastActivity(),activeSections:this.sectionStates.size}}log(...e){this.options.debug&&console.log("[AttentionQuality]",...e)}destroy(){this.isDestroyed||(this.isDestroyed=!0,this.visibilityChangeHandler&&typeof document<"u"&&(document.removeEventListener("visibilitychange",this.visibilityChangeHandler),this.visibilityChangeHandler=null),this.sectionStates.clear())}}});function Ae(s){if(s)return s.replace(/[\u{1F300}-\u{1F9FF}]/gu,"").replace(/[\u{1F600}-\u{1F64F}]/gu,"").replace(/[\u{1F680}-\u{1F6FF}]/gu,"").replace(/[\u{2600}-\u{26FF}]/gu,"").replace(/[\u{2700}-\u{27BF}]/gu,"").replace(/[\u{1F900}-\u{1F9FF}]/gu,"").replace(/[\u{1F1E0}-\u{1F1FF}]/gu,"").replace(/[\u{200D}]/gu,"").replace(/[\u{FE0F}]/gu,"").replace(/[\u{20E3}]/gu,"").trim()}function x(s,e=100){if(!s)return;let t=Ae(s);if(t)return t.substring(0,e)||void 0}var N=g(()=>{"use strict"});var J={};b(J,{HeatmapTrackingManager:()=>H});var Se,H,q=g(()=>{"use strict";U();N();Se={scrollDebounceDelay:100,batchDelay:2e3,maxBatchSize:20,debug:!1},H=class{constructor(e,t={}){this.isDestroyed=!1;this.currentScrollState=null;this.pendingClicks=[];this.pendingScrolls=[];this.scrollDebounceTimer=null;this.batchTimer=null;this.scrollTrackingTimer=null;this.periodicScrollTimer=null;this.lastScrollPosition=0;this.lastScrollTime=Date.now();this.SPLIT_DURATION=3e3;this.tracker=e,this.options={...Se,...t},this.attentionQuality=new f(e.getActivityDetector(),{maxSectionDuration:9e3,minScrollDistance:100,idleThreshold:3e4,debug:this.options.debug}),typeof window<"u"&&typeof document<"u"&&(document.readyState==="loading"?document.addEventListener("DOMContentLoaded",()=>this.initialize()):setTimeout(()=>this.initialize(),0))}initialize(){this.isDestroyed||(this.log("Initializing heatmap tracking"),this.setupClickTracking(),this.setupScrollTracking(),this.startScrollTracking(),this.setupUnloadHandler())}setupClickTracking(){if(typeof document>"u")return;let e=t=>{this.isDestroyed||this.tracker.hasConsent("analytics")&&this.handleClick(t)};document.addEventListener("click",e,{passive:!0,capture:!0})}setupScrollTracking(){if(typeof window>"u")return;let e=()=>{this.scrollDebounceTimer!==null&&clearTimeout(this.scrollDebounceTimer),this.scrollDebounceTimer=window.setTimeout(()=>{this.handleScroll(),this.scrollDebounceTimer=null},this.options.scrollDebounceDelay)};window.addEventListener("scroll",e,{passive:!0})}startScrollTracking(){typeof window>"u"||(this.updateScrollState(),this.scrollTrackingTimer=window.setInterval(()=>{this.isDestroyed||this.updateScrollState()},500),this.startPeriodicScrollTracking())}startPeriodicScrollTracking(){typeof window>"u"||(this.periodicScrollTimer=window.setInterval(()=>{if(this.isDestroyed||!this.currentScrollState||!this.tracker.hasConsent("analytics"))return;if(!this.attentionQuality.shouldTrack()){this.log("Scroll tracking paused:",this.attentionQuality.getLastFilterReason());return}let e=Date.now(),t=e-this.currentScrollState.entryTime;if(t>1e3){let i=window.scrollY||window.pageYOffset,r=window.innerHeight,a=document.documentElement.scrollHeight,n=`viewport_section_${this.currentScrollState.viewportSection}`,o=this.attentionQuality.shouldTrackSection(n,i);if(o.resetAttention){this.log(`Viewport section ${this.currentScrollState.viewportSection}: Attention reset`),this.currentScrollState.entryTime=e;return}if(!o.shouldTrack){this.log(`Viewport section ${this.currentScrollState.viewportSection}: ${o.reason}`);return}let c={pageUrl:window.location.href,viewportSection:this.currentScrollState.viewportSection,scrollDepthPx:i,durationMs:t,entryTimestamp:this.currentScrollState.entryTime,exitTimestamp:e,pageHeight:a,viewportHeight:r};this.tracker.trackSystemEvent("_grain_heatmap_scroll",{page_url:c.pageUrl,viewport_section:c.viewportSection,scroll_depth_px:c.scrollDepthPx,duration_ms:c.durationMs,entry_timestamp:c.entryTimestamp,exit_timestamp:c.exitTimestamp,page_height:c.pageHeight,viewport_height:c.viewportHeight,is_split:!0},{flush:!0}),this.attentionQuality.updateSectionDuration(n,t),this.currentScrollState.entryTime=e}},this.SPLIT_DURATION))}setupUnloadHandler(){if(typeof window>"u")return;let e=()=>{if(this.currentScrollState){let t=Date.now(),i=t-this.currentScrollState.entryTime;if(i>100){let r={pageUrl:window.location.href,viewportSection:this.currentScrollState.viewportSection,scrollDepthPx:this.currentScrollState.scrollDepthPx,durationMs:i,entryTimestamp:this.currentScrollState.entryTime,exitTimestamp:t,pageHeight:document.documentElement.scrollHeight,viewportHeight:window.innerHeight};this.pendingScrolls.push(r)}}this.flushPendingEventsWithBeacon()};window.addEventListener("beforeunload",e),window.addEventListener("pagehide",e)}handleClick(e){if(!this.tracker.hasConsent("analytics"))return;let t=e.target;if(!t)return;let i=window.location.href,r=this.generateXPath(t),a=Math.round(e.clientX),n=Math.round(e.clientY),o=Math.round(e.pageX),c=Math.round(e.pageY),l=t.tagName?.toLowerCase()||"unknown",u=x(t.textContent),d={pageUrl:i,xpath:r,viewportX:a,viewportY:n,pageX:o,pageY:c,elementTag:l,elementText:u||void 0,timestamp:Date.now()};t instanceof HTMLAnchorElement&&t.href?this.tracker.trackSystemEvent("_grain_heatmap_click",{page_url:d.pageUrl,xpath:d.xpath,viewport_x:d.viewportX,viewport_y:d.viewportY,page_x:d.pageX,page_y:d.pageY,element_tag:d.elementTag,element_text:d.elementText,timestamp:d.timestamp},{flush:!0}):(this.pendingClicks.push(d),this.considerBatchFlush())}handleScroll(){this.tracker.hasConsent("analytics")&&this.updateScrollState()}updateScrollState(){if(typeof window>"u"||!this.tracker.hasConsent("analytics"))return;let e=Date.now(),t=window.scrollY||window.pageYOffset,i=window.innerHeight,r=document.documentElement.scrollHeight,a=Math.floor(t/i);if(this.currentScrollState&&this.currentScrollState.viewportSection!==a){let n=e-this.currentScrollState.entryTime;if(n>100){let c={pageUrl:window.location.href,viewportSection:this.currentScrollState.viewportSection,scrollDepthPx:this.currentScrollState.scrollDepthPx,durationMs:n,entryTimestamp:this.currentScrollState.entryTime,exitTimestamp:e,pageHeight:r,viewportHeight:i};this.pendingScrolls.push(c)}let o=`viewport_section_${this.currentScrollState.viewportSection}`;this.attentionQuality.resetSection(o)}(!this.currentScrollState||this.currentScrollState.viewportSection!==a)&&(this.currentScrollState={viewportSection:a,entryTime:e,scrollDepthPx:t}),this.lastScrollPosition=t,this.lastScrollTime=e,this.considerBatchFlush()}generateXPath(e){if(!e)return"";if(e.id)return`//*[@id="${e.id}"]`;let t=[],i=e;for(;i&&i.nodeType===Node.ELEMENT_NODE;){let r=0,a=i;for(;a;)a=a.previousElementSibling,a&&a.nodeName===i.nodeName&&r++;let n=i.nodeName.toLowerCase(),o=r>0?`[${r+1}]`:"";t.unshift(`${n}${o}`),i=i.parentElement}return t.length?`/${t.join("/")}`:""}considerBatchFlush(){let e=this.pendingClicks.length+this.pendingScrolls.length;if(e>=this.options.maxBatchSize){this.flushPendingEvents();return}this.batchTimer===null&&e>0&&(this.batchTimer=window.setTimeout(()=>{this.flushPendingEvents(),this.batchTimer=null},this.options.batchDelay))}flushPendingEvents(){if(!this.isDestroyed){if(!this.tracker.hasConsent("analytics")){this.pendingClicks=[],this.pendingScrolls=[];return}if(this.pendingClicks.length>0){for(let e of this.pendingClicks)this.tracker.trackSystemEvent("_grain_heatmap_click",{page_url:e.pageUrl,xpath:e.xpath,viewport_x:e.viewportX,viewport_y:e.viewportY,page_x:e.pageX,page_y:e.pageY,element_tag:e.elementTag,element_text:e.elementText,timestamp:e.timestamp});this.pendingClicks=[]}if(this.pendingScrolls.length>0){for(let e of this.pendingScrolls)this.tracker.trackSystemEvent("_grain_heatmap_scroll",{page_url:e.pageUrl,viewport_section:e.viewportSection,scroll_depth_px:e.scrollDepthPx,duration_ms:e.durationMs,entry_timestamp:e.entryTimestamp,exit_timestamp:e.exitTimestamp,page_height:e.pageHeight,viewport_height:e.viewportHeight});this.pendingScrolls=[]}this.batchTimer!==null&&(clearTimeout(this.batchTimer),this.batchTimer=null)}}flushPendingEventsWithBeacon(){if(!this.tracker.hasConsent("analytics")){this.pendingClicks=[],this.pendingScrolls=[];return}if(this.pendingClicks.length>0){for(let e of this.pendingClicks)this.tracker.trackSystemEvent("_grain_heatmap_click",{page_url:e.pageUrl,xpath:e.xpath,viewport_x:e.viewportX,viewport_y:e.viewportY,page_x:e.pageX,page_y:e.pageY,element_tag:e.elementTag,element_text:e.elementText,timestamp:e.timestamp},{flush:!0});this.pendingClicks=[]}if(this.pendingScrolls.length>0){for(let e of this.pendingScrolls)this.tracker.trackSystemEvent("_grain_heatmap_scroll",{page_url:e.pageUrl,viewport_section:e.viewportSection,scroll_depth_px:e.scrollDepthPx,duration_ms:e.durationMs,entry_timestamp:e.entryTimestamp,exit_timestamp:e.exitTimestamp,page_height:e.pageHeight,viewport_height:e.viewportHeight},{flush:!0});this.pendingScrolls=[]}}log(...e){this.options.debug&&this.tracker.log("[Heatmap Tracking]",...e)}destroy(){this.isDestroyed=!0,this.scrollDebounceTimer!==null&&(clearTimeout(this.scrollDebounceTimer),this.scrollDebounceTimer=null),this.batchTimer!==null&&(clearTimeout(this.batchTimer),this.batchTimer=null),this.scrollTrackingTimer!==null&&(clearInterval(this.scrollTrackingTimer),this.scrollTrackingTimer=null),this.periodicScrollTimer!==null&&(clearInterval(this.periodicScrollTimer),this.periodicScrollTimer=null),this.attentionQuality.destroy(),this.flushPendingEvents()}}});var X={};b(X,{InteractionTrackingManager:()=>O});var O,Z=g(()=>{"use strict";N();O=class{constructor(e,t,i={}){this.isDestroyed=!1;this.attachedListeners=new Map;this.xpathCache=new Map;this.mutationObserver=null;this.mutationDebounceTimer=null;this.tracker=e,this.interactions=t,this.config={debug:i.debug??!1,enableMutationObserver:i.enableMutationObserver??!0,mutationDebounceDelay:i.mutationDebounceDelay??500,tenantId:i.tenantId,apiUrl:i.apiUrl},typeof window<"u"&&typeof document<"u"&&(this.config.tenantId&&this.config.apiUrl?this.fetchAndMergeTrackers().then(()=>{this.attachAllListeners()}):document.readyState==="loading"?document.addEventListener("DOMContentLoaded",()=>this.attachAllListeners()):setTimeout(()=>this.attachAllListeners(),0),this.config.enableMutationObserver&&this.setupMutationObserver())}async fetchAndMergeTrackers(){if(!(!this.config.tenantId||!this.config.apiUrl))try{let e=typeof window<"u"?window.location.href:"",t=`${this.config.apiUrl}/v1/client/${encodeURIComponent(this.config.tenantId)}/trackers?url=${encodeURIComponent(e)}`;this.log("Fetching trackers from:",t);let i=await fetch(t,{method:"GET",headers:{"Content-Type":"application/json"}});if(!i.ok){this.log("Failed to fetch trackers:",i.status);return}let r=await i.json();if(r.trackers&&Array.isArray(r.trackers)){this.log("Fetched",r.trackers.length,"trackers");let a=r.trackers.map(n=>({eventName:n.eventName,selector:n.selector,priority:5,label:n.eventName,description:`Tracker: ${n.eventName}`}));this.interactions=[...a,...this.interactions],this.log("Merged trackers, total interactions:",this.interactions.length)}}catch(e){this.log("Error fetching trackers:",e)}}attachAllListeners(){if(!this.isDestroyed){this.log("Attaching interaction listeners");for(let e of this.interactions)this.attachInteractionListener(e)}}attachInteractionListener(e){if(this.isDestroyed)return;let t=this.findElementByXPath(e.selector);if(!t){this.log("Element not found for interaction:",e.eventName,"selector:",e.selector);return}if(this.attachedListeners.has(t)){this.log("Listeners already attached for element:",t);return}let i=[],r=a=>this.handleInteractionClick(e,a);if(t.addEventListener("click",r,{passive:!0}),i.push({event:"click",handler:r}),t instanceof HTMLInputElement||t instanceof HTMLTextAreaElement||t instanceof HTMLSelectElement){let a=n=>this.handleInteractionFocus(e,n);t.addEventListener("focus",a,{passive:!0}),i.push({event:"focus",handler:a})}this.attachedListeners.set(t,i)}handleInteractionClick(e,t){if(this.isDestroyed||!this.tracker.hasConsent("analytics"))return;let i=t.target,r=i instanceof HTMLAnchorElement&&i.href,a={interaction_type:"click",interaction_label:e.label,interaction_description:e.description,interaction_priority:e.priority,element_tag:i.tagName?.toLowerCase(),element_text:x(i.textContent),element_id:i.id||void 0,element_class:i.className||void 0,...r&&{href:i.href},timestamp:Date.now()},n=this.tracker.track(e.eventName,a,{flush:!0});n instanceof Promise&&n.catch(o=>{this.log("Failed to track click:",o)})}handleInteractionFocus(e,t){if(this.isDestroyed||!this.tracker.hasConsent("analytics"))return;let i=t.target;this.tracker.track(e.eventName,{interaction_type:"focus",interaction_label:e.label,interaction_description:e.description,interaction_priority:e.priority,element_tag:i.tagName?.toLowerCase(),element_id:i.id||void 0,element_class:i.className||void 0,timestamp:Date.now()})}findElementByXPath(e){if(this.xpathCache.has(e)){let t=this.xpathCache.get(e);if(t&&document.contains(t))return t;this.xpathCache.delete(e)}try{let t=e;e.startsWith("xpath=")&&(t=e.substring(6));let r=document.evaluate(t,document,null,XPathResult.FIRST_ORDERED_NODE_TYPE,null).singleNodeValue;return r&&this.xpathCache.set(e,r),r}catch(t){return this.log("Error evaluating XPath:",e,t),null}}setupMutationObserver(){if(typeof MutationObserver>"u"){this.log("MutationObserver not supported");return}this.mutationObserver=new MutationObserver(e=>{this.mutationDebounceTimer!==null&&clearTimeout(this.mutationDebounceTimer),this.mutationDebounceTimer=window.setTimeout(()=>{this.handleMutations(e),this.mutationDebounceTimer=null},this.config.mutationDebounceDelay)}),this.mutationObserver.observe(document.body,{childList:!0,subtree:!0}),this.log("Mutation observer setup")}handleMutations(e){if(this.isDestroyed)return;this.xpathCache.clear();let t=new Set;for(let i of e)i.removedNodes.forEach(r=>{r instanceof Element&&(t.add(r),this.attachedListeners.forEach((a,n)=>{r.contains(n)&&t.add(n)}))});t.forEach(i=>{this.detachListeners(i)}),this.attachAllListeners()}detachListeners(e){let t=this.attachedListeners.get(e);t&&(t.forEach(({event:i,handler:r})=>{e.removeEventListener(i,r)}),this.attachedListeners.delete(e))}log(...e){this.config.debug&&console.log("[InteractionTracking]",...e)}updateInteractions(e){this.isDestroyed||(this.log("Updating interactions configuration"),this.attachedListeners.forEach((t,i)=>{this.detachListeners(i)}),this.xpathCache.clear(),this.interactions=e,this.attachAllListeners())}destroy(){this.isDestroyed||(this.log("Destroying interaction tracking manager"),this.isDestroyed=!0,this.mutationDebounceTimer!==null&&(clearTimeout(this.mutationDebounceTimer),this.mutationDebounceTimer=null),this.mutationObserver&&(this.mutationObserver.disconnect(),this.mutationObserver=null),this.attachedListeners.forEach((e,t)=>{this.detachListeners(t)}),this.attachedListeners.clear(),this.xpathCache.clear())}}});var ee={};b(ee,{SectionTrackingManager:()=>B});var Ee,B,te=g(()=>{"use strict";U();Ee={minDwellTime:1e3,scrollVelocityThreshold:500,intersectionThreshold:.1,debounceDelay:100,batchDelay:2e3,debug:!1},B=class{constructor(e,t,i={}){this.isDestroyed=!1;this.sectionStates=new Map;this.intersectionObserver=null;this.xpathCache=new Map;this.lastScrollPosition=0;this.lastScrollTime=Date.now();this.scrollVelocity=0;this.scrollDebounceTimer=null;this.pendingEvents=[];this.batchTimer=null;this.sectionTimers=new Map;this.SPLIT_DURATION=3e3;this.tracker=e,this.sections=t,this.options={...Ee,...i},this.attentionQuality=new f(e.getActivityDetector(),{maxSectionDuration:9e3,minScrollDistance:100,idleThreshold:3e4,debug:this.options.debug}),typeof window<"u"&&typeof document<"u"&&(document.readyState==="loading"?document.addEventListener("DOMContentLoaded",()=>this.initialize()):setTimeout(()=>this.initialize(),0))}initialize(){this.isDestroyed||(this.log("Initializing section tracking"),this.setupIntersectionObserver(),this.setupScrollListener(),this.initializeSections())}setupIntersectionObserver(){if(typeof IntersectionObserver>"u"){this.log("IntersectionObserver not supported");return}this.intersectionObserver=new IntersectionObserver(e=>{e.forEach(t=>{this.handleIntersection(t)})},{threshold:[0,.1,.25,.5,.75,1],rootMargin:"0px"}),this.log("IntersectionObserver created")}setupScrollListener(){if(typeof window>"u")return;let e=()=>{this.scrollDebounceTimer!==null&&clearTimeout(this.scrollDebounceTimer),this.scrollDebounceTimer=window.setTimeout(()=>{this.updateScrollVelocity(),this.scrollDebounceTimer=null},this.options.debounceDelay)};window.addEventListener("scroll",e,{passive:!0}),this.log("Scroll listener attached")}initializeSections(){for(let e of this.sections){let t=this.findElementByXPath(e.selector);if(!t){this.log("Section element not found:",e.sectionName,"selector:",e.selector);continue}let i={element:t,config:e,entryTime:null,exitTime:null,isVisible:!1,lastScrollPosition:window.scrollY,lastScrollTime:Date.now(),entryScrollSpeed:0,exitScrollSpeed:0,maxVisibleArea:0};this.sectionStates.set(e.sectionName,i),this.intersectionObserver&&this.intersectionObserver.observe(t)}}handleIntersection(e){if(this.isDestroyed)return;let t=Array.from(this.sectionStates.values()).find(a=>a.element===e.target);if(!t)return;let i=e.isIntersecting&&e.intersectionRatio>=this.options.intersectionThreshold,r=e.intersectionRatio;r>t.maxVisibleArea&&(t.maxVisibleArea=r),i&&!t.isVisible?this.handleSectionEntry(t):!i&&t.isVisible&&this.handleSectionExit(t),t.isVisible=i}handleSectionEntry(e){e.entryTime=Date.now(),e.entryScrollSpeed=this.scrollVelocity,e.lastScrollPosition=window.scrollY,e.lastScrollTime=Date.now(),e.maxVisibleArea=0,this.startPeriodicTracking(e)}startPeriodicTracking(e){this.stopPeriodicTracking(e.config.sectionName);let t=window.setInterval(()=>{if(this.isDestroyed||!e.isVisible||e.entryTime===null){this.stopPeriodicTracking(e.config.sectionName);return}let i=Date.now(),r=i-e.entryTime;if(r>=this.options.minDwellTime){let a=window.scrollY,n=this.attentionQuality.shouldTrackSection(e.config.sectionName,a);if(n.resetAttention){this.log(`Section "${e.config.sectionName}": Attention reset, restarting timer`),e.entryTime=i,e.entryScrollSpeed=this.scrollVelocity;return}if(!n.shouldTrack){this.log(`Section "${e.config.sectionName}": Tracking paused - ${n.reason}`);return}let o={sectionName:e.config.sectionName,sectionType:e.config.sectionType,entryTime:e.entryTime,exitTime:i,duration:r,viewportWidth:window.innerWidth,viewportHeight:window.innerHeight,scrollDepth:this.calculateScrollDepth(),visibleAreaPercentage:Math.round(e.maxVisibleArea*100),scrollSpeedAtEntry:e.entryScrollSpeed,scrollSpeedAtExit:this.scrollVelocity};this.shouldTrackSection(o)&&(this.tracker.trackSystemEvent("_grain_section_view",{section_name:o.sectionName,section_type:o.sectionType,duration_ms:o.duration,viewport_width:o.viewportWidth,viewport_height:o.viewportHeight,scroll_depth_percent:o.scrollDepth,visible_area_percent:o.visibleAreaPercentage,scroll_speed_entry:Math.round(o.scrollSpeedAtEntry||0),scroll_speed_exit:Math.round(o.scrollSpeedAtExit||0),entry_timestamp:o.entryTime,exit_timestamp:o.exitTime,is_split:!0}),this.attentionQuality.updateSectionDuration(e.config.sectionName,r),e.entryTime=i,e.entryScrollSpeed=this.scrollVelocity)}},this.SPLIT_DURATION);this.sectionTimers.set(e.config.sectionName,t)}stopPeriodicTracking(e){let t=this.sectionTimers.get(e);t!==void 0&&(clearInterval(t),this.sectionTimers.delete(e))}handleSectionExit(e){if(this.stopPeriodicTracking(e.config.sectionName),this.attentionQuality.resetSection(e.config.sectionName),e.entryTime===null)return;e.exitTime=Date.now(),e.exitScrollSpeed=this.scrollVelocity;let t=e.exitTime-e.entryTime,i={sectionName:e.config.sectionName,sectionType:e.config.sectionType,entryTime:e.entryTime,exitTime:e.exitTime,duration:t,viewportWidth:window.innerWidth,viewportHeight:window.innerHeight,scrollDepth:this.calculateScrollDepth(),visibleAreaPercentage:Math.round(e.maxVisibleArea*100),scrollSpeedAtEntry:e.entryScrollSpeed,scrollSpeedAtExit:e.exitScrollSpeed};this.shouldTrackSection(i)?this.queueSectionView(i):this.log("Section view filtered out:",e.config.sectionName,"duration:",t),e.entryTime=null}updateScrollVelocity(){let e=Date.now(),t=window.scrollY,i=e-this.lastScrollTime,r=Math.abs(t-this.lastScrollPosition);i>0&&(this.scrollVelocity=r/i*1e3),this.lastScrollPosition=t,this.lastScrollTime=e}calculateScrollDepth(){if(typeof window>"u"||typeof document>"u")return 0;let e=window.innerHeight,t=Math.max(document.body.scrollHeight,document.body.offsetHeight,document.documentElement.clientHeight,document.documentElement.scrollHeight,document.documentElement.offsetHeight),i=window.scrollY,r=t-e;return r<=0?100:Math.round(i/r*100)}shouldTrackSection(e){return!(e.duration<this.options.minDwellTime||(e.scrollSpeedAtEntry+e.scrollSpeedAtExit)/2>this.options.scrollVelocityThreshold*2||e.visibleAreaPercentage<10)}queueSectionView(e){this.pendingEvents.push(e),this.batchTimer===null&&(this.batchTimer=window.setTimeout(()=>{this.flushPendingEvents()},this.options.batchDelay))}flushPendingEvents(){if(!(this.isDestroyed||this.pendingEvents.length===0)){if(!this.tracker.hasConsent("analytics")){this.pendingEvents=[];return}for(let e of this.pendingEvents)this.tracker.trackSystemEvent("_grain_section_view",{section_name:e.sectionName,section_type:e.sectionType,duration_ms:e.duration,viewport_width:e.viewportWidth,viewport_height:e.viewportHeight,scroll_depth_percent:e.scrollDepth,visible_area_percent:e.visibleAreaPercentage,scroll_speed_entry:Math.round(e.scrollSpeedAtEntry||0),scroll_speed_exit:Math.round(e.scrollSpeedAtExit||0),entry_timestamp:e.entryTime,exit_timestamp:e.exitTime});this.pendingEvents=[],this.batchTimer=null}}findElementByXPath(e){if(this.xpathCache.has(e)){let t=this.xpathCache.get(e);if(t&&document.contains(t))return t;this.xpathCache.delete(e)}try{let t=e;e.startsWith("xpath=")&&(t=e.substring(6));let r=document.evaluate(t,document,null,XPathResult.FIRST_ORDERED_NODE_TYPE,null).singleNodeValue;return r&&this.xpathCache.set(e,r),r}catch(t){return this.log("Error evaluating XPath:",e,t),null}}log(...e){this.options.debug&&console.log("[SectionTracking]",...e)}updateSections(e){this.isDestroyed||(this.log("Updating sections configuration"),this.intersectionObserver&&this.intersectionObserver.disconnect(),this.sectionStates.clear(),this.xpathCache.clear(),this.sections=e,this.setupIntersectionObserver(),this.initializeSections())}destroy(){this.isDestroyed||(this.log("Destroying section tracking manager"),this.isDestroyed=!0,this.sectionTimers.forEach(e=>{clearInterval(e)}),this.sectionTimers.clear(),this.flushPendingEvents(),this.scrollDebounceTimer!==null&&(clearTimeout(this.scrollDebounceTimer),this.scrollDebounceTimer=null),this.batchTimer!==null&&(clearTimeout(this.batchTimer),this.batchTimer=null),this.intersectionObserver&&(this.intersectionObserver.disconnect(),this.intersectionObserver=null),this.attentionQuality.destroy(),this.sectionStates.clear(),this.xpathCache.clear(),this.pendingEvents=[])}}});var ie={};b(ie,{DebugAgent:()=>G});var G,re=g(()=>{"use strict";G=class{constructor(e,t,i,r,a={}){this.isDestroyed=!1;this.isInspectMode=!1;this.showTrackers=!1;this.selectedElement=null;this.toolbarElement=null;this.panelElement=null;this.highlightElement=null;this.existingTrackers=[];this.trackerHighlights=[];this.isDragging=!1;this.dragStartX=0;this.dragStartY=0;this.toolbarStartX=0;this.toolbarStartY=0;this.mouseMoveListener=null;this.clickListener=null;this.dragMoveListener=null;this.dragEndListener=null;this.handleEscapeKey=e=>{e.key==="Escape"&&this.isInspectMode&&this.disableInspectMode()};this.tracker=e,this.sessionId=t,this.tenantId=i,this.apiUrl=r,this.config={debug:a.debug??!1},typeof window<"u"&&typeof document<"u"&&this.initialize()}async initialize(){this.log("Initializing debug agent"),await this.loadExistingTrackers(),this.showToolbar(),this.createHighlightElement(),this.showTrackers=!0,this.showTrackerHighlights(),this.showTrackersList()}async loadExistingTrackers(){try{let e=`${this.apiUrl}/v1/tenant/${encodeURIComponent(this.tenantId)}/trackers`,t=await fetch(e);t.ok&&(this.existingTrackers=await t.json(),this.log("Loaded trackers:",this.existingTrackers))}catch(e){this.log("Failed to load trackers:",e),this.existingTrackers=[]}}showToolbar(){if(this.toolbarElement)return;let e=document.createElement("div");e.id="grain-debug-toolbar",e.innerHTML=`
|
|
3
3
|
<style>
|
|
4
4
|
#grain-debug-toolbar {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@grainql/analytics-web",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.2",
|
|
4
4
|
"description": "Lightweight TypeScript SDK for sending analytics events and managing remote configurations via Grain's REST API",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|