├── LICENSE ├── README.md ├── userBehaviour.js └── userBehaviour.min.js /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Taha Al-Jody 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

User Behaviour Tracking

2 |

5.3 KB

3 | 4 |
5 | 6 | [![Status](https://img.shields.io/badge/status-active-success.svg)]() 7 | [![GitHub Issues](https://img.shields.io/github/issues/TA3/web-user-behaviour)](https://github.com/kylelobo/The-Documentation-Compendium/issues) 8 | [![GitHub Pull Requests](https://img.shields.io/github/issues-pr/TA3/web-user-behaviour)](https://github.com/kylelobo/The-Documentation-Compendium/pulls) 9 | [![License](https://img.shields.io/badge/license-MIT-blue.svg)](/LICENSE) 10 | 11 |
12 | 13 | --- 14 | 15 |

Advanced User Behaviour Tracking Library with 15+ tracking dimensions including media interactions, form tracking, and custom event support.
16 |

17 | 18 | ## 📝 Table of Contents 19 | 20 | - [About](#about) 21 | - [Installation](#install) 22 | - [Configuration](#config) 23 | - [Methods](#methods) 24 | - [Tracking](#tracking) 25 | - [Results](#results) 26 | 27 | ## 🧐 About 28 | 29 | This Javascript Library allows to track user's behaviour by recording mouse activities: 30 | 31 | - Mouse tracking (movement, clicks, scroll) 32 | - Keyboard activity monitoring 33 | - Page navigation history 34 | - Form interaction tracking 35 | - Touch event capture 36 | - Media play events (audio/video) 37 | - Window visibility changes 38 | - Custom event registration 39 | - Device/browser fingerprinting 40 | 41 | ## 🏁 Installation 42 | 43 | There are two ways to include userBehaviour.js to your browser: 44 | 45 | 1. jsDelivr CDN 46 | 47 | ```html 48 | 49 | ``` 50 | 51 | 2. Local file 52 | 53 | ```html 54 | 55 | ``` 56 | 57 | ## 🔧 Configuration 58 | 59 | The library requires a configuration object. Pass the object to the library with: 60 | 61 | ```javascript 62 | userBehaviour.config({.....}); 63 | ``` 64 | 65 | If no configuration was passes the libray will use the default configuration: 66 | 67 | ```javascript 68 | { 69 | userInfo: true, 70 | clicks: true, 71 | mouseMovement: true, 72 | mouseMovementInterval: 1, 73 | mouseScroll: true, 74 | timeCount: true, 75 | windowResize: true, 76 | visibilitychange: true, 77 | keyboardActivity: true, 78 | pageNavigation: true, 79 | formInteractions: true, 80 | touchEvents: true, 81 | audioVideoInteraction: true, 82 | clearAfterProcess: true, 83 | processTime: 15, 84 | processData: function(results){ 85 | console.log(results); 86 | }, 87 | } 88 | ``` 89 | 90 | | Config Key | Description | Type | Default | 91 | | ----------------------- | --------------------------------------------------------------- | -------- | ------- | 92 | | userInfo | Record browser/device details | bool | true | 93 | | clicks | Track mouse clicks | bool | true | 94 | | mouseMovement | Track mouse movement | bool | true | 95 | | mouseMovementInterval | Mouse position sampling interval (seconds) | int | 1 | 96 | | mouseScroll | Track page scrolling | bool | true | 97 | | timeCount | Track session timing | bool | true | 98 | | windowResize | Track window size changes | bool | true | 99 | | visibilitychange | Track tab visibility changes | bool | true | 100 | | keyboardActivity | Track keyboard input | bool | true | 101 | | pageNavigation | Track history changes (pushState/popState) | bool | true | 102 | | formInteractions | Track form submissions | bool | true | 103 | | touchEvents | Track touch interactions | bool | true | 104 | | audioVideoInteraction | Track media play events | bool | true | 105 | | customEventRegistration | Enable custom event tracking | bool | true | 106 | | clearAfterProcess | Clear data after processing | bool | true | 107 | | processTime | Automatic processing interval (seconds) - false for manual only | int/bool | 15 | 108 | | processData | Callback function for processed data | function | console | 109 | 110 | ## 📚 Methods 111 | 112 | This is a list of all available methods that can be called: 113 | 114 | | Method | Description | Example | 115 | | --------------------- | ------------------------------- | ------------------------------------------------------ | 116 | | registerCustomEvent() | Register custom event tracking | `userBehaviour.registerCustomEvent('event', callback)` | 117 | | showConfig() | View current configuration | `userBehaviour.showConfig()` | 118 | | config() | Update configuration | `userBehaviour.config({...})` | 119 | | start() | Start tracking | `userBehaviour.start()` | 120 | | stop() | Stop tracking | `userBehaviour.stop()` | 121 | | showResult() | Get current dataset | `userBehaviour.showResult()` | 122 | | processResults() | Force immediate data processing | `userBehaviour.processResults()` | 123 | 124 | ## 🚀 Tracking 125 | 126 | Start tracking with: 127 | 128 | ```javascript 129 | userBehaviour.start(); 130 | ``` 131 | 132 | Track custom events: 133 | 134 | ```javascript 135 | userBehaviour.registerCustomEvent("surveyCompleted", (e) => { 136 | console.log("Survey completed:", e.detail); 137 | }); 138 | ``` 139 | 140 | Manual data processing: 141 | 142 | ```javascript 143 | userBehaviour.processResults(); 144 | ``` 145 | 146 | Stop tracking with: 147 | 148 | ```javascript 149 | userBehaviour.stop(); 150 | ``` 151 | 152 | ## 🎈 Results 153 | 154 | To view the results at anytime after the tracking has started: 155 | 156 | ```javascript 157 | userBehaviour.showResult(); 158 | ``` 159 | 160 | The result will be passed to a function set regularly with an interval set in the [configuration](#config) section. 161 | 162 | The data could also be sent via a POST request using any HTTP request libraries e.g axios, ajax, ... 163 | 164 | ```javascript 165 | processData: function(results){ 166 | axios.post('https://example.com', results); 167 | } 168 | ``` 169 | 170 | If processTime was set to false, data will not be processed automatically. Therefore, you might require to process the data manually with: 171 | 172 | ```javascript 173 | userBehaviour.processResults(); 174 | ``` 175 | 176 | This method will still require processData to be set in the configuration. 177 | 178 | ### Example of Result 179 | 180 | ```javascript 181 | { 182 | "userInfo": { 183 | "appCodeName": "Mozilla", 184 | "appName": "Netscape", 185 | "vendor": "Google Inc.", 186 | "platform": "MacIntel", 187 | "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.87 Safari/537.36" 188 | }, 189 | "time": { 190 | "startTime": 1572725042761, 191 | "currentTime": 1572725069204 192 | }, 193 | "clicks": { 194 | "clickCount": 3, 195 | "clickDetails": [ 196 | [ 197 | 554, 198 | 542, 199 | "html>body>div#login>div.ui.container.animated.fadeInDown>div.ui.center.aligned.colored.trends.segment>form.ui.form>div.fields>div.ten.wide.field>input", 200 | 1572725045313 201 | ] 202 | ] 203 | }, 204 | "mouseMovements": [ 205 | [ 206 | 1031, 207 | 328, 208 | 1572725043646 209 | ] 210 | ], 211 | "mouseScroll": [], 212 | "keyboardActivities": [ 213 | ["Enter", 1676543210000], 214 | ["Escape", 1676543220000] 215 | ], 216 | "navigationHistory": [ 217 | ["https://example.com/about", 1676543230000], 218 | ["https://example.com/contact", 1676543240000] 219 | ], 220 | "formInteractions": [ 221 | ["email_signup", 1676543250000], 222 | ["contact_form", 1676543260000] 223 | ], 224 | "touchEvents": [ 225 | ["touchstart", 320, 480, 1676543270000] 226 | ], 227 | "mediaInteractions": [ 228 | ["play", "video.mp4", 1676543280000] 229 | ] 230 | } 231 | ``` 232 | 233 | ## 🎉 Acknowledgements 234 | 235 | - https://github.com/shnere/user-behavior for inispiration. 236 | -------------------------------------------------------------------------------- /userBehaviour.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Taha Al-Jody 3 | * https://github.com/TA3/web-user-behaviour 4 | */ 5 | var userBehaviour = (function () { 6 | var defaults = { 7 | userInfo: true, 8 | clicks: true, 9 | mouseMovement: true, 10 | mouseMovementInterval: 1, 11 | mouseScroll: true, 12 | timeCount: true, 13 | clearAfterProcess: true, 14 | processTime: 15, 15 | windowResize: true, 16 | visibilitychange: true, 17 | keyboardActivity: true, 18 | pageNavigation: true, 19 | formInteractions: true, 20 | touchEvents: true, 21 | audioVideoInteraction: true, 22 | customEventRegistration: true, 23 | processData: function (results) { 24 | console.log(results); 25 | }, 26 | }; 27 | var user_config = {}; 28 | var mem = { 29 | processInterval: null, 30 | mouseInterval: null, 31 | mousePosition: [], //x,y,timestamp 32 | eventListeners: { 33 | scroll: null, 34 | click: null, 35 | mouseMovement: null, 36 | windowResize: null, 37 | visibilitychange: null, 38 | keyboardActivity: null, 39 | touchStart: null 40 | }, 41 | eventsFunctions: { 42 | scroll: () => { 43 | results.mouseScroll.push([window.scrollX, window.scrollY, getTimeStamp()]); 44 | }, 45 | click: (e) => { 46 | results.clicks.clickCount++; 47 | var path = []; 48 | var node = ""; 49 | e.composedPath().forEach((el, i) => { 50 | if ((i !== e.composedPath().length - 1) && (i !== e.composedPath().length - 2)) { 51 | node = el.localName; 52 | (el.className !== "") ? el.classList.forEach((clE) => { 53 | node += "." + clE 54 | }): 0; 55 | (el.id !== "") ? node += "#" + el.id: 0; 56 | path.push(node); 57 | } 58 | }) 59 | path = path.reverse().join(">"); 60 | results.clicks.clickDetails.push([e.clientX, e.clientY, path, getTimeStamp()]); 61 | }, 62 | mouseMovement: (e) => { 63 | mem.mousePosition = [e.clientX, e.clientY, getTimeStamp()]; 64 | }, 65 | windowResize: (e) => { 66 | results.windowSizes.push([window.innerWidth, window.innerHeight, getTimeStamp()]); 67 | }, 68 | visibilitychange: (e) => { 69 | results.visibilitychanges.push([document.visibilityState, getTimeStamp()]); 70 | processResults(); 71 | }, 72 | keyboardActivity: (e) => { 73 | results.keyboardActivities.push([e.key, getTimeStamp()]); 74 | }, 75 | pageNavigation: () => { 76 | results.navigationHistory.push([location.href, getTimeStamp()]); 77 | }, 78 | formInteraction: (e) => { 79 | e.preventDefault(); // Prevent the form from submitting normally 80 | results.formInteractions.push([e.target.name, getTimeStamp()]); 81 | // Optionally, submit the form programmatically after tracking 82 | }, 83 | touchStart: (e) => { 84 | results.touchEvents.push(['touchstart', e.touches[0].clientX, e.touches[0].clientY, getTimeStamp()]); 85 | }, 86 | mediaInteraction: (e) => { 87 | results.mediaInteractions.push(['play', e.target.currentSrc, getTimeStamp()]); 88 | } 89 | } 90 | }; 91 | var results = {}; 92 | 93 | function resetResults() { 94 | results = { 95 | userInfo: { 96 | windowSize: [window.innerWidth, window.innerHeight], 97 | appCodeName: navigator.appCodeName || '', 98 | appName: navigator.appName || '', 99 | vendor: navigator.vendor || '', 100 | platform: navigator.platform || '', 101 | userAgent: navigator.userAgent || '' 102 | }, 103 | time: { 104 | startTime: 0, 105 | currentTime: 0, 106 | stopTime: 0, 107 | }, 108 | clicks: { 109 | clickCount: 0, 110 | clickDetails: [] 111 | }, 112 | mouseMovements: [], 113 | mouseScroll: [], 114 | keyboardActivities: [], 115 | navigationHistory: [], 116 | formInteractions: [], 117 | touchEvents: [], 118 | mediaInteractions: [], 119 | windowSizes: [], 120 | visibilitychanges: [], 121 | }; 122 | }; 123 | resetResults(); 124 | 125 | function getTimeStamp() { 126 | return Date.now(); 127 | }; 128 | 129 | function config(ob) { 130 | user_config = {}; 131 | Object.keys(defaults).forEach((i) => { 132 | i in ob ? user_config[i] = ob[i] : user_config[i] = defaults[i]; 133 | }) 134 | }; 135 | 136 | function start() { 137 | 138 | if (Object.keys(user_config).length !== Object.keys(defaults).length) { 139 | console.log("no config provided. using default.."); 140 | user_config = defaults; 141 | } 142 | // TIME SET 143 | if (user_config.timeCount !== undefined && user_config.timeCount) { 144 | results.time.startTime = getTimeStamp(); 145 | } 146 | // MOUSE MOVEMENTS 147 | if (user_config.mouseMovement) { 148 | mem.eventListeners.mouseMovement = window.addEventListener("mousemove", mem.eventsFunctions.mouseMovement); 149 | mem.mouseInterval = setInterval(() => { 150 | if (mem.mousePosition && mem.mousePosition.length) { 151 | if (!results.mouseMovements.length || ((mem.mousePosition[0] !== results.mouseMovements[results.mouseMovements.length - 1][0]) && (mem.mousePosition[1] !== results.mouseMovements[results.mouseMovements.length - 1][1]))) { 152 | results.mouseMovements.push(mem.mousePosition) 153 | } 154 | } 155 | }, defaults.mouseMovementInterval * 1000); 156 | } 157 | //CLICKS 158 | if (user_config.clicks) { 159 | mem.eventListeners.click = window.addEventListener("click", mem.eventsFunctions.click); 160 | } 161 | //SCROLL 162 | if (user_config.mouseScroll) { 163 | mem.eventListeners.scroll = window.addEventListener("scroll", mem.eventsFunctions.scroll); 164 | } 165 | //Window sizes 166 | if (user_config.windowResize !== false) { 167 | mem.eventListeners.windowResize = window.addEventListener("resize", mem.eventsFunctions.windowResize); 168 | } 169 | //Before unload / visibilitychange 170 | if (user_config.visibilitychange !== false) { 171 | mem.eventListeners.visibilitychange = window.addEventListener("visibilitychange", mem.eventsFunctions.visibilitychange); 172 | } 173 | //Keyboard Activity 174 | if (user_config.keyboardActivity) { 175 | mem.eventListeners.keyboardActivity = window.addEventListener("keydown", mem.eventsFunctions.keyboardActivity); 176 | } 177 | //Page Navigation 178 | if (user_config.pageNavigation) { 179 | window.history.pushState = (f => function pushState() { 180 | var ret = f.apply(this, arguments); 181 | window.dispatchEvent(new Event('pushstate')); 182 | window.dispatchEvent(new Event('locationchange')); 183 | return ret; 184 | })(window.history.pushState); 185 | 186 | window.addEventListener('popstate', mem.eventsFunctions.pageNavigation); 187 | window.addEventListener('pushstate', mem.eventsFunctions.pageNavigation); 188 | window.addEventListener('locationchange', mem.eventsFunctions.pageNavigation); 189 | } 190 | //Form Interactions 191 | if (user_config.formInteractions) { 192 | document.querySelectorAll('form').forEach(form => form.addEventListener('submit', mem.eventsFunctions.formInteraction)); 193 | } 194 | //Touch Events 195 | if (user_config.touchEvents) { 196 | mem.eventListeners.touchStart = window.addEventListener("touchstart", mem.eventsFunctions.touchStart); 197 | } 198 | //Audio & Video Interaction 199 | if (user_config.audioVideoInteraction) { 200 | document.querySelectorAll('video').forEach(video => { 201 | video.addEventListener('play', mem.eventsFunctions.mediaInteraction); 202 | // Add other media events as needed 203 | }); 204 | } 205 | 206 | //PROCESS INTERVAL 207 | if (user_config.processTime !== false) { 208 | mem.processInterval = setInterval(() => { 209 | processResults(); 210 | }, user_config.processTime * 1000) 211 | } 212 | }; 213 | 214 | function processResults() { 215 | user_config.processData(result()); 216 | if (user_config.clearAfterProcess) { 217 | resetResults(); 218 | } 219 | } 220 | 221 | function stop() { 222 | if (user_config.processTime !== false) { 223 | clearInterval(mem.processInterval); 224 | } 225 | clearInterval(mem.mouseInterval); 226 | window.removeEventListener("scroll", mem.eventsFunctions.scroll); 227 | window.removeEventListener("click", mem.eventsFunctions.click); 228 | window.removeEventListener("mousemove", mem.eventsFunctions.mouseMovement); 229 | window.removeEventListener("resize", mem.eventsFunctions.windowResize); 230 | window.removeEventListener("visibilitychange", mem.eventsFunctions.visibilitychange); 231 | window.removeEventListener("keydown", mem.eventsFunctions.keyboardActivity); 232 | window.removeEventListener("touchstart", mem.eventsFunctions.touchStart); 233 | results.time.stopTime = getTimeStamp(); 234 | processResults(); 235 | } 236 | 237 | function result() { 238 | if (user_config.userInfo === false && userBehaviour.showResult().userInfo !== undefined) { 239 | delete userBehaviour.showResult().userInfo; 240 | } 241 | if (user_config.timeCount !== undefined && user_config.timeCount) { 242 | results.time.currentTime = getTimeStamp(); 243 | } 244 | return results 245 | }; 246 | 247 | function showConfig() { 248 | if (Object.keys(user_config).length !== Object.keys(defaults).length) { 249 | return defaults; 250 | } else { 251 | return user_config; 252 | } 253 | }; 254 | 255 | return { 256 | showConfig: showConfig, 257 | config: config, 258 | start: start, 259 | stop: stop, 260 | showResult: result, 261 | processResults: processResults, 262 | registerCustomEvent: (eventName, callback) => { 263 | window.addEventListener(eventName, callback); 264 | }, 265 | }; 266 | 267 | })(); 268 | -------------------------------------------------------------------------------- /userBehaviour.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Taha Al-Jody 3 | * https://github.com/TA3/web-user-behaviour 4 | */ 5 | var userBehaviour=function(){var e={userInfo:!0,clicks:!0,mouseMovement:!0,mouseMovementInterval:1,mouseScroll:!0,timeCount:!0,clearAfterProcess:!0,processTime:15,windowResize:!0,visibilitychange:!0,keyboardActivity:!0,pageNavigation:!0,formInteractions:!0,touchEvents:!0,audioVideoInteraction:!0,customEventRegistration:!0,processData:function(e){console.log(e)}},t={},n={processInterval:null,mouseInterval:null,mousePosition:[],eventListeners:{scroll:null,click:null,mouseMovement:null,windowResize:null,visibilitychange:null,keyboardActivity:null,touchStart:null},eventsFunctions:{scroll:()=>{i.mouseScroll.push([window.scrollX,window.scrollY,s()])},click:e=>{i.clicks.clickCount++;var t=[],n="";e.composedPath().forEach(((i,o)=>{o!==e.composedPath().length-1&&o!==e.composedPath().length-2&&(n=i.localName,""!==i.className&&i.classList.forEach((e=>{n+="."+e})),""!==i.id&&(n+="#"+i.id),t.push(n))})),t=t.reverse().join(">"),i.clicks.clickDetails.push([e.clientX,e.clientY,t,s()])},mouseMovement:e=>{n.mousePosition=[e.clientX,e.clientY,s()]},windowResize:e=>{i.windowSizes.push([window.innerWidth,window.innerHeight,s()])},visibilitychange:e=>{i.visibilitychanges.push([document.visibilityState,s()]),r()},keyboardActivity:e=>{i.keyboardActivities.push([e.key,s()])},pageNavigation:()=>{i.navigationHistory.push([location.href,s()])},formInteraction:e=>{e.preventDefault(),i.formInteractions.push([e.target.name,s()])},touchStart:e=>{i.touchEvents.push(["touchstart",e.touches[0].clientX,e.touches[0].clientY,s()])},mediaInteraction:e=>{i.mediaInteractions.push(["play",e.target.currentSrc,s()])}}},i={};function o(){i={userInfo:{windowSize:[window.innerWidth,window.innerHeight],appCodeName:navigator.appCodeName||"",appName:navigator.appName||"",vendor:navigator.vendor||"",platform:navigator.platform||"",userAgent:navigator.userAgent||""},time:{startTime:0,currentTime:0,stopTime:0},clicks:{clickCount:0,clickDetails:[]},mouseMovements:[],mouseScroll:[],keyboardActivities:[],navigationHistory:[],formInteractions:[],touchEvents:[],mediaInteractions:[],windowSizes:[],visibilitychanges:[]}}function s(){return Date.now()}function r(){t.processData(c()),t.clearAfterProcess&&o()}function c(){return!1===t.userInfo&&void 0!==userBehaviour.showResult().userInfo&&delete userBehaviour.showResult().userInfo,void 0!==t.timeCount&&t.timeCount&&(i.time.currentTime=s()),i}return o(),{showConfig:function(){return Object.keys(t).length!==Object.keys(e).length?e:t},config:function(n){t={},Object.keys(e).forEach((i=>{t[i]=i in n?n[i]:e[i]}))},start:function(){var o;Object.keys(t).length!==Object.keys(e).length&&(console.log("no config provided. using default.."),t=e),void 0!==t.timeCount&&t.timeCount&&(i.time.startTime=s()),t.mouseMovement&&(n.eventListeners.mouseMovement=window.addEventListener("mousemove",n.eventsFunctions.mouseMovement),n.mouseInterval=setInterval((()=>{n.mousePosition&&n.mousePosition.length&&(!i.mouseMovements.length||n.mousePosition[0]!==i.mouseMovements[i.mouseMovements.length-1][0]&&n.mousePosition[1]!==i.mouseMovements[i.mouseMovements.length-1][1])&&i.mouseMovements.push(n.mousePosition)}),1e3*e.mouseMovementInterval)),t.clicks&&(n.eventListeners.click=window.addEventListener("click",n.eventsFunctions.click)),t.mouseScroll&&(n.eventListeners.scroll=window.addEventListener("scroll",n.eventsFunctions.scroll)),!1!==t.windowResize&&(n.eventListeners.windowResize=window.addEventListener("resize",n.eventsFunctions.windowResize)),!1!==t.visibilitychange&&(n.eventListeners.visibilitychange=window.addEventListener("visibilitychange",n.eventsFunctions.visibilitychange)),t.keyboardActivity&&(n.eventListeners.keyboardActivity=window.addEventListener("keydown",n.eventsFunctions.keyboardActivity)),t.pageNavigation&&(window.history.pushState=(o=window.history.pushState,function(){var e=o.apply(this,arguments);return window.dispatchEvent(new Event("pushstate")),window.dispatchEvent(new Event("locationchange")),e}),window.addEventListener("popstate",n.eventsFunctions.pageNavigation),window.addEventListener("pushstate",n.eventsFunctions.pageNavigation),window.addEventListener("locationchange",n.eventsFunctions.pageNavigation)),t.formInteractions&&document.querySelectorAll("form").forEach((e=>e.addEventListener("submit",n.eventsFunctions.formInteraction))),t.touchEvents&&(n.eventListeners.touchStart=window.addEventListener("touchstart",n.eventsFunctions.touchStart)),t.audioVideoInteraction&&document.querySelectorAll("video").forEach((e=>{e.addEventListener("play",n.eventsFunctions.mediaInteraction)})),!1!==t.processTime&&(n.processInterval=setInterval((()=>{r()}),1e3*t.processTime))},stop:function(){!1!==t.processTime&&clearInterval(n.processInterval),clearInterval(n.mouseInterval),window.removeEventListener("scroll",n.eventsFunctions.scroll),window.removeEventListener("click",n.eventsFunctions.click),window.removeEventListener("mousemove",n.eventsFunctions.mouseMovement),window.removeEventListener("resize",n.eventsFunctions.windowResize),window.removeEventListener("visibilitychange",n.eventsFunctions.visibilitychange),window.removeEventListener("keydown",n.eventsFunctions.keyboardActivity),window.removeEventListener("touchstart",n.eventsFunctions.touchStart),i.time.stopTime=s(),r()},showResult:c,processResults:r,registerCustomEvent:(e,t)=>{window.addEventListener(e,t)}}}(); --------------------------------------------------------------------------------