PageSourceSearch

https://krisp.ai/wp-content/themes/krisp-v4/js/app-helpers.js?ver=24.08.03.21

js krisp.ai collected 2026-09-24 08:24:33 UTC 13,039 bytes, 420 lines download raw bytes

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.