1const FormHelpers = {}; 2const AnalyticsHelpers = {}; 3const ThirdPartyHelpers = {}; 4const UiHelpers = {}; 5 6FormHelpers.pushAndOverwrite = (array, name, value, isCheckbox = false) => { 7 if ( 8 (typeof value === 'string' && value.trim() !== '') || 9 (typeof value !== 'string' && value !== null && value !== undefined) 10 ) { 11 const existingIndex = array.findIndex(function (item) { 12 return item.name === name; 13 }); 14 15 if (existingIndex === -1) { 16 if (isCheckbox) { 17 array.push({ name, value: [value] }); 18 } else { 19 array.push({ name, value }); 20 } 21 } else { 22 if (isCheckbox) { 23 let currentValue = array[existingIndex].value; 24 if (!Array.isArray(currentValue)) { 25 currentValue = [currentValue]; 26 } 27 if (!currentValue.includes(value)) { 28 currentValue.push(value); 29 } 30 array[existingIndex].value = currentValue; 31 } else { 32 array[existingIndex].value = value; 33 } 34 } 35 } 36}; 37 38FormHelpers.getFieldValue = (fields, name) => { 39 const field = fields.find((f) => f.name === name); 40 return field ? encodeURIComponent(field.value) : ''; 41}; 42 43FormHelpers.removeFromArray = (array, name, value) => { 44 const existingIndex = array.findIndex((item) => item.name === name); 45 if (existingIndex !== -1) { 46 let existingValues = array[existingIndex].value; 47 48 if (Array.isArray(existingValues)) { 49 existingValues = existingValues.filter((val) => val !== value); 50 51 if (existingValues.length === 0) { 52 array.splice(existingIndex, 1); 53 } else {
54 array[existingIndex].value = existingValues; 55 } 56 } 57 } 58}; 59 60// Amplitude only initialises once OneTrust grants the performance category, 61// which regularly lands after the first events are raised. Handing those to the 62// pre-init snippet stub loses them, so they are held here and flushed in order 63// the moment the SDK reports a device id â its "initialised" tell. 64(function () { 65 // Consent is often granted minutes after the first events are raised (the 66 // visitor reads the banner, or ignores it for a while), and those events must 67 // still land â the old snippet-stub path had no deadline at all. So the wait 68 // is long, but it steps down to a cheap heartbeat after the window where 69 // sub-second delivery actually matters, and ends eventually: consent may never 70 // be granted, or the SDK may be blocked outright. 71 const FAST_POLL_INTERVAL = 200; 72 const FAST_POLL_WINDOW = 30000; 73 const SLOW_POLL_INTERVAL = 5000; 74 const READY_TIMEOUT = 1800000; 75 const MAX_QUEUE = 50; 76 77 const queue = []; 78 let poll = null; 79 80 // ?kr_debug_analytics=1 (or localStorage kr_debug_analytics = '1', which 81 // survives navigation) logs what happened to every event, so "did that reach 82 // Amplitude?" is answerable from the console without a build. 83 const debugOn = (function () { 84 try { 85 return ( 86 new URLSearchParams(window.location.search).has('kr_debug_analytics') || 87 window.localStorage.getItem('kr_debug_analytics') === '1' 88 ); 89 } catch (e) { 90 return false; 91 } 92 })(); 93 94 function log(state, event, properties) { 95 if (!debugOn) return; 96 console.log('[analytics:' + state + ']', 'website:' + event, properties || ''); 97 } 98 99 function isReady() { 100 const amp = window.amplitude; 101 if (!amp || typeof amp.getDeviceId !== 'function') return false; 102 try { 103 // The stub returns undefined; a real, initialised client returns an id. 104 return !!amp.getDeviceId(); 105 } catch (e) { 106 return false; 107 } 108 } 109 110 function track(event, properties) { 111 try { 112 window.amplitude.track(`website:${event}`, properties); 113 log('sent', event, properties); 114 } catch (e) { 115 // One bad event must never break the page or stall the rest of the queue. 116 log('failed', event, e && e.message); 117 } 118 } 119 120 // Compared against null rather than tested for truthiness: a timer handle of 0 121 // is legal, and treating it as "no timer" would start a second interval that 122 // nothing could then clear. 123 function stopPolling() { 124 if (poll === null) return; 125 window.clearInterval(poll); 126 poll = null; 127 } 128 129 function watch(startedAt, interval) { 130 if (poll !== null) return; 131 const since = startedAt || Date.now(); 132 poll = window.setInterval(function () { 133 // Anything escaping here would leave the timer running and re-throwing 134 // every tick, so the timer is always stopped on failure. 135 try { 136 const waited = Date.now() - since; 137 138 if (isReady()) { 139 stopPolling(); 140 log('ready', 'flushing ' + queue.length + ' queued event(s)'); 141 while (queue.length) { 142 const queued = queue.shift(); 143 track(queued.event, queued.properties); 144 } 145 return; 146 } 147 148 if (waited > READY_TIMEOUT) { 149 stopPolling(); 150 log('dropped', queue.length + ' event(s) - Amplitude never initialised'); 151 queue.length = 0; 152 return; 153 } 154 155 // Step down to the heartbeat once sub-second delivery stops mattering. 156 if (interval !== SLOW_POLL_INTERVAL && waited > FAST_POLL_WINDOW) { 157 stopPolling(); 158 watch(since, SLOW_POLL_INTERVAL); 159 } 160 } catch (e) { 161 stopPolling(); 162 queue.length = 0; 163 } 164 }, interval || FAST_POLL_INTERVAL); 165 } 166 167 AnalyticsHelpers.sendEvent = (event, properties = {}) => { 168 // Callers raise events from submit-success paths; a failure in here must 169 // never take the caller down with it. 170 try { 171 if (isReady()) { 172 track(event, properties); 173 return; 174 } 175 // A page left open for hours with consent never granted should not grow 176 // this without bound. 177 if (queue.length >= MAX_QUEUE) queue.shift(); 178 queue.push({ event, properties }); 179 log('queued', event, properties); 180 watch(); 181 } catch (e) { 182 // Swallowed on purpose: analytics is never worth a broken page. 183 } 184 }; 185 186 // Read-only debug view of what is still waiting on consent/init. Returns 187 // copies, so poking at the result can never alter or drop a queued event, and 188 // it swallows its own failures â a debug helper must not break a page. 189 AnalyticsHelpers.pendingEvents = () => { 190 try { 191 return queue.map((q) => ({ event: q.event, properties: q.properties })); 192 } catch (e) { 193 return []; 194 } 195 }; 196})(); 197 198const GTAG_ID = 199 'krisp.ai' === window.location.hostname ? 'G-1KQ247DGBJ' : 'G-ZCP24MJ0RW'; 200 201window.dataLayer = window.dataLayer || []; 202 203function gtag() { 204 window.dataLayer.push(arguments); 205} 206 207gtag('consent', 'default', { 208 ad_storage: 'denied', 209 ad_user_data: 'denied', 210 ad_personalization: 'denied', 211 analytics_storage: 'denied', 212}); 213 214const AMPLITUDE_KEY = 215 'krisp.ai' === window.location.hostname 216 ? '01b641bf88b394e1f502ca56d9f6d0a1'
217 : '6d5fd31691509c665fc2c20a8b8c8612'; 218 219// Amplitude is the only tag left relying on OneTrust rewriting a text/plain 220// block, and that rewrite does not always happen - when it doesn't, nothing 221// ever initialises the SDK and every queued event is dropped. This runs the 222// same init from the consent gate the rest of the site already uses. The flag 223// is shared with the text/plain block so whichever fires first wins and the 224// other is a no-op. 225AnalyticsHelpers.initAmplitude = () => { 226 if (window.__krispAmplitudeInit) return; 227 if (!window.amplitude || typeof window.amplitude.init !== 'function') return; 228 window.__krispAmplitudeInit = true; 229 window.amplitude.init(AMPLITUDE_KEY, undefined, { 230 minIdLength: 1, 231 serverUrl: typeof serverUrl !== 'undefined' ? serverUrl : undefined, 232 }); 233}; 234 235AnalyticsHelpers.initGtag = () => { 236 gtag('consent', 'update', { 237 ad_user_data: 'granted', 238 ad_personalization: 'granted', 239 ad_storage: 'granted', 240 analytics_storage: 'granted', 241 }); 242 gtag('js', new Date()); 243 gtag('config', GTAG_ID); 244 window.gtag = gtag; 245}; 246 247AnalyticsHelpers.setAnalyticsEvent = (event, body) => { 248 if (!window.gtag || event === 'pageview') return; 249 const payload = { ...body, send_to: GTAG_ID }; 250 window.gtag('event', event, payload); 251}; 252 253ThirdPartyHelpers.initZendesk = () => { 254 var key, 255 script = document.createElement('script'); 256 key = 'cccd5f74-66f6-43bc-9eb8-644df922cbb0'; 257 script.async = false; 258 script.src = 'https://static.zdassets.com/ekr/snippet.js?key=' + key; 259 script.id = 'ze-snippet'; 260 script.defer = true; 261 document.getElementsByTagName('head')[0].appendChild(script); 262}; 263 264ThirdPartyHelpers.initHbCalendar = () => { 265 var script = document.createElement('script'); 266 script.async = false; 267 script.src = 268 'https://static.hsappstatic.net/MeetingsEmbed/ex/MeetingsEmbedCode.js'; 269 script.id = 'hb-calendar'; 270 script.defer = true; 271 document.getElementsByTagName('head')[0].appendChild(script); 272}; 273 274UiHelpers.isInView = (element) => { 275 const { top, bottom } = element.getBoundingClientRect(); 276 const vHeight = window.innerHeight || document.documentElement.clientHeight; 277 278 return (top > 0 || bottom > 0) && top < vHeight; 279}; 280 281UiHelpers.delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); 282 283UiHelpers.intervalTimer = function (callback, interval) { 284 var timerId, 285 startTime, 286 remaining = 0; 287 var state = 0; 288 // 0 = idle, 1 = running, 2 = paused, 3= resumed 289 290 this.pause = function () { 291 if (state == 2) return; 292 remaining = Math.max(0, interval - (new Date() - startTime)); 293 window.clearInterval(timerId); 294 state = 2; 295 }; 296 297 this.resume = function () { 298 if (state !== 2) return; 299 let that = this; 300 state = 3; 301 window.setTimeout(function () { 302 that.timeoutCallback(); 303 }, remaining); 304 remaining = Math.max(0, interval - (new Date() - startTime)); 305 }; 306 307 this.timeoutCallback = function () { 308 if (state !== 3) return; 309 310 callback(); 311 312 startTime = new Date(); 313 timerId = window.setInterval(callback, interval); 314 state = 1; 315 }; 316 317 this.getRemaining = function () { 318 return Math.max(0, interval - (new Date() - startTime)); 319 }; 320 321 this.getState = function () { 322 return state; 323 }; 324 325 startTime = new Date(); 326 timerId = window.setInterval(callback, interval); 327 state = 1; 328}; 329 330UiHelpers.isLargeScreen = () => window.innerWidth > 1024; 331 332UiHelpers.isMobile = () => window.innerWidth < 769; 333 334UiHelpers.animateLoader = (loader, sec, currentWidth = 0) => { 335 const targetWidth = Math.min((currentWidth + 1) * 100, 100); 336 loader.animate( 337 { width: `${targetWidth}%` }, 338 { 339 duration: sec, 340 easing: 'linear', 341 } 342 ); 343}; 344 345UiHelpers.delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); 346 347const UA = navigator.userAgent || navigator.vendor || window.opera; 348 349UiHelpers.isIOS = () => { 350 const ua = navigator.userAgent; 351 const platform = navigator.platform; 352 const hasTouch = navigator.maxTouchPoints > 1; 353 354 // Old iOS: UA contains iPhone/iPod/iPad 355 const isiOSUA = /iPad|iPhone|iPod/.test(ua); 356 357 // New iPadOS: platform is MacIntel but it still has touch 358 const isiPadOS = platform === 'MacIntel' && hasTouch; 359 360 return isiOSUA || isiPadOS; 361}; 362 363UiHelpers.isMobileOrTablet = () => { 364 const mobileHint = navigator.userAgentData?.mobile; 365 const ua = navigator.userAgent || ''; 366 367 if (window.matchMedia('(pointer: coarse) and (hover: none)').matches) { 368 return true; 369 } 370 const mobileRE = 371 /Android.*Mobile|iPhone|IEMobile|BlackBerry|Opera Mini|Mobile/i; 372 const tabletRE = /iPad|Android(?!.*Mobile)|Tablet|PlayBook|Silk|Kindle/i; 373 return typeof mobileHint === 'boolean' 374 ? mobileHint 375 : mobileRE.test(ua) || tabletRE.test(ua); 376}; 377 378UiHelpers.swapMainAppCTAs = () => { 379 if (!UiHelpers.isMobileOrTablet()) return; 380 381 const targetURL = UiHelpers.isIOS() ? APP_STORE_URL : PLAY_STORE_URL; 382 383 document 384 .querySelectorAll(`.main_app_btn > a, .cta_primary > a`) 385 .forEach((a) => { 386 a.href = targetURL; 387 if (!a.closest('.pricing_app_btn') && !a.closest('.banner_download')) { 388 a.textContent = 'Download Krisp'; 389 } 390 }); 391}; 392 393UiHelpers.isSafari = () => { 394 const ua = navigator.userAgent; 395 return ( 396 /Safari/.test(ua) && !/Chrome|Chromium|CriOS|FxiOS|OPiOS|EdgiOS/.test(ua) 397 ); 398}; 399 400UiHelpers.isMobileSafari = () => 401 !!navigator.userAgent.match(/Version\/[\d\.]+.*Safari/); 402 403// Safari on iOS, and nothing else. Apple draws its own Smart Ap
403p Banner there 404// (the apple-itunes-app meta in header.php), so the custom download banner 405// stands down for it alone. 406// 407// `Version/` on its own does not mean Safari: Android WebView and the older 408// stock browsers carry `Version/4.0`, so matching on it left those users with 409// no banner at all - Apple's never appears off iOS. The other iOS browsers and 410// the in-app webviews get no native banner either, so they keep the custom one. 411UiHelpers.isIosSafari = () => { 412 const ua = navigator.userAgent; 413 414 if (!UiHelpers.isIOS()) return false; 415 if (/CriOS|FxiOS|OPiOS|EdgiOS|FBAN|FBAV|Instagram|Line\/|GSA\//.test(ua)) { 416 return false; 417 } 418 419 return /Version\/[\d.]+.*Safari/.test(ua); 420};
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.