UNPKG

@trlogic/tracker-web

Version:
2 lines 56.9 kB
import FingerprintJS from"@fingerprintjs/fingerprintjs";import Bowser from"bowser";var TrackerVariableType,CacheStrategy;!function(e){e.URL="URL",e.ELEMENT="ELEMENT",e.COOKIE="COOKIE",e.JAVASCRIPT="JAVASCRIPT",e.EVENT="EVENT",e.IP_ADDRESS="IP_ADDRESS",e.DEVICE_FINGERPRINT="DEVICE_FINGERPRINT",e.SCREEN_RESOLUTION="SCREEN_RESOLUTION",e.USER_AGENT="USER_AGENT",e.BROWSER="BROWSER",e.OS="OS",e.LANGUAGE="LANGUAGE",e.TIMEZONE="TIMEZONE",e.PLATFORM="PLATFORM",e.SDK_VERSION="SDK_VERSION",e.TIMESTAMP="TIMESTAMP",e.ELAPSED_TIME="ELAPSED_TIME",e.CURRENT_TRIGGER_NAME="CURRENT_TRIGGER_NAME",e.THEME_MODE="THEME_MODE",e.SUSPICIOUS_ACTIVITY="SUSPICIOUS_ACTIVITY",e.SUSPICIOUS_BROWSER="SUSPICIOUS_BROWSER",e.REMOTE_ACCESS_SCORE="REMOTE_ACCESS_SCORE",e.REMOTE_ACCESS_FLAGS="REMOTE_ACCESS_FLAGS",e.ENVIRONMENT_CHANGE_FLAGS="ENVIRONMENT_CHANGE_FLAGS",e.ENVIRONMENT_CHANGE_SCORE="ENVIRONMENT_CHANGE_SCORE",e.NO_MOUSE_MOVE_DURATION_MS="NO_MOUSE_MOVE_DURATION_MS",e.CLICK_ONLY_PATTERN="CLICK_ONLY_PATTERN",e.FAST_PAGE_TRANSITION="FAST_PAGE_TRANSITION",e.FORM_FILL_ANOMALY="FORM_FILL_ANOMALY",e.FORM_FILL_DURATION_MS="FORM_FILL_DURATION_MS",e.NAVIGATION_VELOCITY="NAVIGATION_VELOCITY",e.INPUT_PASTE_RATIO="INPUT_PASTE_RATIO",e.HEADLESS_INDICATOR_COUNT="HEADLESS_INDICATOR_COUNT",e.REDIRECT_DETECTED="REDIRECT_DETECTED",e.MOUSE_DYNAMICS="MOUSE_DYNAMICS",e.KEYSTROKE_DYNAMICS="KEYSTROKE_DYNAMICS",e.SCROLL_BEHAVIOR="SCROLL_BEHAVIOR",e.SESSION_BEHAVIOR="SESSION_BEHAVIOR",e.TOUCH_DYNAMICS="TOUCH_DYNAMICS",e.BEHAVIORAL_BIOMETRIC_SCORE="BEHAVIORAL_BIOMETRIC_SCORE",e.BEHAVIORAL_BIOMETRIC_FLAGS="BEHAVIORAL_BIOMETRIC_FLAGS",e.VIEW_DURATION="VIEW_DURATION",e.CUSTOM_TRIGGER_ARGS="CUSTOM_TRIGGER_ARGS",e.REACHABLE="REACHABLE"}(TrackerVariableType||(TrackerVariableType={}));class BaseDataCollector{collectIfNeeded(e,t,r,i){if(!i||BaseDataCollector.isVariableMapped(t,i))try{e.set(t,r())}catch{}}static mapVariablesToBackend(e,t){const r={},i=e.event.variableMappings;for(const[e,s]of Object.entries(i)){const i=s,n=t.get(i);i===TrackerVariableType.CUSTOM_TRIGGER_ARGS?r[e]=n&&"object"==typeof n?n[e]??null:null:r[e]=n??null}return r}static isVariableMapped(e,t){return t.some(t=>{const r=t.event.variableMappings;return Object.values(r).includes(e)})}}class CachedDataCollector extends BaseDataCollector{constructor(){super(...arguments),this.cache=new Map}collectWithCache(e,t,r,i,s){this.collectIfNeeded(e,t,()=>{const e=this.cache.get(t),s=Date.now();if(e&&s-e.timestamp<e.ttl)return e.value;const n=i();return this.cache.set(t,{value:n,timestamp:s,ttl:r}),n},s)}invalidateCache(e){this.cache.delete(e)}clearCache(){this.cache.clear()}}!function(e){e[e.FAST=1e4]="FAST",e[e.MEDIUM=6e4]="MEDIUM",e[e.SLOW=3e5]="SLOW",e[e.HEAVY=9007199254740991]="HEAVY"}(CacheStrategy||(CacheStrategy={}));class RunningStats{constructor(){this.n=0,this.m=0,this.m2=0,this.lo=1/0,this.hi=-1/0}push(e){this.n++;const t=e-this.m;this.m+=t/this.n;const r=e-this.m;this.m2+=t*r,e<this.lo&&(this.lo=e),e>this.hi&&(this.hi=e)}getMean(){return 0===this.n?0:this.m}getStddev(){return this.n<2?0:Math.sqrt(this.m2/(this.n-1))}getMin(){return 0===this.n?0:this.lo}getMax(){return 0===this.n?0:this.hi}getCount(){return this.n}reset(){this.n=0,this.m=0,this.m2=0,this.lo=1/0,this.hi=-1/0}toJSON(){return{mean:this.getMean(),stddev:this.getStddev(),min:this.getMin(),max:this.getMax(),count:this.getCount()}}}function throttle(e,t){let r=0;return(...i)=>{const s=performance.now();s-r>=t&&(r=s,e(...i))}}const MAX_PATH_SEGMENTS=20,PAUSE_THRESHOLD_MS=200,IDLE_THRESHOLD_MS=5e3,IDLE_CHECK_INTERVAL_MS=5e3,INTERACTION_WINDOW_MS=6e4,BURST_WINDOW_MS=3e3,BURST_MIN_EVENTS=5,TAP_MAX_DURATION_MS=300,SWIPE_MIN_DISPLACEMENT_PX=50,INTER_KEY_MAX_GAP_MS=2e3;class BehavioralCollector extends CachedDataCollector{constructor(){super(...arguments),this.mouseVelocityStats=new RunningStats,this.mouseAccelStats=new RunningStats,this.lastMouseSample=null,this.pathSegments=[],this.currentPathLength=0,this.currentPathStartX=0,this.currentPathStartY=0,this.angularVarianceStats=new RunningStats,this.lastAngle=null,this.lastMouseSampleTs=0,this.lastKeydownTs=0,this.lastKeyupTs=0,this.interKeyStats=new RunningStats,this.dwellTimeStats=new RunningStats,this.flightTimeStats=new RunningStats,this.pendingKeydowns=new Map,this.lastScrollY=0,this.lastScrollTs=0,this.maxScrollDepthPx=0,this.scrollSpeedStats=new RunningStats,this.scrollDirectionChanges=0,this.lastScrollDirection=0,this.scrollTotalDistance=0,this.smoothScrollCount=0,this.totalScrollSamples=0,this.lastAnyInteractionTs=Date.now(),this.idleCount=0,this.totalIdleMs=0,this.maxIdleMs=0,this.currentIdleStart=null,this.tabSwitchCount=0,this.totalHiddenMs=0,this.lastHiddenTs=null,this.interactionTimestamps=[],this.burstCount=0,this.touchSupported=!1,this.touchRadiusStats=new RunningStats,this.touchPressureStats=new RunningStats,this.swipeVelocityStats=new RunningStats,this.tapCount=0,this.swipeCount=0,this.activeTouchStart=null,this.listenersAttached=!1,this.idleCheckIntervalId=null,this.initTs=0,this.boundMouseHandler=null,this.boundKeydownHandler=null,this.boundKeyupHandler=null,this.boundScrollHandler=null,this.boundVisibilityHandler=null,this.boundTouchStartHandler=null,this.boundTouchMoveHandler=null,this.boundTouchEndHandler=null}collect(){const e=new Map,t=Date.now()-this.initTs;return this.collectWithCache(e,TrackerVariableType.MOUSE_DYNAMICS,CacheStrategy.FAST,()=>this.computeMouseDynamics()),this.collectWithCache(e,TrackerVariableType.KEYSTROKE_DYNAMICS,CacheStrategy.FAST,()=>this.computeKeystrokeDynamics()),this.collectWithCache(e,TrackerVariableType.SCROLL_BEHAVIOR,CacheStrategy.FAST,()=>this.computeScrollBehavior()),this.collectWithCache(e,TrackerVariableType.SESSION_BEHAVIOR,CacheStrategy.FAST,()=>this.computeSessionBehavior()),this.touchSupported?this.collectWithCache(e,TrackerVariableType.TOUCH_DYNAMICS,CacheStrategy.FAST,()=>this.computeTouchDynamics()):this.collectIfNeeded(e,TrackerVariableType.TOUCH_DYNAMICS,()=>null),this.collectIfNeeded(e,TrackerVariableType.BEHAVIORAL_BIOMETRIC_SCORE,()=>this.computeBiometricScore(t)),this.collectIfNeeded(e,TrackerVariableType.BEHAVIORAL_BIOMETRIC_FLAGS,()=>this.computeBiometricFlags(t)),e}init(){this.initTs=Date.now(),this.lastAnyInteractionTs=Date.now(),this.touchSupported="ontouchstart"in window,this.attachListeners(),this.startIdleCheck()}destroy(){this.detachListeners(),this.stopIdleCheck()}resetRollingWindow(){const e=Date.now()-6e4;this.interactionTimestamps=this.interactionTimestamps.filter(t=>t>=e)}computeMouseDynamics(){const e=this.mouseVelocityStats,t=this.mouseAccelStats;let r=0;if(this.pathSegments.length>0){let e=0;for(const t of this.pathSegments)e+=t.pathLength>0?t.displacement/t.pathLength:0;r=e/this.pathSegments.length}const i=this.angularVarianceStats,s=i.getCount()>1?Math.min(1,i.getStddev()/Math.PI):0;return{velocity:{mean:e.getMean(),max:e.getMax(),stddev:e.getStddev()},acceleration:{mean:t.getMean(),max:t.getMax()},straightness:r,jitter:s,sampleCount:e.getCount()}}computeKeystrokeDynamics(){const e=this.interKeyStats,t=e.getMean(),r=e.getStddev(),i=t>0&&e.getCount()>2?Math.max(0,Math.min(1,1-r/t)):0;return{interKeyDelay:{mean:t,stddev:r,min:e.getMin(),max:e.getMax()},dwellTime:{mean:this.dwellTimeStats.getMean(),stddev:this.dwellTimeStats.getStddev()},flightTime:{mean:this.flightTimeStats.getMean(),stddev:this.flightTimeStats.getStddev()},rhythmConsistency:i,sampleCount:e.getCount()}}computeScrollBehavior(){const e=document.documentElement.scrollHeight-window.innerHeight;return{maxDepthPercent:e>0?Math.min(100,this.maxScrollDepthPx/e*100):0,avgSpeed:this.scrollSpeedStats.getMean(),directionChanges:this.scrollDirectionChanges,totalDistance:this.scrollTotalDistance,smoothness:this.totalScrollSamples>0?this.smoothScrollCount/this.totalScrollSamples:0,sampleCount:this.totalScrollSamples}}computeSessionBehavior(){const e=Date.now()-6e4,t=this.interactionTimestamps.filter(t=>t>=e).length;return{idle:{count:this.idleCount,totalMs:this.totalIdleMs,maxMs:this.maxIdleMs},focus:{tabSwitchCount:this.tabSwitchCount,totalHiddenMs:this.totalHiddenMs},tempo:{interactionsPerMinute:t,burstCount:this.burstCount}}}computeTouchDynamics(){return{avgTouchRadius:this.touchRadiusStats.getMean(),avgPressure:this.touchPressureStats.getMean(),swipeVelocity:{mean:this.swipeVelocityStats.getMean(),max:this.swipeVelocityStats.getMax()},tapCount:this.tapCount,swipeCount:this.swipeCount,sampleCount:this.touchRadiusStats.getCount()}}computeBiometricScore(e){let t=0;if(e<5e3)return 0;const r=this.computeMouseDynamics();let i=0;r.sampleCount>10&&(r.straightness>.95&&(i+=.4),r.jitter<.05&&(i+=.3),r.velocity.stddev<.01&&r.velocity.mean>0&&(i+=.3)),t+=.25*Math.min(1,i);const s=this.computeKeystrokeDynamics();let n=0;s.sampleCount>10&&(s.rhythmConsistency>.9&&(n+=.5),s.dwellTime.mean<10&&s.dwellTime.mean>0&&(n+=.5)),t+=.2*Math.min(1,n);const o=this.computeScrollBehavior();let a=0;o.sampleCount>3&&(o.smoothness<.2&&(a+=.5),0===o.directionChanges&&o.totalDistance>500&&(a+=.5)),t+=.15*Math.min(1,a);const l=this.computeSessionBehavior();let c=0;if(e>6e4&&(0===l.idle.count&&(c+=.3),0===l.focus.tabSwitchCount&&(c+=.2)),l.tempo.interactionsPerMinute>200?c+=.5:l.tempo.interactionsPerMinute<1&&e>3e4&&(c+=.3),t+=.15*Math.min(1,c),this.touchSupported){const e=this.computeTouchDynamics();let r=0;e.sampleCount>5&&(0===e.avgPressure&&(r+=.5),0===e.avgTouchRadius&&(r+=.5)),t+=.1*Math.min(1,r)}let h=0;return e>3e4&&(0!==r.sampleCount||this.touchSupported||(h+=.33),0===s.sampleCount&&(h+=.33),0===o.sampleCount&&(h+=.34)),t+=.15*Math.min(1,h),Math.min(1,t)}computeBiometricFlags(e){const t=this.computeMouseDynamics(),r=this.computeKeystrokeDynamics(),i=this.computeScrollBehavior(),s=this.computeSessionBehavior();return{perfectMouseLinearity:t.sampleCount>10&&t.straightness>.95,zeroMouseJitter:t.sampleCount>10&&t.jitter<.05,uniformKeystrokeTiming:r.sampleCount>10&&r.rhythmConsistency>.9,noKeystrokeData:0===r.sampleCount&&e>3e4,programmaticScrolling:i.sampleCount>3&&i.smoothness<.2,noIdlePeriods:0===s.idle.count&&e>6e4,noTouchPressure:this.touchSupported&&this.touchPressureStats.getCount()>5&&0===this.touchPressureStats.getMean(),abnormalTempo:s.tempo.interactionsPerMinute>200||s.tempo.interactionsPerMinute<1&&e>3e4}}attachListeners(){this.listenersAttached||(this.listenersAttached=!0,this.boundMouseHandler=throttle(e=>{this.onMouseSample(e.clientX,e.clientY,performance.now()),this.recordInteraction()},50),window.addEventListener("mousemove",this.boundMouseHandler,{passive:!0}),this.boundKeydownHandler=e=>{if(e.repeat)return;const t=performance.now();if(this.lastKeydownTs>0){const e=t-this.lastKeydownTs;e<2e3&&this.interKeyStats.push(e)}if(this.lastKeydownTs=t,this.lastKeyupTs>0){const e=t-this.lastKeyupTs;e<2e3&&this.flightTimeStats.push(e)}this.pendingKeydowns.set(e.code,t),this.recordInteraction()},document.addEventListener("keydown",this.boundKeydownHandler,{passive:!0}),this.boundKeyupHandler=e=>{const t=performance.now();this.lastKeyupTs=t;const r=this.pendingKeydowns.get(e.code);if(void 0!==r){const i=t-r;this.dwellTimeStats.push(i),this.pendingKeydowns.delete(e.code)}},document.addEventListener("keyup",this.boundKeyupHandler,{passive:!0}),this.boundScrollHandler=throttle(()=>{this.onScrollSample(),this.recordInteraction()},200),window.addEventListener("scroll",this.boundScrollHandler,{passive:!0}),this.boundVisibilityHandler=()=>{const e=Date.now();document.hidden?this.lastHiddenTs=e:(null!==this.lastHiddenTs&&(this.totalHiddenMs+=e-this.lastHiddenTs,this.lastHiddenTs=null),this.tabSwitchCount++)},document.addEventListener("visibilitychange",this.boundVisibilityHandler),this.touchSupported&&(this.boundTouchStartHandler=e=>{const t=e.touches[0];t&&(this.activeTouchStart={x:t.clientX,y:t.clientY,t:performance.now()},this.touchRadiusStats.push((t.radiusX+t.radiusY)/2),t.force>0&&this.touchPressureStats.push(t.force),this.recordInteraction())},document.addEventListener("touchstart",this.boundTouchStartHandler,{passive:!0}),this.boundTouchMoveHandler=throttle(e=>{const t=e.touches[0];t&&(this.touchRadiusStats.push((t.radiusX+t.radiusY)/2),t.force>0&&this.touchPressureStats.push(t.force))},100),document.addEventListener("touchmove",this.boundTouchMoveHandler,{passive:!0}),this.boundTouchEndHandler=e=>{if(!this.activeTouchStart)return;const t=e.changedTouches[0];if(!t)return;const r=performance.now()-this.activeTouchStart.t,i=t.clientX-this.activeTouchStart.x,s=t.clientY-this.activeTouchStart.y,n=Math.sqrt(i*i+s*s);if(r<300&&n<50)this.tapCount++;else if(n>=50){this.swipeCount++;const e=n/r;this.swipeVelocityStats.push(e)}this.activeTouchStart=null},document.addEventListener("touchend",this.boundTouchEndHandler,{passive:!0})))}detachListeners(){this.boundMouseHandler&&window.removeEventListener("mousemove",this.boundMouseHandler),this.boundKeydownHandler&&document.removeEventListener("keydown",this.boundKeydownHandler),this.boundKeyupHandler&&document.removeEventListener("keyup",this.boundKeyupHandler),this.boundScrollHandler&&window.removeEventListener("scroll",this.boundScrollHandler),this.boundVisibilityHandler&&document.removeEventListener("visibilitychange",this.boundVisibilityHandler),this.boundTouchStartHandler&&document.removeEventListener("touchstart",this.boundTouchStartHandler),this.boundTouchMoveHandler&&document.removeEventListener("touchmove",this.boundTouchMoveHandler),this.boundTouchEndHandler&&document.removeEventListener("touchend",this.boundTouchEndHandler),this.listenersAttached=!1}onMouseSample(e,t,r){if(this.lastMouseSample){const i=r-this.lastMouseSample.t;if(i<=0)return;const s=e-this.lastMouseSample.x,n=t-this.lastMouseSample.y,o=Math.sqrt(s*s+n*n),a=o/i;this.mouseVelocityStats.push(a);const l=Math.abs(a-this.lastMouseSample.velocity)/i;this.mouseAccelStats.push(l);if(r-this.lastMouseSampleTs>200&&this.currentPathLength>0){const r=this.lastMouseSample.x-this.currentPathStartX,i=this.lastMouseSample.y-this.currentPathStartY,s=Math.sqrt(r*r+i*i);this.pushPathSegment({displacement:s,pathLength:this.currentPathLength}),this.currentPathLength=0,this.currentPathStartX=e,this.currentPathStartY=t}else 0===this.currentPathLength&&(this.currentPathStartX=this.lastMouseSample.x,this.currentPathStartY=this.lastMouseSample.y);if(this.currentPathLength+=o,o>1){const e=Math.atan2(n,s);if(null!==this.lastAngle){let t=Math.abs(e-this.lastAngle);t>Math.PI&&(t=2*Math.PI-t),this.angularVarianceStats.push(t)}this.lastAngle=e}this.lastMouseSample={x:e,y:t,t:r,velocity:a}}else this.lastMouseSample={x:e,y:t,t:r,velocity:0},this.currentPathStartX=e,this.currentPathStartY=t;this.lastMouseSampleTs=r}pushPathSegment(e){this.pathSegments.length>=20&&this.pathSegments.shift(),this.pathSegments.push(e)}onScrollSample(){const e=window.scrollY,t=performance.now();if(e>this.maxScrollDepthPx&&(this.maxScrollDepthPx=e),this.lastScrollTs>0){const r=t-this.lastScrollTs,i=e-this.lastScrollY,s=Math.abs(i);if(r>0&&s>0){const e=s/r;this.scrollSpeedStats.push(e),this.scrollTotalDistance+=s,this.totalScrollSamples++,s<100&&this.smoothScrollCount++;const t=i>0?1:-1;0!==this.lastScrollDirection&&t!==this.lastScrollDirection&&this.scrollDirectionChanges++,this.lastScrollDirection=t}}this.lastScrollY=e,this.lastScrollTs=t}recordInteraction(){const e=Date.now();if(this.lastAnyInteractionTs=e,this.interactionTimestamps.push(e),null!==this.currentIdleStart){const t=e-this.currentIdleStart;this.totalIdleMs+=t,t>this.maxIdleMs&&(this.maxIdleMs=t),this.idleCount++,this.currentIdleStart=null}const t=e-3e3;if(5===this.interactionTimestamps.filter(e=>e>=t).length&&this.burstCount++,this.interactionTimestamps.length>500){const t=e-6e4;this.interactionTimestamps=this.interactionTimestamps.filter(e=>e>=t)}}startIdleCheck(){this.idleCheckIntervalId=setInterval(()=>{Date.now()-this.lastAnyInteractionTs>5e3&&null===this.currentIdleStart&&(this.currentIdleStart=this.lastAnyInteractionTs)},5e3)}stopIdleCheck(){null!==this.idleCheckIntervalId&&(clearInterval(this.idleCheckIntervalId),this.idleCheckIntervalId=null)}}const SDK_VERSION="2.0.0";class BrowserDetector{static getParser(){return BrowserDetector.parser||(BrowserDetector.parser=Bowser.getParser(navigator.userAgent)),BrowserDetector.parser}static getBrowser(){const e=BrowserDetector.getParser().getBrowser();return{name:e.name??"Unknown",version:e.version??"Unknown"}}static getOS(){const e=BrowserDetector.getParser().getOS();return{name:e.name??"Unknown",version:e.version??"Unknown"}}static getPlatform(){const e=BrowserDetector.getParser().getPlatformType();return"mobile"===e?"mobile":"tablet"===e?"tablet":"desktop"}static getLanguage(){return navigator.language||"unknown"}static getTimezone(){try{return Intl.DateTimeFormat().resolvedOptions().timeZone}catch{return"unknown"}}static isSuspiciousBrowser(){const e=navigator.userAgent.toLowerCase();return["bot","crawler","spider","headless","phantom","selenium","puppeteer","playwright","webdriver","electron"].some(t=>e.includes(t))}static getHeadlessIndicatorCount(){let e=0;navigator.webdriver&&e++,navigator.languages&&0!==navigator.languages.length||e++,navigator.plugins&&0!==navigator.plugins.length||e++,BrowserDetector.isSuspiciousBrowser()&&e++;try{const t=document.createElement("canvas");t.getContext("webgl")||t.getContext("experimental-webgl")||e++}catch{e++}return 0!==screen.width&&0!==screen.height||e++,e}static reset(){BrowserDetector.parser=null}}BrowserDetector.parser=null;class DeviceCollector extends CachedDataCollector{constructor(){super(...arguments),this.fingerprintPromise=null,this.resolvedFingerprint=null,this.initTimestamp=Date.now()}collect(){const e=new Map;return this.collectWithCache(e,TrackerVariableType.DEVICE_FINGERPRINT,CacheStrategy.HEAVY,()=>this.getFingerprint()),this.collectWithCache(e,TrackerVariableType.SCREEN_RESOLUTION,CacheStrategy.HEAVY,()=>`${screen.width}x${screen.height}`),this.collectWithCache(e,TrackerVariableType.USER_AGENT,CacheStrategy.HEAVY,()=>navigator.userAgent),this.collectWithCache(e,TrackerVariableType.BROWSER,CacheStrategy.HEAVY,()=>{const e=BrowserDetector.getBrowser();return`${e.name} ${e.version}`}),this.collectWithCache(e,TrackerVariableType.OS,CacheStrategy.HEAVY,()=>{const e=BrowserDetector.getOS();return`${e.name} ${e.version}`}),this.collectWithCache(e,TrackerVariableType.LANGUAGE,CacheStrategy.MEDIUM,()=>BrowserDetector.getLanguage()),this.collectWithCache(e,TrackerVariableType.TIMEZONE,CacheStrategy.MEDIUM,()=>BrowserDetector.getTimezone()),this.collectWithCache(e,TrackerVariableType.PLATFORM,CacheStrategy.HEAVY,()=>BrowserDetector.getPlatform()),this.collectWithCache(e,TrackerVariableType.SUSPICIOUS_BROWSER,CacheStrategy.SLOW,()=>BrowserDetector.isSuspiciousBrowser()),this.collectWithCache(e,TrackerVariableType.HEADLESS_INDICATOR_COUNT,CacheStrategy.SLOW,()=>BrowserDetector.getHeadlessIndicatorCount()),this.collectWithCache(e,TrackerVariableType.THEME_MODE,CacheStrategy.MEDIUM,()=>window.matchMedia?.("(prefers-color-scheme: dark)").matches?"DARK":"LIGHT"),this.collectIfNeeded(e,TrackerVariableType.SDK_VERSION,()=>"2.0.0"),this.collectIfNeeded(e,TrackerVariableType.TIMESTAMP,()=>Date.now()),this.collectIfNeeded(e,TrackerVariableType.ELAPSED_TIME,()=>Date.now()-this.initTimestamp),e}getFingerprint(){return this.resolvedFingerprint?this.resolvedFingerprint:(this.fingerprintPromise||(this.fingerprintPromise=FingerprintJS.load().then(e=>e.get()).then(e=>(this.resolvedFingerprint=e.visitorId,e.visitorId)).catch(()=>(this.resolvedFingerprint="unavailable","unavailable"))),this.fingerprintPromise)}async initFingerprint(){const e=this.getFingerprint();if(e instanceof Promise){const t=await e;return this.resolvedFingerprint=t,this.invalidateCache(TrackerVariableType.DEVICE_FINGERPRINT),t}return e}}class Logger{static setEnabled(e){Logger.enabled=e}static debug(...e){Logger.enabled&&console.debug(Logger.PREFIX,...e)}static info(...e){Logger.enabled&&console.info(Logger.PREFIX,...e)}static warn(...e){Logger.enabled&&console.warn(Logger.PREFIX,...e)}static error(...e){Logger.enabled&&console.error(Logger.PREFIX,...e)}}Logger.PREFIX="[tracker]",Logger.enabled=!1;class HttpError extends Error{constructor(e,t){super(e),this.status=t,this.name="HttpError"}}class HttpClient{static async get(e,t){return HttpClient.request(e,{method:"GET",...t})}static async post(e,t,r){return HttpClient.request(e,{method:"POST",body:t,...r})}static async request(e,t){const r=new AbortController,i=t.timeout??3e4,s=setTimeout(()=>r.abort(),i);try{const i={...t.headers};void 0!==t.body&&(i["Content-Type"]="application/json");const s={method:t.method,headers:i,signal:r.signal};void 0!==t.body&&(s.body=JSON.stringify(t.body));const n=await fetch(e,s);if(!n.ok)throw new HttpError(`HTTP Error ${n.status}: ${n.statusText}`,n.status);return(n.headers.get("content-type")??"").includes("application/json")?await n.json():await n.text()}catch(e){if(e instanceof HttpError)throw e;if(e instanceof DOMException&&"AbortError"===e.name)throw new HttpError(`Request timeout after ${i}ms`,408);throw Logger.error("HTTP request failed:",e),e}finally{clearTimeout(s)}}}const IP_API_URL="https://api.ipify.org?format=json";class NetworkCollector extends CachedDataCollector{constructor(){super(...arguments),this.resolvedIp=null}collect(){const e=new Map;return this.collectWithCache(e,TrackerVariableType.IP_ADDRESS,CacheStrategy.SLOW,()=>this.resolvedIp??"unknown"),this.collectIfNeeded(e,TrackerVariableType.REACHABLE,()=>navigator.onLine),e}async fetchIp(){try{const e=await HttpClient.get(IP_API_URL,{timeout:5e3});return this.resolvedIp=e.ip,this.resolvedIp}catch(e){return Logger.warn("Failed to fetch IP address:",e),this.resolvedIp="unknown",this.resolvedIp}}}class ScreenActivityCollector extends BaseDataCollector{constructor(){super(...arguments),this.viewDurationMs=0,this.timerIntervalId=null}collect(){const e=new Map;return this.collectIfNeeded(e,TrackerVariableType.VIEW_DURATION,()=>this.viewDurationMs),e}resolveVariable(e,t){try{switch(e.type){case TrackerVariableType.URL:return this.resolveUrl(e.option);case TrackerVariableType.ELEMENT:return this.resolveElement(e.option);case TrackerVariableType.COOKIE:return this.resolveCookie(e.option);case TrackerVariableType.JAVASCRIPT:return this.resolveJavascript(e.option);case TrackerVariableType.EVENT:return this.resolveEvent(e,t);default:return null}}catch(t){return Logger.warn(`Failed to resolve variable ${e.name}:`,t),null}}startTimer(){null===this.timerIntervalId&&(this.viewDurationMs=0,this.timerIntervalId=setInterval(()=>{this.viewDurationMs+=100},100))}stopTimer(){null!==this.timerIntervalId&&(clearInterval(this.timerIntervalId),this.timerIntervalId=null)}resetTimer(){this.viewDurationMs=0}resolveUrl(e){const t=e?.selection??"full",r=new URL(window.location.href);switch(t){case"host":return r.hostname;case"port":return r.port;case"path":return r.pathname;case"query":return r.search;case"fragment":return r.hash;case"protocol":return r.protocol;default:return r.href}}resolveElement(e){if(!e?.cssSelector)return null;const t=document.querySelector(e.cssSelector);return t?e.attribute?t.getAttribute(e.attribute):t.textContent:null}resolveCookie(e){if(!e?.cookieName)return null;const t=document.cookie.split(";");for(const r of t){const[t,...i]=r.split("=");if(t.trim()===e.cookieName){const t=i.join("=").trim();return e.decodeUrlCookie?decodeURIComponent(t):t}}return null}resolveJavascript(e){if(!e?.code)return null;try{return new Function(`return (${e.code})`)()}catch(e){return Logger.warn("JavaScript variable evaluation failed:",e),null}}resolveEvent(e,t){return t?t[e.name]??null:null}resolveConfigVariables(e,t){const r=new Map;for(const i of e.variables){const e=i.type;if(e===TrackerVariableType.URL||e===TrackerVariableType.ELEMENT||e===TrackerVariableType.COOKIE||e===TrackerVariableType.JAVASCRIPT||e===TrackerVariableType.EVENT){const s=this.resolveVariable(i,t);null!=s&&r.set(e,s)}}return r}destroy(){this.stopTimer()}}const BASELINE_KEY_PREFIX="formica_baseline_";class SecurityCollector extends CachedDataCollector{constructor(){super(...arguments),this.suspiciousFlags={unnaturalMouseMoves:!1,bigClipboardPaste:!1,lowFPSDetected:!1,delayedClickDetected:!1},this.lastMouseMoveTs=Date.now(),this.mouseMoveCounterWindow=0,this.clickCounterWindow=0,this.pageViewTimestamps=[],this.pasteCharCount=0,this.typedCharCount=0,this.lastPageViewTs=null,this.lastFormInteractionStart=null,this.lastFormFieldCount=0,this.lastFormDurationMs=0,this.formFillAnomaly=!1,this.fastPageTransition=!1,this.redirectSource=null,this.lastUrl=null,this.lastMouseX=null,this.lastMouseY=null,this.unnaturalMoveCount=0,this.lowFPSCount=0,this.lastFrameTs=0,this.fpsRafId=null,this.listenersAttached=!1,this.baseline=null,this.envChangeFlags=null,this.envChangeScore=0,this.tenant="",this.deviceId=""}collect(){const e=new Map,t=this.getAggregates();this.collectWithCache(e,TrackerVariableType.SUSPICIOUS_ACTIVITY,CacheStrategy.FAST,()=>({...this.suspiciousFlags})),this.collectIfNeeded(e,TrackerVariableType.NO_MOUSE_MOVE_DURATION_MS,()=>t.noMouseMoveDurationMs),this.collectIfNeeded(e,TrackerVariableType.CLICK_ONLY_PATTERN,()=>t.clickOnlyPattern),this.collectIfNeeded(e,TrackerVariableType.FAST_PAGE_TRANSITION,()=>t.fastPageTransition),this.collectIfNeeded(e,TrackerVariableType.NAVIGATION_VELOCITY,()=>t.navigationVelocity),this.collectIfNeeded(e,TrackerVariableType.FORM_FILL_ANOMALY,()=>t.formFillAnomaly),this.collectIfNeeded(e,TrackerVariableType.FORM_FILL_DURATION_MS,()=>t.formFillDurationMs),this.collectIfNeeded(e,TrackerVariableType.INPUT_PASTE_RATIO,()=>t.inputPasteRatio),this.collectIfNeeded(e,TrackerVariableType.REDIRECT_DETECTED,()=>t.redirectSource),this.collectIfNeeded(e,TrackerVariableType.ENVIRONMENT_CHANGE_FLAGS,()=>this.envChangeFlags?{...this.envChangeFlags}:null),this.collectIfNeeded(e,TrackerVariableType.ENVIRONMENT_CHANGE_SCORE,()=>this.envChangeScore);const r=BrowserDetector.getHeadlessIndicatorCount(),i=this.calcRemoteAccessScore(r);return this.collectIfNeeded(e,TrackerVariableType.REMOTE_ACCESS_SCORE,()=>i),this.collectIfNeeded(e,TrackerVariableType.REMOTE_ACCESS_FLAGS,()=>({...this.suspiciousFlags,clickOnlyPattern:t.clickOnlyPattern,headlessIndicatorCount:r,environmentMismatch:this.envChangeScore>0})),e}init(e,t){this.tenant=e,this.deviceId=t,this.attachListeners(),this.ensureBaseline()}registerPageView(){const e=Date.now();this.fastPageTransition=null!==this.lastPageViewTs&&e-this.lastPageViewTs<1500,this.lastPageViewTs=e,this.pageViewTimestamps.push(e);const t=e-3e5;this.pageViewTimestamps=this.pageViewTimestamps.filter(e=>e>=t),this.detectRedirect()}getRemoteAccessScore(){return this.calcRemoteAccessScore(BrowserDetector.getHeadlessIndicatorCount())}resetRollingWindow(){this.mouseMoveCounterWindow=0,this.clickCounterWindow=0}updateEnvironmentDiff(){if(!this.baseline)return;const e=this.getCurrentEnvironment();this.envChangeFlags=this.diffEnvironment(this.baseline,e),this.envChangeScore=this.calcEnvironmentChangeScore(this.envChangeFlags)}getAggregates(){const e=Date.now()-this.lastMouseMoveTs,t=e>1e4&&this.clickCounterWindow>=3&&this.mouseMoveCounterWindow<2,r=this.pageViewTimestamps.length/5,i=this.computePasteRatio();return{noMouseMoveDurationMs:e,clickOnlyPattern:t,fastPageTransition:this.fastPageTransition,navigationVelocity:r,formFillAnomaly:this.formFillAnomaly,formFillDurationMs:this.lastFormDurationMs,inputPasteRatio:i,redirectSource:this.redirectSource}}computePasteRatio(){const e=this.pasteCharCount+this.typedCharCount;return 0===e?0:this.pasteCharCount/e}calcRemoteAccessScore(e){const t=this.getAggregates();let r=0;return this.suspiciousFlags.unnaturalMouseMoves&&(r+=.2),this.suspiciousFlags.bigClipboardPaste&&(r+=.15),this.suspiciousFlags.lowFPSDetected&&(r+=.15),this.suspiciousFlags.delayedClickDetected&&(r+=.1),t.clickOnlyPattern&&(r+=.15),e>0&&(r+=Math.min(.05*e,.25)),this.envChangeScore>0&&(r+=.2),Math.min(1,r)}attachListeners(){if(this.listenersAttached)return;this.listenersAttached=!0,window.addEventListener("mousemove",e=>{if(this.lastMouseMoveTs=Date.now(),this.mouseMoveCounterWindow++,null!==this.lastMouseX&&null!==this.lastMouseY){const t=Math.abs(e.clientX-this.lastMouseX),r=Math.abs(e.clientY-this.lastMouseY);(t>100||r>100||0===t&&0===r)&&this.unnaturalMoveCount++,this.unnaturalMoveCount>5&&(this.suspiciousFlags.unnaturalMouseMoves=!0)}this.lastMouseX=e.clientX,this.lastMouseY=e.clientY}),document.addEventListener("click",()=>{this.clickCounterWindow++;const e=performance.now();setTimeout(()=>{performance.now()-e>150&&(this.suspiciousFlags.delayedClickDetected=!0)},0)}),document.addEventListener("paste",e=>{const t=e,r=t.clipboardData?.getData("text")??"";this.pasteCharCount+=r.length,r.length>5e3&&(this.suspiciousFlags.bigClipboardPaste=!0)}),document.addEventListener("keydown",e=>{1===e.key.length&&this.typedCharCount++}),document.addEventListener("focusin",e=>{const t=e.target?.closest("form");t&&null===this.lastFormInteractionStart&&(this.lastFormInteractionStart=Date.now(),this.lastFormFieldCount=t.querySelectorAll("input,textarea,select").length)}),document.addEventListener("submit",e=>{e.target&&(this.lastFormInteractionStart&&(this.lastFormDurationMs=Date.now()-this.lastFormInteractionStart,this.formFillAnomaly=this.detectFormAnomaly(this.lastFormDurationMs,this.lastFormFieldCount)),this.lastFormInteractionStart=null)}),this.lastFrameTs=performance.now();const e=()=>{const t=performance.now();t-this.lastFrameTs>250&&this.lowFPSCount++,this.lowFPSCount>10&&(this.suspiciousFlags.lowFPSDetected=!0),this.lastFrameTs=t,this.fpsRafId=requestAnimationFrame(e)};this.fpsRafId=requestAnimationFrame(e)}detectFormAnomaly(e,t){if(0===t)return!1;return e<Math.min(120*t,1500)}detectRedirect(){try{const e=window.location.href;if(this.lastUrl&&this.lastUrl!==e)if(document.referrer&&document.referrer!==e)try{const t=new URL(document.referrer),r=new URL(e);this.redirectSource=t.origin!==r.origin?t.origin:"internal"}catch{this.redirectSource=document.referrer}else document.referrer?this.redirectSource=null:this.redirectSource="direct";else this.redirectSource=null;this.lastUrl=e}catch{this.redirectSource=null}}ensureBaseline(){try{const e=`formica_baseline_${this.tenant}_${this.deviceId}`,t=localStorage.getItem(e);if(t)this.baseline=JSON.parse(t);else{const t=this.getCurrentEnvironment();this.baseline={...t,firstSeen:Date.now()},localStorage.setItem(e,JSON.stringify(this.baseline))}this.updateEnvironmentDiff()}catch{Logger.warn("localStorage unavailable for environment baseline")}}getCurrentEnvironment(){const e=BrowserDetector.getBrowser(),t=BrowserDetector.getOS();return{language:BrowserDetector.getLanguage(),timezone:BrowserDetector.getTimezone(),os:`${t.name} ${t.version}`,browser:`${e.name} ${e.version}`}}diffEnvironment(e,t){return{languageChanged:e.language!==t.language,timezoneChanged:e.timezone!==t.timezone,osChanged:e.os!==t.os,browserChanged:e.browser!==t.browser}}calcEnvironmentChangeScore(e){let t=0;return e.languageChanged&&(t+=.25),e.timezoneChanged&&(t+=.25),e.osChanged&&(t+=.2),e.browserChanged&&(t+=.3),Math.min(1,t)}destroy(){null!==this.fpsRafId&&(cancelAnimationFrame(this.fpsRafId),this.fpsRafId=null)}}var TrackerEventType;!function(e){e.PAGE_VIEW="PAGE_VIEW",e.CLICK="CLICK",e.SCROLL="SCROLL",e.CUSTOM="CUSTOM",e.RISK_ALERT="RISK_ALERT",e.VISIBILITY_CHANGE="VISIBILITY_CHANGE",e.IDLE="IDLE",e.FORM_INTERACTION="FORM_INTERACTION",e.COPY_PASTE="COPY_PASTE"}(TrackerEventType||(TrackerEventType={}));class ClickListener{constructor(e,t=1e3){this.started=!1,this.boundHandler=null,this.lastTriggerTs=0,this.trigger=e,this.cooldownMs=t}start(){this.started||(this.started=!0,this.boundHandler=()=>{const e=Date.now();e-this.lastTriggerTs>=this.cooldownMs&&(this.lastTriggerTs=e,this.trigger(TrackerEventType.CLICK))},document.addEventListener("click",this.boundHandler))}stop(){this.started&&(this.started=!1,this.boundHandler&&(document.removeEventListener("click",this.boundHandler),this.boundHandler=null))}getTypes(){return[TrackerEventType.CLICK]}getVariableTypes(){return[TrackerVariableType.ELEMENT]}}class CopyPasteListener{constructor(e,t=2e3){this.started=!1,this.boundHandler=null,this.lastTriggerTs=0,this.trigger=e,this.cooldownMs=t}start(){this.started||(this.started=!0,this.boundHandler=()=>{const e=Date.now();e-this.lastTriggerTs>=this.cooldownMs&&(this.lastTriggerTs=e,this.trigger(TrackerEventType.COPY_PASTE))},document.addEventListener("paste",this.boundHandler))}stop(){this.started&&(this.started=!1,this.boundHandler&&(document.removeEventListener("paste",this.boundHandler),this.boundHandler=null))}getTypes(){return[TrackerEventType.COPY_PASTE]}getVariableTypes(){return[TrackerVariableType.INPUT_PASTE_RATIO]}}class FormInteractionListener{constructor(e,t=3e3){this.started=!1,this.boundFocusHandler=null,this.boundSubmitHandler=null,this.lastTriggerTs=0,this.trigger=e,this.cooldownMs=t}start(){this.started||(this.started=!0,this.boundFocusHandler=e=>{const t=e.target;if(!t)return;(t instanceof HTMLInputElement||t instanceof HTMLTextAreaElement||t instanceof HTMLSelectElement)&&this.triggerWithCooldown()},this.boundSubmitHandler=()=>{this.triggerWithCooldown()},document.addEventListener("focusin",this.boundFocusHandler,{passive:!0}),document.addEventListener("submit",this.boundSubmitHandler,{passive:!0}))}stop(){this.started&&(this.started=!1,this.boundFocusHandler&&(document.removeEventListener("focusin",this.boundFocusHandler),this.boundFocusHandler=null),this.boundSubmitHandler&&(document.removeEventListener("submit",this.boundSubmitHandler),this.boundSubmitHandler=null))}getTypes(){return[TrackerEventType.FORM_INTERACTION]}getVariableTypes(){return[TrackerVariableType.FORM_FILL_ANOMALY,TrackerVariableType.FORM_FILL_DURATION_MS]}triggerWithCooldown(){const e=Date.now();e-this.lastTriggerTs>=this.cooldownMs&&(this.lastTriggerTs=e,this.trigger(TrackerEventType.FORM_INTERACTION))}}const ACTIVITY_EVENTS=["mousemove","keydown","scroll","touchstart","click"];class IdleListener{constructor(e,t=3e4,r=6e4){this.started=!1,this.idleTimer=null,this.boundActivityHandler=null,this.lastTriggerTs=0,this.trigger=e,this.idleThresholdMs=t,this.cooldownMs=r}start(){if(!this.started){this.started=!0,this.boundActivityHandler=()=>this.resetTimer();for(const e of ACTIVITY_EVENTS)document.addEventListener(e,this.boundActivityHandler,{passive:!0});this.resetTimer()}}stop(){if(this.started&&(this.started=!1,this.idleTimer&&(clearTimeout(this.idleTimer),this.idleTimer=null),this.boundActivityHandler)){for(const e of ACTIVITY_EVENTS)document.removeEventListener(e,this.boundActivityHandler);this.boundActivityHandler=null}}getTypes(){return[TrackerEventType.IDLE]}getVariableTypes(){return[]}resetTimer(){this.idleTimer&&clearTimeout(this.idleTimer),this.idleTimer=setTimeout(()=>{const e=Date.now();e-this.lastTriggerTs>=this.cooldownMs&&(this.lastTriggerTs=e,this.trigger(TrackerEventType.IDLE))},this.idleThresholdMs)}}class PageViewListener{constructor(e){this.started=!1,this.boundPopstate=null,this.origPushState=null,this.origReplaceState=null,this.trigger=e}start(){if(this.started)return;this.started=!0,this.boundPopstate=()=>this.trigger(TrackerEventType.PAGE_VIEW),window.addEventListener("popstate",this.boundPopstate),this.origPushState=history.pushState.bind(history),this.origReplaceState=history.replaceState.bind(history);const e=this.origPushState,t=this.origReplaceState,r=()=>this.trigger(TrackerEventType.PAGE_VIEW);history.pushState=function(...t){e&&e(...t),r()},history.replaceState=function(...e){t&&t(...e),r()}}stop(){this.started&&(this.started=!1,this.boundPopstate&&(window.removeEventListener("popstate",this.boundPopstate),this.boundPopstate=null),this.origPushState&&(history.pushState=this.origPushState,this.origPushState=null),this.origReplaceState&&(history.replaceState=this.origReplaceState,this.origReplaceState=null))}getTypes(){return[TrackerEventType.PAGE_VIEW]}getVariableTypes(){return[TrackerVariableType.URL,TrackerVariableType.VIEW_DURATION]}}class ScrollListener{constructor(e,t=800,r=2e3){this.started=!1,this.boundHandler=null,this.debounceTimer=null,this.lastTriggerTs=0,this.trigger=e,this.debounceMs=t,this.cooldownMs=r}start(){this.started||(this.started=!0,this.boundHandler=()=>{this.debounceTimer&&clearTimeout(this.debounceTimer),this.debounceTimer=setTimeout(()=>{const e=Date.now();e-this.lastTriggerTs>=this.cooldownMs&&(this.lastTriggerTs=e,this.trigger(TrackerEventType.SCROLL))},this.debounceMs)},window.addEventListener("scroll",this.boundHandler,{passive:!0}))}stop(){this.started&&(this.started=!1,this.boundHandler&&(window.removeEventListener("scroll",this.boundHandler),this.boundHandler=null),this.debounceTimer&&(clearTimeout(this.debounceTimer),this.debounceTimer=null))}getTypes(){return[TrackerEventType.SCROLL]}getVariableTypes(){return[]}}class VisibilityListener{constructor(e){this.started=!1,this.boundHandler=null,this.trigger=e}start(){this.started||(this.started=!0,this.boundHandler=()=>this.trigger(TrackerEventType.VISIBILITY_CHANGE),document.addEventListener("visibilitychange",this.boundHandler))}stop(){this.started&&(this.started=!1,this.boundHandler&&(document.removeEventListener("visibilitychange",this.boundHandler),this.boundHandler=null))}getTypes(){return[TrackerEventType.VISIBILITY_CHANGE]}getVariableTypes(){return[TrackerVariableType.VIEW_DURATION]}}var FilterOperator;!function(e){e.IS_EQUALS="isEquals",e.IS_EQUALS_IGNORE_CASE="isEqualsIgnoreCase",e.NOT_EQUALS="notEquals",e.NOT_EQUALS_IGNORE_CASE="notEqualsIgnoreCase",e.IS_CONTAINS="isContains",e.IS_CONTAINS_IGNORE_CASE="isContainsIgnoreCase",e.NOT_CONTAINS="notContains",e.NOT_CONTAINS_IGNORE_CASE="notContainsIgnoreCase",e.IS_STARTS_WITH="isStartsWith",e.IS_STARTS_WITH_IGNORE_CASE="isStartsWithIgnoreCase",e.NOT_STARTS_WITH="notStartsWith",e.NOT_STARTS_WITH_IGNORE_CASE="notStartsWithIgnoreCase",e.IS_ENDS_WITH="isEndsWith",e.IS_ENDS_WITH_IGNORE_CASE="isEndsWithIgnoreCase",e.NOT_ENDS_WITH="notEndsWith",e.NOT_ENDS_WITH_IGNORE_CASE="notEndsWithIgnoreCase",e.IS_REGEX_MATCH="isRegexMatch",e.NOT_REGEX_MATCH="notRegexMatch",e.LESS_THAN="lessThan",e.LESS_THAN_OR_EQUALS="lessThanOrEquals",e.GREATER_THAN="greaterThan",e.GREATER_THAN_OR_EQUALS="greaterThanOrEquals"}(FilterOperator||(FilterOperator={}));class FilterEngine{static passesTriggerFilters(e,t){return!e||0===e.length||e.every(e=>{const r=t.get(e.field);return FilterEngine.applyFilter(r,e.operator,e.value)})}static applyFilter(e,t,r){if(null==e)return FilterEngine.isNegationOperator(t);if(Array.isArray(e)){const i=e.some(e=>FilterEngine.applySingleFilter(String(e),t,r));return FilterEngine.isNegationOperator(t)?!i:i}return FilterEngine.applySingleFilter(String(e),t,r)}static applySingleFilter(e,t,r){switch(t){case FilterOperator.IS_EQUALS:return e===r;case FilterOperator.IS_EQUALS_IGNORE_CASE:return e.toLowerCase()===r.toLowerCase();case FilterOperator.NOT_EQUALS:return e!==r;case FilterOperator.NOT_EQUALS_IGNORE_CASE:return e.toLowerCase()!==r.toLowerCase();case FilterOperator.IS_CONTAINS:return e.includes(r);case FilterOperator.IS_CONTAINS_IGNORE_CASE:return e.toLowerCase().includes(r.toLowerCase());case FilterOperator.NOT_CONTAINS:return!e.includes(r);case FilterOperator.NOT_CONTAINS_IGNORE_CASE:return!e.toLowerCase().includes(r.toLowerCase());case FilterOperator.IS_STARTS_WITH:return e.startsWith(r);case FilterOperator.IS_STARTS_WITH_IGNORE_CASE:return e.toLowerCase().startsWith(r.toLowerCase());case FilterOperator.NOT_STARTS_WITH:return!e.startsWith(r);case FilterOperator.NOT_STARTS_WITH_IGNORE_CASE:return!e.toLowerCase().startsWith(r.toLowerCase());case FilterOperator.IS_ENDS_WITH:return e.endsWith(r);case FilterOperator.IS_ENDS_WITH_IGNORE_CASE:return e.toLowerCase().endsWith(r.toLowerCase());case FilterOperator.NOT_ENDS_WITH:return!e.endsWith(r);case FilterOperator.NOT_ENDS_WITH_IGNORE_CASE:return!e.toLowerCase().endsWith(r.toLowerCase());case FilterOperator.IS_REGEX_MATCH:return new RegExp(r).test(e);case FilterOperator.NOT_REGEX_MATCH:return!new RegExp(r).test(e);case FilterOperator.LESS_THAN:return Number(e)<Number(r);case FilterOperator.LESS_THAN_OR_EQUALS:return Number(e)<=Number(r);case FilterOperator.GREATER_THAN:return Number(e)>Number(r);case FilterOperator.GREATER_THAN_OR_EQUALS:return Number(e)>=Number(r);default:return!1}}static isNegationOperator(e){return e===FilterOperator.NOT_EQUALS||e===FilterOperator.NOT_EQUALS_IGNORE_CASE||e===FilterOperator.NOT_CONTAINS||e===FilterOperator.NOT_CONTAINS_IGNORE_CASE||e===FilterOperator.NOT_STARTS_WITH||e===FilterOperator.NOT_STARTS_WITH_IGNORE_CASE||e===FilterOperator.NOT_ENDS_WITH||e===FilterOperator.NOT_ENDS_WITH_IGNORE_CASE||e===FilterOperator.NOT_REGEX_MATCH}}class EventCoordinator{constructor(e,t,r,i,s){this.listeners=[],this.userDefinedValuesProvider=null,this.stopped=!0,this.nativeEventListener=null,this.riskAlertIntervalId=null,this.lastRiskAlertTs=0,this.eventService=e,this.collectors=t,this.payload=r,this.screenActivityCollector=i,this.securityCollector=s}setUserDefinedValuesProvider(e){this.userDefinedValuesProvider=e}start(){Logger.debug("Starting EventCoordinator"),this.stopped=!1,this.initializeListeners(),this.startRiskAlertEvaluator(),this.triggerNativeEvent(TrackerEventType.PAGE_VIEW),Logger.debug("EventCoordinator started with",this.listeners.length,"listeners")}stop(){Logger.debug("Stopping EventCoordinator"),this.stopped=!0,null!==this.riskAlertIntervalId&&(clearInterval(this.riskAlertIntervalId),this.riskAlertIntervalId=null);for(const e of this.listeners)try{e.stop()}catch(e){Logger.error("Error stopping listener:",e)}this.listeners=[],this.nativeEventListener=null,Logger.debug("EventCoordinator stopped")}setNativeEventListener(e){this.nativeEventListener=e}collectAllVariablesNow(){const e=new Map,t=this.payload.getCollectedVariables();for(const[r,i]of t)e.set(r,i);return this.collectFromAllCollectors(e),e}triggerNativeEvent(e){if(this.stopped)Logger.warn("Ignoring native event (coordinator stopped):",e);else try{this.triggerEventInternal(e,null,null)}catch(e){Logger.error("Error in triggerNativeEvent:",e)}}triggerCustomEvent(e,t){try{this.triggerEventInternal(TrackerEventType.CUSTOM,e,t)}catch(e){Logger.error("Error in triggerCustomEvent:",e)}}triggerEventInternal(e,t,r){const i=this.payload.getTrackerConfig(),s=e===TrackerEventType.CUSTOM;if(!i)return void Logger.error("Config not initialized, cannot trigger event");Logger.debug(s?`Custom event triggered: ${t}`:`Native event triggered: ${e}`),e===TrackerEventType.PAGE_VIEW&&(this.securityCollector.registerPageView(),this.screenActivityCollector.resetTimer(),this.screenActivityCollector.startTimer());const n=this.collectAllConfigVariables(),o=this.userDefinedValuesProvider?.()??{};if(s&&t){const e={...o,...r};Object.keys(e).length>0&&n.set(TrackerVariableType.CUSTOM_TRIGGER_ARGS,e)}else Object.keys(o).length>0&&n.set(TrackerVariableType.CUSTOM_TRIGGER_ARGS,o);for(const r of i.configs)try{const i=this.findMatchingTrigger(r,e,t);if(!i)continue;if(n.set(TrackerVariableType.CURRENT_TRIGGER_NAME,i.name),!this.passesTriggerFilters(r,i,n)){Logger.debug("Trigger",i.name,"blocked by filters");continue}const s=this.screenActivityCollector.resolveConfigVariables(r);for(const[e,t]of s)n.set(e,t);const o=BaseDataCollector.mapVariablesToBackend(r,n),a=this.payload.getDeviceId(),l={name:r.event.name,key:a,variables:o};this.eventService.enqueueEvent(l),Logger.debug("Event enqueued:",r.event.name)}catch(e){Logger.error("Error processing config for event:",e)}if(this.nativeEventListener)try{const t={type:e,args:Object.fromEntries(n),timestamp:Date.now()};this.nativeEventListener.onNativeEvent(t)}catch(e){Logger.error("Error notifying native event listener:",e)}}collectAllConfigVariables(){const e=new Map,t=this.payload.getCollectedVariables();for(const[r,i]of t)e.set(r,i);return this.collectFromAllCollectors(e),e}collectFromAllCollectors(e){for(const t of this.collectors)try{const r=t.collect();for(const[t,i]of r)e.set(t,i)}catch(e){Logger.error("Error collecting from collector:",e)}}findMatchingTrigger(e,t,r){return t===TrackerEventType.CUSTOM&&r?e.triggers.find(e=>e.type===TrackerEventType.CUSTOM&&e.name===r)??null:e.triggers.find(e=>e.type===t)??null}passesTriggerFilters(e,t,r){if(!t.filters||0===t.filters.length)return!0;const i=new Map;for(const t of e.variables){const e=t.type,s=r.get(e);void 0!==s&&i.set(t.name,s)}return FilterEngine.passesTriggerFilters(t.filters,i)}initializeListeners(){const e=this.payload.getTrackerConfig();if(!e)return void Logger.error("Config not initialized, cannot initialize listeners");const t=e=>this.triggerNativeEvent(e);this.listeners.push(new PageViewListener(t)),this.listeners.push(new ClickListener(t)),this.listeners.push(new ScrollListener(t)),this.listeners.push(new VisibilityListener(t)),this.listeners.push(new IdleListener(t)),this.listeners.push(new FormInteractionListener(t)),this.listeners.push(new CopyPasteListener(t));for(const t of this.listeners){const r=t.getTypes().some(t=>e.configs.some(e=>e.triggers.some(e=>e.type===t))),i=t.getVariableTypes().some(t=>e.configs.some(e=>Object.values(e.event.variableMappings).includes(t)));if(r||i)try{t.start(),Logger.debug("Started listener:",t.constructor.name)}catch(e){Logger.error("Failed to start listener:",t.constructor.name,e)}else Logger.debug("Skipping listener (not configured):",t.constructor.name)}}startRiskAlertEvaluator(){const e=this.payload.getTrackerConfig();if(!e)return;e.configs.some(e=>e.triggers.some(e=>e.type===TrackerEventType.RISK_ALERT))&&(this.riskAlertIntervalId=setInterval(()=>{this.evaluateRiskAlert()},3e4))}evaluateRiskAlert(){if(this.stopped)return;if(Date.now()-this.lastRiskAlertTs<6e4)return;const e=this.collectAllConfigVariables(),t=.6*(e.get(TrackerVariableType.BEHAVIORAL_BIOMETRIC_SCORE)??0)+.4*(e.get(TrackerVariableType.REMOTE_ACCESS_SCORE)??0);t>.6&&(this.lastRiskAlertTs=Date.now(),Logger.debug("RISK_ALERT triggered (combined score:",t,")"),this.triggerNativeEvent(TrackerEventType.RISK_ALERT))}}class TrackerEngine{constructor(e,t,r,i,s,n,o,a,l,c){this.userDefinedValues={},this.isConfigLoaded=!1,this.eventService=e,this.eventCoordinator=t,this.networkService=r,this.payload=i,this.scheduler=s,this.deviceCollector=n,this.networkCollector=o,this.securityCollector=a,this.behavioralCollector=l,this.screenActivityCollector=c,this.eventCoordinator.setUserDefinedValuesProvider(()=>({...this.userDefinedValues}))}async initialize(e){Logger.debug("Initializing TrackerEngine");try{const t=await this.networkService.fetchConfiguration();if(!t)return void e.onComplete(!1,"Config fetch returned null");this.payload.setTrackerConfig(t),this.isConfigLoaded=!0,await Promise.all([this.deviceCollector.initFingerprint(),this.networkCollector.fetchIp()]);const r=this.payload.getConfiguration(),i=this.payload.getDeviceId();this.securityCollector.init(r?.tenant??"",i),this.behavioralCollector.init(),this.eventCoordinator.start(),this.eventService.start(),this.scheduler.start(),this.screenActivityCollector.startTimer(),Logger.debug("TrackerEngine initialized successfully"),e.onComplete(!0,"Tracker initialized successfully")}catch(t){Logger.error("Failed to initialize:",t);const r=t instanceof Error?t.message:String(t);e.onComplete(!1,`Failed to initialize: ${r}`)}}triggerCustomEvent(e,t){this.isConfigLoaded?this.eventCoordinator.triggerCustomEvent(e,t):Logger.error("Config not loaded, cannot trigger custom event")}async triggerCustomEventSync(e,t,r){if(!this.isConfigLoaded)return void r.onError(new Error("Config not loaded"));const i={...this.userDefinedValues,...t},s=this.payload.getTrackerConfig();if(!s)return void r.onError(new Error("Config not available"));const n=this.eventCoordinator.collectAllVariablesNow();n.set(TrackerVariableType.CUSTOM_TRIGGER_ARGS,i);for(const t of s.configs)try{const i=t.triggers.find(t=>t.type===TrackerEventType.CUSTOM&&t.name===e);if(!i)continue;n.set(TrackerVariableType.CURRENT_TRIGGER_NAME,i.name);const s=this.screenActivityCollector.resolveConfigVariables(t);for(const[e,t]of s)n.set(e,t);const o=BaseDataCollector.mapVariablesToBackend(t,n),a=this.payload.getDeviceId(),l={name:t.event.name,key:a,variables:o};await this.eventService.sendEventSync(l,r)}catch(e){Logger.error("Error creating event for config:",e),r.onError(e instanceof Error?e:new Error(String(e)))}}setUserDefinedValue(e,t){this.userDefinedValues[e]=null==t?void 0:t}getUserDefinedValues(){return{...this.userDefinedValues}}getAllVariables(){return this.eventCoordinator.collectAllVariablesNow()}getConfiguration(){return this.payload.getTrackerConfig()}triggerNativeEvent(e){this.eventCoordinator.triggerNativeEvent(e)}setNativeEventListener(e){this.eventCoordinator.setNativeEventListener(e)}terminate(){Logger.debug("Terminating TrackerEngine"),this.isConfigLoaded=!1;try{this.eventCoordinator.stop()}catch(e){Logger.error("Error stopping coordinator:",e)}try{this.eventService.stop()}catch(e){Logger.error("Error stopping event service:",e)}try{this.scheduler.stop()}catch(e){Logger.error("Error stopping scheduler:",e)}try{this.securityCollector.destroy()}catch(e){Logger.error("Error destroying security:",e)}try{this.behavioralCollector.destroy()}catch(e){Logger.error("Error destroying behavioral:",e)}try{this.screenActivityCollector.destroy()}catch(e){Logger.error("Error destroying screen:",e)}Object.keys(this.userDefinedValues).forEach(e=>{this.userDefinedValues[e]=void 0}),Logger.debug("TrackerEngine terminated")}}class PeriodicDataScheduler{constructor(e,t=5e3){this.task=e,this.intervalMs=t,this.intervalId=null}start(){null===this.intervalId&&(this.intervalId=setInterval(()=>{try{this.task()}catch{}},this.intervalMs))}stop(){null!==this.intervalId&&(clearInterval(this.intervalId),this.intervalId=null)}isRunning(){return null!==this.intervalId}}class DefaultNetworkService{constructor(e){this.configuration=e}async fetchConfiguration(){const e=`${this.configuration.apiUrl}/sentinel/v1`;Logger.debug("Fetching configuration from:",e);const t=await HttpClient.get(e,{headers:{"api-key":this.configuration.apiKey,tenant:this.configuration.tenant,platform:"web"},timeout:this.configuration.timeout});Logger.debug("Received configuration:",JSON.stringify(t,null,2));const r=t.configs.find(e=>"WEB"===e.platform);return{...t,configs:r?[r]:t.configs}}async sendEvent(e){const t=`${this.configuration.apiUrl}/sentinel/v1`;Logger.debug("Sending event:",e.name);const{retryCount:r,...i}=e;return HttpClient.post(t,i,{headers:{"api-key":this.configuration.apiKey,tenant:this.configuration.tenant,platform:"web"},timeout:this.configuration.timeout})}isNetworkAvailable(){return navigator.onLine}}class EventService{constructor(e,t,r){this.scheduler=null,this.consecutiveFailures=0,this.eventStorage=e,this.networkService=t,this.configuration=r}start(){Logger.debug("Starting EventService"),this.scheduler=new PeriodicDataScheduler(()=>this.processQueue(),this.configuration.queueProcessInterval??5e3),this.scheduler.start()}stop(){Logger.debug("Stopping EventService"),this.scheduler?.stop(),this.scheduler=null}enqueueEvent(e){Logger.debug("Enqueuing event:",e.name),this.eventStorage.saveEvent(e)}async sendEventSync(e,t){try{Logger.debug("Sending event sync:",e.name);const r=await this.networkService.sendEvent(e);r&&t.onSuccess(r)}catch(r){Logger.error("Failed to send event:",e.name,r),t.onError(r instanceof Error?r:new Error(String(r)))}}async processQueue(){try{if(!this.networkService.isNetworkAvailable())return void Logger.debug("Network not available, skipping queue processing");if(this.consecutiveFailures>=EventService.MAX_CONSECUTIVE_FAILURES)return void Logger.debug("Too many consecutive failures, pausing queue processing");const e=this.eventStorage.getPendingEvents();if(0===e.length)return;Logger.debug("Processing queue:",e.length,"events");for(const t of e)try{await this.networkService.sendEvent(t),Logger.debug("Event sent from queue:",t.name),this.eventStorage.removeEvent(t),this.consecutiveFailures=0}catch{if(this.consecutiveFailures++,t.retryCount=(t.retryCount??0)+1,t.retryCount>=EventService.MAX_EVENT_RETRIES?(Logger.warn("Dropping event after max retries:",t.name),this.eventStorage.removeEvent(t)):1===this.consecutiveFailures&&Logger.warn("Failed to send event, will retry:",t.name),this.consecutiveFailures>=EventService.MAX_CONSECUTIVE_FAILURES)return void Logger.warn("Queue paused after",EventService.MAX_CONSECUTIVE_FAILURES,"consecutive failures")}}catch(e){Logger.error("Critical error in processQueue:",e)}}resetFailureCount(){this.consecutiveFailures=0}}EventService.MAX_CONSECUTIVE_FAILURES=5,EventService.MAX_EVENT_RETRIES=3;class InMemoryEventStorage{constructor(){this.events=[]}saveEvent(e){this.events.push(e)}getPendingEvents(){return[...this.events]}removeEvent(e){this.events=this.events.filter(t=>t.key!==e.key)}clear(){this.events=[]}}class DataPayload{constructor(){this.trackerConfig=null,this.configuration=null,this.collectedVariables=new Map,this.userDefinedValues=new Map}setTrackerConfig(e){this.trackerConfig=e}getTrackerConfig(){return this.trackerConfig}setConfiguration(e){this.configuration=e}getConfiguration(){return this.configuration}setVariable(e,t){this.collectedVariables.set(e,t)}getVariable(e){return this.collectedVariables.get(e)}getAllVariables(){return new Map(this.collectedVariables)}getCollectedVariables(){return new Map(this.collectedVariables)}updateVariables(e){for(const[t,r]of e)this.collectedVariables.set(t,r)}setUserDefinedValue(e,t){this.userDefinedValues.set(e,t)}getUserDefinedValue(e){return this.userDefinedValues.get(e)}getUserDefinedValues(){return Object.fromEntries(this.userDefinedValues)}getDeviceId(){try{let e=localStorage.getItem("formica_device_id");return e||(e=crypto.randomUUID(),localStorage.setItem("formica_device_id",e)),e}catch{return`anon_${Date.now()}`}}clear(){this.trackerConfig=null,this.collectedVariables.clear(),this.userDefinedValues.clear()}}class TrackerModule{constructor(e){this.configuration=e,this.build()}build(){this.payload=new DataPayload,this.payload.setConfiguration(this.configuration),this.deviceCollector=new DeviceCollector,this.networkCollector=new NetworkCollector,this.securityCollector=new SecurityCollector,this.behavioralCollector=new BehavioralCollector,this.screenActivityCollector=new ScreenActivityCollector;const e=[this.deviceCollector,this.networkCollector,this.securityCollector,this.behavioralCollector,this.screenActivityCollector];this.networkService=new DefaultNetworkService(this.configuration),this.eventStorage=new InMemoryEventStorage,this.eventService=new EventService(this.eventStorage,this.networkService,this.configuration),this.eventCoordinator=new EventCoordinator(this.eventService,e,this.payload,this.screenActivityCollector,this.securityCollector),this.scheduler=new PeriodicDataScheduler(()=>{this.securityCollector.resetRollingWindow(),this.behavioralCollector.resetRollingWindow()},6e4),this.engine=new TrackerEngine(this.eventService,this.eventCoordinator,this.networkService,this.payload,this.scheduler,this.deviceCollector,this.networkCollector,this.securityCollector,this.behavioralCollector,this.screenActivityCollector)}getTrackerEngine(){return this.engine}}class TrackerSDK{constructor(e){this.isInitialized=!1,Logger.setEnabled(e.debug??!1);const t=new TrackerModule(e);this.engine=t.getTrackerEngine()}async initialize(e){try{if(this.isInitialized)return void e.onComplete(!0,"Tracker SDK already initialized");await this.engine.initialize({onComplete:(t,r)=>{t&&(this.isInitialized=!0),e.onComplete(t,r)}})}catch(t){try{const r=t instanceof Error?t.message:String(t);e.onComplete(!1,`Initialization failed: ${r}`)}catch{}}}getIsInitialized(){return this.isInitialized}triggerCustomEvent(e,t={}){if(this.isInitialized)try{this.engine.triggerCustomEvent(e,t)}catch{}}async triggerCustomEventSync(e,t,r){if(this.isInitialized)try{await this.engine.triggerCustomEventSync(e,t,r)}catch(e){try{r.onError(e instanceof Error?e:new Error(String(e)))}catch{}}else r.onError(new Error("Tracker SDK not initialized"))}setUserDefinedValue(e,t){this.isInitialized&&this.engine.setUserDefinedValue(e,t)}getUserDefinedValues(){return this.isInitialized?this.engine.getUserDefinedValues():null}getAllVariables(){return this.isInitialized?this.engine.getAllVariables():null}getConfiguration(){return this.isInitialized?this.engine.getConfiguration():null}triggerNativeEvent(e){this.isInitialized&&this.engine.triggerNativeEvent(e)}setNativeEventListener(e){this.isInitialized&&this.engine.setNativeEventListener(e)}terminate(){if(this.isInitialized)try{this.engine.terminate()}catch{}finally{this.isInitialized=!1}}}TrackerSDK.Builder=class{constructor(){this.configuration=null}setConfiguration(e){return this.configuration=e,this}build(){if(!this.configuration)throw new Error("Configuration must be set before building TrackerSDK");return new TrackerSDK(this.configuration)}};export{CacheStrategy,FilterOperator,TrackerEventType,TrackerSDK,TrackerVariableType};