@trlogic/tracker-web
Version:
Formica Tracker Web
2 lines (1 loc) • 26.8 kB
JavaScript
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t(require("bowser"),require("@fingerprintjs/fingerprintjs")):"function"==typeof define&&define.amd?define(["bowser","@fingerprintjs/fingerprintjs"],t):(e="undefined"!=typeof globalThis?globalThis:e||self).umd=t(e.Bowser,e.FingerprintJS)}(this,function(Bowser,FingerprintJS){"use strict";var TrackerTriggerTypeMobile,TrackerTriggerTypeWeb,TrackerVariableWebType;!function(e){e.APP_STATE_CHANGE="APP_STATE_CHANGE",e.CUSTOM="CUSTOM",e.APPLICATION_OPEN="APPLICATION_OPEN",e.NETWORK_CHANGE="NETWORK_CHANGE",e.APP_CALL_END="APP_CALL_END",e.APP_CALL_START="APP_CALL_START",e.APP_CALL_RING="APP_CALL_RING",e.APP_CALL_REJECT="APP_CALL_REJECT",e.SCREEN_RECORD_END="SCREEN_RECORD_END",e.SCREEN_RECORD_START="SCREEN_RECORD_START"}(TrackerTriggerTypeMobile||(TrackerTriggerTypeMobile={})),function(e){e.CLICK="CLICK",e.SCROLL="SCROLL",e.PAGE_VIEW="PAGE_VIEW",e.CUSTOM="CUSTOM",e.RISK_ALERT="RISK_ALERT"}(TrackerTriggerTypeWeb||(TrackerTriggerTypeWeb={})),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.SUSPICIOUS_ACTIVITY="SUSPICIOUS_ACTIVITY",e.SUSPICIOUS_BROWSER="SUSPICIOUS_BROWSER",e.LANGUAGE="LANGUAGE",e.TIMEZONE="TIMEZONE",e.OS="OS",e.BROWSER="BROWSER",e.CUSTOM="CUSTOM",e.SCREEN_RESOLUTION="SCREEN_RESOLUTION",e.USER_AGENT="USER_AGENT",e.REMOTE_ACCESS_SCORE="REMOTE_ACCESS_SCORE",e.REMOTE_ACCESS_FLAGS_JSON="REMOTE_ACCESS_FLAGS_JSON",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"}(TrackerVariableWebType||(TrackerVariableWebType={}));const isDev=(()=>{var e,t;try{const r="undefined"!=typeof globalThis?globalThis:{};return"production"!==(null===(t=null===(e=r.process)||void 0===e?void 0:e.env)||void 0===t?void 0:t.NODE_ENV)}catch{return!0}})(),logger={debug:(...e)=>{isDev&&console.debug("[tracker]",...e)},info:(...e)=>{isDev&&console.info("[tracker]",...e)},warn:(...e)=>{isDev&&console.warn("[tracker]",...e)},error:(...e)=>{isDev&&console.error("[tracker]",...e)}};function monitorRemoteControlSuspicion(e){try{const t={unnaturalMouseMoves:!1,bigClipboardPaste:!1,lowFPSDetected:!1,delayedClickDetected:!1},r={...t};function i(){try{t.unnaturalMouseMoves===r.unnaturalMouseMoves&&t.bigClipboardPaste===r.bigClipboardPaste&&t.lowFPSDetected===r.lowFPSDetected&&t.delayedClickDetected===r.delayedClickDetected||(r.unnaturalMouseMoves=t.unnaturalMouseMoves,r.bigClipboardPaste=t.bigClipboardPaste,r.lowFPSDetected=t.lowFPSDetected,r.delayedClickDetected=t.delayedClickDetected,e({...t}))}catch(e){logger.debug("Error while triggering suspicious change callback",e)}}let a=null,s=null,n=0;window.addEventListener("mousemove",e=>{try{if(null!==a&&null!==s){const r=Math.abs(e.clientX-a),o=Math.abs(e.clientY-s);(r>100||o>100||0===r&&0===o)&&n++,n>5&&!t.unnaturalMouseMoves&&(t.unnaturalMouseMoves=!0,i())}a=e.clientX,s=e.clientY}catch(e){logger.debug("Error in mousemove handler",e)}}),window.addEventListener("paste",e=>{var r;try{((null===(r=e.clipboardData)||void 0===r?void 0:r.getData("text"))||"").length>5e3&&!t.bigClipboardPaste&&(t.bigClipboardPaste=!0,i())}catch(e){logger.debug("Error detecting large clipboard paste",e)}}),document.addEventListener("click",()=>{try{const e=performance.now();setTimeout(()=>{try{performance.now()-e>150&&!t.delayedClickDetected&&(t.delayedClickDetected=!0,i())}catch(e){logger.debug("Error evaluating delayed click",e)}},0)}catch(e){logger.debug("Error scheduling delayed click evaluation",e)}});let o=performance.now(),c=0;function l(){try{const e=performance.now();e-o>250&&c++,c>10&&!t.lowFPSDetected&&(t.lowFPSDetected=!0,i()),o=e}catch(e){logger.debug("Error in FPS monitor",e)}requestAnimationFrame(l)}l()}catch(u){logger.debug("Error initializing remote control suspicion monitor",u)}}const BASELINE_KEY_PREFIX="formica_baseline_";class RiskMetricsService{constructor(){this.lastMouseMoveTs=Date.now(),this.lastClickTs=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}initListeners(){const e=window;e.__riskMetricsListenersAttached||(e.__riskMetricsListenersAttached=!0,window.addEventListener("mousemove",()=>{this.lastMouseMoveTs=Date.now(),this.mouseMoveCounterWindow++}),document.addEventListener("click",()=>{this.lastClickTs=Date.now(),this.clickCounterWindow++}),document.addEventListener("paste",e=>{var t;const r=(null===(t=e.clipboardData)||void 0===t?void 0:t.getData("text"))||"";this.pasteCharCount+=r.length}),document.addEventListener("keydown",e=>{1===e.key.length&&(this.typedCharCount+=1)}),document.addEventListener("focusin",e=>{var t;const r=null===(t=e.target)||void 0===t?void 0:t.closest("form");r&&null===this.lastFormInteractionStart&&(this.lastFormInteractionStart=Date.now(),this.lastFormFieldCount=r.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)}))}registerPageView(){const e=Date.now();this.lastPageViewTs&&e-this.lastPageViewTs<1500?this.fastPageTransition=!0:this.fastPageTransition=!1,this.lastPageViewTs=e,this.pageViewTimestamps.push(e);const t=e-3e5;this.pageViewTimestamps=this.pageViewTimestamps.filter(e=>e>=t)}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{headlessIndicatorCount:0,noMouseMoveDurationMs:e,clickOnlyPattern:t,fastPageTransition:this.fastPageTransition,navigationVelocity:r,formFillAnomaly:this.formFillAnomaly,formFillDurationMs:this.lastFormDurationMs,inputPasteRatio:i}}computePasteRatio(){const e=this.pasteCharCount+this.typedCharCount;return 0===e?0:this.pasteCharCount/e}resetRollingWindow(){this.mouseMoveCounterWindow=0,this.clickCounterWindow=0}detectFormAnomaly(e,t){if(0===t)return!1;return e<Math.min(120*t,1500)}loadBaseline(e,t){try{const r=localStorage.getItem(this.getBaselineKey(e,t));return r?JSON.parse(r):null}catch{return null}}ensureBaseline(e,t,r){const i=this.loadBaseline(e,t);if(i)return i;const a={...r,firstSeen:Date.now()};try{localStorage.setItem(this.getBaselineKey(e,t),JSON.stringify(a))}catch(e){}return a}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)}calcRemoteAccessScore(e,t,r){let i=0;return e.unnaturalMouseMoves&&(i+=.2),e.bigClipboardPaste&&(i+=.15),e.lowFPSDetected&&(i+=.15),e.delayedClickDetected&&(i+=.1),r.clickOnlyPattern&&(i+=.15),r.headlessIndicatorCount>0&&(i+=Math.min(.05*r.headlessIndicatorCount,.25)),t&&(i+=.2),Math.min(1,i)}getBaselineKey(e,t){return`${BASELINE_KEY_PREFIX}${e}_${t}`}}const riskMetricsService=new RiskMetricsService;class BrowserDetector{static getBrowser(){return this.parser.getBrowser().name||null}static getBrowserVersion(){return this.parser.getBrowser().version||null}static getOS(){return this.parser.getOS().name||null}static getOSVersion(){return this.parser.getOS().version||null}static getPlatform(){return this.parser.getPlatform().type||null}static getEngine(){return this.parser.getEngine().name||null}static getLanguage(){const e=navigator;return navigator.language||e.userLanguage||""}static getTimezone(){try{return Intl.DateTimeFormat().resolvedOptions().timeZone||""}catch{return""}}static isDesktop(){return"desktop"===this.parser.getPlatform().type}static isMobile(){return"mobile"===this.parser.getPlatform().type}static isTablet(){return"tablet"===this.parser.getPlatform().type}static isBot(){const e=navigator.userAgent.toLowerCase();return["bot","crawler","spider","crawling","facebook","google","baidu","bing","msn","duckduckbot","teoma","slurp","yandex"].some(t=>e.includes(t))}static async isSuspiciousBrowser(){try{if(navigator.webdriver)return!0;if(!navigator.languages||0===navigator.languages.length)return!0;if(!navigator.plugins||0===navigator.plugins.length)return!0;const e=document.createElement("canvas");if(!(e.getContext("webgl")||e.getContext("experimental-webgl")))return!0;if(e.remove(),navigator.permissions)try{const e=await navigator.permissions.query({name:"notifications"});if("denied"===Notification.permission&&"prompt"===e.state)return!0}catch{}return!!(navigator.hardwareConcurrency&&navigator.hardwareConcurrency<=1)||(!(!this.isMobile()||"ontouchstart"in window)||!!this.isBot())}catch{return!0}}static getDetailedInfo(){return{browser:{name:this.getBrowser(),version:this.getBrowserVersion()},os:{name:this.getOS(),version:this.getOSVersion()},platform:{type:this.getPlatform(),isDesktop:this.isDesktop(),isMobile:this.isMobile(),isTablet:this.isTablet()},engine:this.getEngine(),language:this.getLanguage(),timezone:this.getTimezone(),isBot:this.isBot()}}}BrowserDetector.parser=Bowser.getParser(window.navigator.userAgent);class EventBuilder{static buildEvent(e,t){var r;const i=e.event.name,a={};return Object.entries(e.event.variableMappings).forEach(([e,r])=>{a[e]=this.resolveMapping(r,t)}),{name:i,key:String(null!==(r=a[e.event.keyMapping])&&void 0!==r?r:""),variables:a}}static resolveMapping(e,t){const r=this.findMatches(e,/{.*?}/g);if(0===r.length&&Object.prototype.hasOwnProperty.call(t,e))return t[e];let i=e;return r.forEach(e=>{var r;const a=e.substring(1,e.length-1),s=String(null!==(r=t[a])&&void 0!==r?r:"");i=i.replace(e,s)}),i}static findMatches(e,t){const r=[];let i;for(t.lastIndex=0;null!==(i=t.exec(e));)r.push(i[0]),0===i[0].length&&t.lastIndex++;return r}}class HttpError extends Error{constructor(e,t,r){super(e),this.status=t,this.response=r,this.name="HttpError"}}class HttpClient{constructor(e={}){this.defaultHeaders=e}async makeRequest(e,t={}){const{headers:r={},timeout:i,method:a="GET",body:s}=t,n={...this.defaultHeaders,...r},o=new AbortController,c=i?setTimeout(()=>o.abort(),i):null;try{const t={method:a,headers:n,signal:o.signal};s&&"GET"!==a&&(t.body="string"==typeof s?s:JSON.stringify(s),n["Content-Type"]||(n["Content-Type"]="application/json"));const r=await fetch(e,t);if(c&&clearTimeout(c),!r.ok)throw new HttpError(`HTTP Error: ${r.status} ${r.statusText}`,r.status,r);if("0"===r.headers.get("Content-Length"))return{};const i=r.headers.get("Content-Type");return(null==i?void 0:i.includes("application/json"))?await r.json():await r.text()}catch(e){if(c&&clearTimeout(c),e instanceof Error&&"AbortError"===e.name)throw new HttpError("Request timeout",408);throw e}}async get(e,t){return this.makeRequest(e,{...t,method:"GET"})}async post(e,t,r){return this.makeRequest(e,{...r,method:"POST",body:t})}async put(e,t,r){return this.makeRequest(e,{...r,method:"PUT",body:t})}async delete(e,t){return this.makeRequest(e,{...t,method:"DELETE"})}}class EventQueue{constructor(e,t){this.queue=[],this.processingInterval=null,this.tenant=e,this.apiUrl=t,this.httpClient=new HttpClient}add(e){this.queue.push(e)}async sendSync(e,t){return this.httpClient.post(this.apiUrl,e,{headers:{tenant:this.tenant},timeout:t})}startProcessing(){this.processingInterval||(this.processingInterval=window.setInterval(()=>{this.processQueue()},3e3))}stopProcessing(){this.processingInterval&&(clearInterval(this.processingInterval),this.processingInterval=null)}async processQueue(){if(navigator.onLine&&0!==this.queue.length)for(;this.queue.length>0&&navigator.onLine;){const e=this.queue.pop();if(!e)break;try{await this.httpClient.post(this.apiUrl,e,{headers:{tenant:this.tenant}})}catch(t){logger.error("Failed to send event:",t),this.queue.unshift(e);break}}}getQueueLength(){return this.queue.length}}class EventValidator{static validate(e,t){return"click"!==t.name||this.validateClick(e,t.option)}static validateClick(e,t){if(void 0!==t&&!t.justLinks)return!0;const r=e.target;return!!r&&r.hasAttribute("href")}}class FingerprintService{static async getDeviceFingerprint(){try{const e=await FingerprintJS.load();return(await e.get()).visitorId}catch(e){return console.error("Error generating device fingerprint:",e),""}}}class IpService{static async fetchIp(){try{return(await this.httpClient.get("https://api.ipify.org?format=json")).ip}catch(e){return console.error("Error fetching IP address:",e),"unknown"}}}IpService.httpClient=new HttpClient;class TimerService{constructor(){this.timerInstance=null,this.viewDuration=0,this.INTERVAL_MS=100}start(){this.timerInstance||(this.timerInstance=window.setInterval(()=>{this.viewDuration+=this.INTERVAL_MS},this.INTERVAL_MS))}stop(){this.timerInstance&&(clearInterval(this.timerInstance),this.timerInstance=null)}reset(){this.viewDuration=0}getViewDuration(){return this.viewDuration}isRunning(){return null!==this.timerInstance}}class TrackerConfigService{constructor(){this.httpClient=new HttpClient}async getTrackers(e,t,r){const i=`${e}/sentinel/v1`;return{trackers:(await this.httpClient.get(i,{headers:{tenant:t,"api-key":r,"Content-Type":"application/json",platform:"web"}})).configs,apiUrl:i}}}class VariableResolver{constructor(e,t,r,i){this.ip=e,this.deviceFingerprint=t,this.suspiciousFlags=r,this.globalVariables=i}async resolve(e,t){switch(e.type){case TrackerVariableWebType.URL:return this.resolveUrlVariable(e);case TrackerVariableWebType.COOKIE:return this.resolveCookieVariable(e);case TrackerVariableWebType.ELEMENT:return this.resolveElementVariable(e);case TrackerVariableWebType.JAVASCRIPT:return this.resolveJavascriptVariable(e);case TrackerVariableWebType.EVENT:return this.resolveTriggerVariable(e,t);case TrackerVariableWebType.IP_ADDRESS:return this.resolveIpAddressVariable();case TrackerVariableWebType.DEVICE_FINGERPRINT:return this.resolveDeviceFingerprint();case TrackerVariableWebType.SUSPICIOUS_ACTIVITY:return this.resolveSuspiciousActivityVariable();case TrackerVariableWebType.SUSPICIOUS_BROWSER:return BrowserDetector.isSuspiciousBrowser();case TrackerVariableWebType.LANGUAGE:return BrowserDetector.getLanguage();case TrackerVariableWebType.TIMEZONE:return BrowserDetector.getTimezone();case TrackerVariableWebType.OS:return BrowserDetector.getOS();case TrackerVariableWebType.BROWSER:return BrowserDetector.getBrowser();case TrackerVariableWebType.CUSTOM:return this.resolveCustomVariable(e);case TrackerVariableWebType.SCREEN_RESOLUTION:return`${window.screen.width}x${window.screen.height}`;case TrackerVariableWebType.USER_AGENT:return navigator.userAgent;case TrackerVariableWebType.NO_MOUSE_MOVE_DURATION_MS:case TrackerVariableWebType.CLICK_ONLY_PATTERN:case TrackerVariableWebType.FAST_PAGE_TRANSITION:case TrackerVariableWebType.FORM_FILL_ANOMALY:case TrackerVariableWebType.FORM_FILL_DURATION_MS:case TrackerVariableWebType.NAVIGATION_VELOCITY:case TrackerVariableWebType.INPUT_PASTE_RATIO:case TrackerVariableWebType.REMOTE_ACCESS_SCORE:case TrackerVariableWebType.REMOTE_ACCESS_FLAGS_JSON:case TrackerVariableWebType.ENVIRONMENT_CHANGE_FLAGS:case TrackerVariableWebType.ENVIRONMENT_CHANGE_SCORE:case TrackerVariableWebType.HEADLESS_INDICATOR_COUNT:return this.resolveRiskVariable(e.type);default:return""}}resolveRiskVariable(e){var t,r,i,a,s;const n=riskMetricsService.getAggregates(),o=window;switch(e){case TrackerVariableWebType.NO_MOUSE_MOVE_DURATION_MS:return n.noMouseMoveDurationMs;case TrackerVariableWebType.CLICK_ONLY_PATTERN:return n.clickOnlyPattern;case TrackerVariableWebType.FAST_PAGE_TRANSITION:return n.fastPageTransition;case TrackerVariableWebType.FORM_FILL_ANOMALY:return n.formFillAnomaly;case TrackerVariableWebType.FORM_FILL_DURATION_MS:return n.formFillDurationMs;case TrackerVariableWebType.NAVIGATION_VELOCITY:return n.navigationVelocity;case TrackerVariableWebType.INPUT_PASTE_RATIO:return n.inputPasteRatio;case TrackerVariableWebType.ENVIRONMENT_CHANGE_FLAGS:return(null===(t=o.__formicaEnvChange)||void 0===t?void 0:t.flags)?JSON.stringify(o.__formicaEnvChange.flags):"{}";case TrackerVariableWebType.ENVIRONMENT_CHANGE_SCORE:return null!==(i=null===(r=o.__formicaEnvChange)||void 0===r?void 0:r.score)&&void 0!==i?i:0;case TrackerVariableWebType.HEADLESS_INDICATOR_COUNT:return null!==(a=o.__formicaHeadlessIndicators)&&void 0!==a?a:0;case TrackerVariableWebType.REMOTE_ACCESS_FLAGS_JSON:return o.__formicaRemoteAccessFlags?JSON.stringify(o.__formicaRemoteAccessFlags):"{}";case TrackerVariableWebType.REMOTE_ACCESS_SCORE:return null!==(s=o.__formicaRemoteAccessScore)&&void 0!==s?s:0;default:return""}}resolveCustomVariable(e){var t;const r=e.name;return String(null!==(t=this.globalVariables[r])&&void 0!==t?t:"")}resolveUrlVariable(e){const t=window.location;switch(e.selection){case"full":return t.href;case"host":return t.hostname;case"port":return t.port;case"path":return t.pathname;case"query":return t.search.substring(1);case"fragment":{const e=t.href.split("#");return e.length>1?e[1]:""}case"protocol":return t.protocol.substring(0,t.protocol.length-1);default:return""}}resolveCookieVariable(e){var t;const r=e.cookieName;return null!==(t=document.cookie.split(";").map(e=>e.trim()).filter(e=>e.startsWith(r)).map(t=>e.decodeUrlCookie?decodeURIComponent(t.substring(r.length+1)):t.substring(r.length+1))[0])&&void 0!==t?t:""}resolveElementVariable(e){var t,r;const i=e.option,a=document.querySelector(i.cssSelector);return a?i.attribute?null!==(r=a.getAttribute(i.attribute))&&void 0!==r?r:"":null!==(t=a.textContent)&&void 0!==t?t:"":""}resolveJavascriptVariable(trackerVariableSchema){try{const result=eval(trackerVariableSchema.code);return String(null!=result?result:"")}catch(e){return console.error("Error executing JavaScript variable:",e),""}}resolveIpAddressVariable(){return"unknown"===this.ip?"":this.ip}resolveDeviceFingerprint(){return this.deviceFingerprint}resolveSuspiciousActivityVariable(){const e=this.suspiciousFlags;let t=0;return e.unnaturalMouseMoves&&(t+=.25),e.bigClipboardPaste&&(t+=.25),e.lowFPSDetected&&(t+=.25),e.delayedClickDetected&&(t+=.25),t}resolveTriggerVariable(e,t){var r,i;const a=e.selection,s=document.createElement("div"),n=t.target;if(!n)return"";s.appendChild(n.cloneNode(!0));const o=s.querySelector(a.cssSelector);return o?a.attribute?null!==(r=o.getAttribute(a.attribute))&&void 0!==r?r:"":null!==(i=o.textContent)&&void 0!==i?i:"":""}}class FormicaTracker{constructor(){this.state={tenant:"",serviceUrl:"",apiKey:""},this.config={apiUrl:"",trackers:[]},this.globalVariables={viewDuration:0},this.suspiciousFlags={unnaturalMouseMoves:!1,bigClipboardPaste:!1,lowFPSDetected:!1,delayedClickDetected:!1},this.timerService=new TimerService,this.trackerConfigService=new TrackerConfigService,this.lastRiskAlertScore=0,this.lastRiskAlertAt=0,this.riskAlertCooldownMs=6e4,this.riskAlertThreshold=.7,this.riskAlertEnabled=!0}async initialize(e){try{this.validateInitArgs(e);const{serviceUrl:t,tenantName:r,apiKey:i}=e;this.state={tenant:r,serviceUrl:t,apiKey:i},await this.setupTracker(),await this.initializeServices(),this.setupEventListeners(),this.startMonitoring()}catch(e){throw console.error("Tracker initialization failed:",e),e}}async triggerCustom(e,t){const r=await this.buildCustomTriggerPayload(e,t);r&&this.eventQueue&&this.eventQueue.add(r)}async triggerCustomSync(e,t,r){try{const i=await this.buildCustomTriggerPayload(e,t);if(!i||!this.eventQueue)return null;return await this.eventQueue.sendSync(i,null==r?void 0:r.timeoutMs)}catch(e){return console.error("Sync trigger failed:",e),null}}setUserDefinedVariable(e,t){this.globalVariables[e]=t}getUserDefinedVariable(e){return this.globalVariables[e]}getUserDefinedVariables(){return{...this.globalVariables}}validateInitArgs(e){var t,r,i;if(!e)throw new Error("Tracker initialization args must be provided");if(!(null===(t=e.serviceUrl)||void 0===t?void 0:t.trim()))throw new Error("Service URL must be provided");if(!(null===(r=e.apiKey)||void 0===r?void 0:r.trim()))throw new Error("API key must be provided");if(!(null===(i=e.tenantName)||void 0===i?void 0:i.trim()))throw new Error("Tenant name must be provided")}async setupTracker(){const{trackers:e,apiUrl:t}=await this.trackerConfigService.getTrackers(this.state.serviceUrl,this.state.tenant,this.state.apiKey);this.config={trackers:e,apiUrl:t}}async initializeServices(){this.state.ip=await IpService.fetchIp(),this.state.deviceIdFingerPrint=await FingerprintService.getDeviceFingerprint(),this.eventQueue=new EventQueue(this.state.tenant,this.config.apiUrl),this.eventQueue.startProcessing(),this.variableResolver=new VariableResolver(this.state.ip||"",this.state.deviceIdFingerPrint||"",this.suspiciousFlags,this.globalVariables),this.timerService.start()}setupEventListeners(){this.config.trackers.forEach(e=>{e.triggers.forEach(t=>{this.initListener(t,e)})})}startMonitoring(){monitorRemoteControlSuspicion(e=>{this.suspiciousFlags={...e};window.__formicaRemoteAccessFlags={...e}}),riskMetricsService.initListeners(),this.setupEnvironmentBaseline(),this.computeHeadlessIndicators(),window.setInterval(()=>this.updateCompositeRiskScores(),5e3)}setupEnvironmentBaseline(){try{const e=window,t=riskMetricsService.ensureBaseline(this.state.tenant,this.state.deviceIdFingerPrint||"anon",{language:BrowserDetector.getLanguage(),timezone:BrowserDetector.getTimezone(),os:BrowserDetector.getOS(),browser:BrowserDetector.getBrowser()}),r={language:BrowserDetector.getLanguage(),timezone:BrowserDetector.getTimezone(),os:BrowserDetector.getOS(),browser:BrowserDetector.getBrowser()},i=riskMetricsService.diffEnvironment(t,r),a=riskMetricsService.calcEnvironmentChangeScore(i);e.__formicaEnvChange={flags:i,score:a}}catch{logger.warn("Failed to setup environment baseline, continuing without it")}}async computeHeadlessIndicators(){try{const e=window;let t=0;navigator.webdriver&&t++,navigator.languages&&0!==navigator.languages.length||t++,navigator.plugins&&0!==navigator.plugins.length||t++;const r=document.createElement("canvas");r.getContext("webgl")||r.getContext("experimental-webgl")||t++,r.remove(),BrowserDetector.isBot()&&t++,e.__formicaHeadlessIndicators=t}catch{logger.warn("Failed to compute headless indicators")}}updateCompositeRiskScores(){var e,t;try{const r=window,i=riskMetricsService.getAggregates();i.headlessIndicatorCount=r.__formicaHeadlessIndicators||0;const a=((null===(e=r.__formicaEnvChange)||void 0===e?void 0:e.score)||0)>.5,s=riskMetricsService.calcRemoteAccessScore(this.suspiciousFlags,a,i);r.__formicaRemoteAccessScore=s,r.__formicaRemoteAccessFlags={...this.suspiciousFlags,clickOnlyPattern:i.clickOnlyPattern,fastPageTransition:i.fastPageTransition,headlessIndicatorCount:i.headlessIndicatorCount,envChangeScore:(null===(t=r.__formicaEnvChange)||void 0===t?void 0:t.score)||0},this.maybeEmitRiskAlert(s)}catch{logger.warn("Failed to update composite risk scores")}}async maybeEmitRiskAlert(e){if(!this.riskAlertEnabled)return;const t=Date.now(),r=this.lastRiskAlertScore<this.riskAlertThreshold&&e>=this.riskAlertThreshold,i=t-this.lastRiskAlertAt>this.riskAlertCooldownMs;if(this.lastRiskAlertScore=e,r&&i&&(this.lastRiskAlertAt=t,this.variableResolver&&this.eventQueue))for(const t of this.config.trackers){if(!t.triggers.find(e=>e.type===TrackerTriggerTypeWeb.RISK_ALERT))continue;const r={riskScore:e};for(const e of t.variables)e.type!==TrackerVariableWebType.CUSTOM&&(r[e.name]=await this.variableResolver.resolve(e,new MouseEvent("")));const i=EventBuilder.buildEvent(t,r);this.eventQueue.add(i)}}initListener(e,t){switch(e.type){case TrackerTriggerTypeWeb.PAGE_VIEW:this.setupPageViewListener(e,t);break;case TrackerTriggerTypeWeb.RISK_ALERT:break;case TrackerTriggerTypeWeb.CUSTOM:this.setupCustomEventListener(e,t);break;default:this.setupDefaultEventListener(e,t)}}setupPageViewListener(e,t){this.state.previousHref=document.location.href,window.addEventListener("load",()=>{const r=document.querySelector("body");if(!r)return;riskMetricsService.registerPageView();new MutationObserver(()=>{this.state.previousHref!==document.location.href&&(this.handleEvent(new Event("pageview"),e,t),this.state.previousHref=document.location.href,riskMetricsService.registerPageView())}).observe(r,{childList:!0,subtree:!0})})}setupCustomEventListener(e,t){document.addEventListener(e.name,r=>{this.handleEvent(r,e,t)})}setupDefaultEventListener(e,t){document.addEventListener(e.type.toLowerCase(),r=>{this.handleEvent(r,e,t)})}async handleEvent(e,t,r){if(!this.variableResolver||!this.eventQueue)return;const i={},a=e;for(const e of r.variables)i[e.name]=await this.variableResolver.resolve(e,a);if(!EventValidator.validate(a,t))return;const s=EventBuilder.buildEvent(r,i);this.eventQueue.add(s)}async buildCustomTriggerPayload(e,t){if(!this.variableResolver)return null;for(const r of this.config.trackers){if(r.triggers.find(t=>{var r;return t.type===TrackerTriggerTypeWeb.CUSTOM&&((null===(r=t.option)||void 0===r?void 0:r.event)===e||t.name===e)})){for(const e of r.variables)e.type===TrackerVariableWebType.CUSTOM&&Object.prototype.hasOwnProperty.call(t,e.name)||(t[e.name]=await this.variableResolver.resolve(e,new MouseEvent("")));return EventBuilder.buildEvent(r,t)}}return null}configureRiskAlert(e){"number"==typeof e.threshold&&e.threshold>=0&&e.threshold<=1&&(this.riskAlertThreshold=e.threshold),"number"==typeof e.cooldownMs&&e.cooldownMs>=0&&(this.riskAlertCooldownMs=e.cooldownMs),"boolean"==typeof e.enabled&&(this.riskAlertEnabled=e.enabled)}}const formicaTracker=new FormicaTracker,TrackerManager={initialize:formicaTracker.initialize.bind(formicaTracker),triggerCustom:formicaTracker.triggerCustom.bind(formicaTracker),triggerCustomSync:formicaTracker.triggerCustomSync.bind(formicaTracker),setUserDefinedVariable:formicaTracker.setUserDefinedVariable.bind(formicaTracker),getUserDefinedVariable:formicaTracker.getUserDefinedVariable.bind(formicaTracker),getUserDefinedVariables:formicaTracker.getUserDefinedVariables.bind(formicaTracker),configureRiskAlert:formicaTracker.configureRiskAlert.bind(formicaTracker)};return TrackerManager});