1(function () { 2 // globals 3 let isNavigationOrRefreshEvent = false; 4 let isOnline = true; 5 let idle = initIdleTimer({ idle: 1800000 }); // 30m idle timeout 6 const HEARTBEAT_TIME_MIN = 5; 7 8 idle.start(); 9 10 // events 11 window.addEventListener( 12 "pageshow", 13 function () { 14 handleActiveNow({ name: "pageshow", active: 1 }); 15 }, 16 { capture: true } 17 ); 18 19 window.addEventListener( 20 "pagehide", 21 function () { 22 handleActiveNow({ name: "pagehide", active: 0 }); 23 }, 24 { capture: true } 25 ); 26 27 document.addEventListener( 28 "visibilitychange", 29 function () { 30 handleActiveNow({ 31 name: "visibility", 32 active: document.visibilityState === "visible" ? 1 : 0, 33 }); 34 }, 35 { capture: true } 36 ); 37 38 document.addEventListener( 39 "focus", 40 function () { 41 handleActiveNow({ name: "focus", active: 1 }); 42 }, 43 { capture: true } 44 ); 45 46 const debouncedScrollEvent = debouncedHandleActiveNow({ name: "scroll", active: 1 }, { delay: 1000 }); 47 document.addEventListener( 48 "scroll", 49 function () { 50 debouncedScrollEvent(); 51 }, 52 { capture: true } 53 ); 54 55 const debouncedMouseupEvent = debouncedHandleActiveNow({ name: "mouseup", active: 1 }, { delay: 1000 }); 56 document.addEventListener( 57 "mouseup", 58 function (mouseEvent) { 59 debouncedMouseupEvent(); 60 handleIsNavigationEvent(mouseEvent); 61 }, 62 { capture: true } 63 ); 64 65 const debouncedKeyupEvent = debouncedHandleActiveNow({ name: "touchend", active: 1 }, { delay: 1000 }); 66 document.addEventListener( 67 "touchend", 68 function (touchendEvent) { 69 debouncedKeyupEvent(); 70 handleIsNavigationEvent(touchendEvent); 71 }, 72 { capture: true } 73 ); 74 75 window.addEventListener( 76 "online", 77 function () { 78 isOnline = true; 79 }, 80 { capture: true } 81 ); 82 83 window.addEventListener( 84 "online", 85 function () { 86 isOnline = false; 87 }, 88 { capture: true } 89 ); 90 91 // FUNCTIONS/API 92 93 /** 94 * Saves guest preference in our system 95 * 96 * @param {string} name Name of the event that triggered status change 97 * @param {number} status Status to change lead status to 98 * @param {FormData} data Data to send enapoint to 99 * @returns 100 */ 101 function saveGuestPreference(name, data) { 102 // Activity belongs to a lead. Anonymous visitors and crawlers without a 103 // current or historical lead cookie must not call the Drupal endpoint. 104 if (!getCookie("lead_id") && !getCookie("lead_ids")) { 105 return; 106 } 107 108 const lastEvent = getPreviousEvent(); 109 const currentEvent = { 110 type: name, 111 activeNow: Number(data.get("active_now")), 112 }; 113 114 // if event has not changed do not send 115 if (!isValidEvent(lastEvent, currentEvent)) { 116 isNavigationOrRefreshEvent = false; 117 localStorage.setItem("_iwm_gp_last_event_type_denied", currentEvent.type); 118 return; 119 } else { 120 localStorage.removeItem("_iwm_gp_last_event_type_denied"); 121 } 122 123 // sendBeacon is a more reliable way of sending HTTP request than ajax on page close. 124 navigator.sendBeacon("/active_now/save", data); 125 localStorage.setItem("_iwm_gp_last_event_time", Date.now()); 126 localStorage.setItem("_iwm_gp_last_event_type", currentEvent.type); 127 localStorage.setItem("_iwm_gp_last_active_now", currentEvent.activeNow); 128 129 if (currentEvent.type !== "idle") { 130 idle.reset(); 131 } 132 } 133 134 /** 135 * Read a cookie value by its exact name. 136 * 137 * @param {string} name 138 * @returns {string} 139 */ 140 function getCookie(name) { 141 const prefix = name + "="; 142 const cookies = document.cookie ? document.cookie.split(";") : []; 143 144 for (let index = 0; index < cookies.length; index += 1) { 145 const cookie = cookies[index].trim(); 146 if (cookie.indexOf(prefix) === 0) { 147 return cookie.substring(prefix.length); 148 } 149 } 150 151 return ""; 152 } 153 154 /** 155 * Retrieve the last event that triggered a lead status change 156 * 157 */ 158 function getPreviousEvent() { 159 const previousEvtTypeDenied = localStorage.getItem("_iwm_gp_last_event_type_denied"); 160 const previousEvtTime = localStorage.getItem("_iwm_gp_last_event_time"); 161 const previousEvtType = localStorage.getItem("_iwm_gp_last_event_type"); 162 const previousEvtActiveNow = localStorage.getItem("_iwm_gp_last_active_now"); 163 const previousEvtObject = { 164 timeElapsed: 0, 165 type: previousEvtType, 166 eventTime: previousEvtTime, 167 activeNow: previousEvtActiveNow, 168 denied: previousEvtTypeDenied || "", 169 }; 170 171 if (!previousEvtTime) { 172 return false; 173 } 174 175 // calculate time since last event 176 previousEvtObject.timeElapsed = Math.abs(Math.round((Date.now() - previousEvtTime) / 60000)); 177 178 return previousEvtObject; 179 } 180 181 /** 182 * Handle lead active status toggle 183 * 184 * @param {object} event 185 * @param {string} event.name The event that triggered the toggle 186 * @param {number} event.active Wether user is active or not. 0 and 1 for inactive and active respectively. 187 * @returns 188 */ 189 function handleActiveNow(event) { 190 const name = event.name; 191 const status = event.active; 192 // data sent needs to be form data type for endpoint to ingest 193 const data = new FormData(); 194 data.append("active_now", status); 195 saveGuestPreference(name, data); 196 } 197 198 /** 199 * Debounced handle active now 200 * 201 * @param {*} event 202 * @param {*} options 203 * @returns 204 */ 205 function debouncedHandleActiveNow(event, options) { 206 return debounce(function () { 207 handleActiveNow(event); 208 }, options); 209 } 210 211 /** 212 * General debounce fn impl. 213 * 214 * @param {*} fn 215 * @param {*} options 216 * @returns 217 */ 218 function debounce(fn, options = {}) {
219 const delay = options.delay || 500; 220 let timeout; 221 return function (...args) { 222 clearTimeout(timeout); 223 timeout = setTimeout(() => { 224 fn(...args); 225 }, delay); 226 }; 227 } 228 229 /** 230 * Determiness if an event is valid and should be sent 231 * 232 * @param {*} previousEvent 233 * @param {*} currentEvent 234 * @returns 235 */ 236 function isValidEvent(previousEvent, currentEvent) { 237 // first load allow event right away 238 if (!previousEvent) { 239 return true; 240 } 241 242 // dont send events when offline 243 if (!isOnline) { 244 return false; 245 } 246 247 // dont send events on page navigation or reload 248 if (!currentEvent.activeNow && isNavigationOrRefreshEvent) { 249 return false; 250 } 251 252 // if previous event was pagehide and it was denied, then deny next visibility hidden 253 // some browsers do both pagehide and visibility on navigation 254 if ( 255 !currentEvent.activeNow && 256 currentEvent.type === "visibility" && 257 previousEvent.denied && 258 previousEvent.denied === "pagehide" 259 ) { 260 return false; 261 } 262 263 if (previousEvent.activeNow != currentEvent.activeNow) { 264 // on event change allow event 265 return true; 266 } 267 268 // only allow active events within heartbeat timeframe 269 if (previousEvent.timeElapsed < HEARTBEAT_TIME_MIN) { 270 return false; 271 } 272 273 return true; 274 } 275 276 /** 277 * Handles checking if refresh is due to navigation. Refresh is considered navigation if a link tag was clicked. 278 * 279 * @param {MouseEvent | TouchEvent} event 280 */ 281 function handleIsNavigationEvent(event) { 282 // only process user initiated events 283 if (!event.isTrusted) { 284 return; 285 } 286 287 if (!event.target) { 288 return; 289 } 290 291 if (!event.target.nodeName) { 292 return; 293 } 294 295 const nodeName = event.target.nodeName; 296 const href = event.target.href ? event.target.href : ""; 297 if (nodeName === "A" && href.length && href != "#") { 298 isNavigationOrRefreshEvent = true; 299 } 300 } 301 302 // idle timer 303 function initIdleTimer(options = {}) { 304 const delay = options.idle || 1800000; 305 var idleTimer; 306 307 return { 308 start: function () { 309 idleTimer = setTimeout(() => { 310 handleActiveNow({ name: "idle", active: 0 }); 311 }, delay); 312 }, 313 reset: function () { 314 clearTimeout(idleTimer); 315 idleTimer = setTimeout(() => { 316 handleActiveNow({ name: "idle", active: 0 }); 317 }, delay); 318 }, 319 stop: function () { 320 clearTimeout(idleTimer); 321 }, 322 }; 323 } 324})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.