PageSourceSearch

https://mentalnote.ai/js/gtag-events.js?v=mentalnote-undecided-passthrough-20260824

js mentalnote.ai collected 2026-10-05 12:15:04 UTC 45,378 bytes, 1,239 lines download raw bytes

1// Conversion-event autotracking for mentalnote (added 2026-04-30).
2// This file is also the single acquisition-linking layer for the marketing
3// site. It keeps a bounded local first-touch record, while cross-domain CTAs
4// retain their authored current campaign and receive only safe bridge fields.
5// It never reads or stores clinical form fields, note text, recordings, or
6// generated output.
7(function (root, factory) {
8  var api = factory();
9  if (typeof module === 'object' && module.exports) module.exports = api;
10  if (!root || !root.document || !root.location) return;
11
12  var ACQUISITION_STORAGE_KEY = 'mentalnote_acquisition_v1';
13  var acquisition = {};
14  var acquisitionInstalled = false;
15
16  function hasAdvertisingConsent() {
17    return !!(
18      root.GTConsent
19      && typeof root.GTConsent.hasAdvertisingConsent === 'function'
20      && root.GTConsent.hasAdvertisingConsent()
21    );
22  }
23
24  function hasConsentDecision() {
25    return !!(
26      root.GTConsent
27      && typeof root.GTConsent.hasConsentDecision === 'function'
28      && root.GTConsent.hasConsentDecision()
29    );
30  }
31
32  function isConsentUndecided() {
33    return !hasConsentDecision() && !hasAdvertisingConsent();
34  }
35
36  function replaceAcquisition(next) {
37    Object.keys(acquisition).forEach(function (key) { delete acquisition[key]; });
38    Object.keys(next || {}).forEach(function (key) { acquisition[key] = next[key]; });
39  }
40
41  function syncAcquisitionConsent() {
42    if (!hasAdvertisingConsent()) {
43      api.purgeAcquisition(api.safeStorage(root, {}), ACQUISITION_STORAGE_KEY);
44      api.clearAttributionTargets(root);
45      if (isConsentUndecided()) {
46        // No decision yet: nothing is read from or written to storage, but the
47        // visitor's own current-page attribution may travel on the web-app
48        // handoff URL. The web app holds it in memory and persists it only if
49        // advertising measurement is granted there; a Reject at either end
50        // discards it.
51        api.installUndecidedPassthrough(root, {
52          rawAdIdentifiersEnabled: api.DEFAULT_RAW_AD_IDENTIFIERS_ENABLED,
53          shouldPassThrough: isConsentUndecided,
54        });
55      } else {
56        api.installCurrentPageHandoff(root);
57      }
58      replaceAcquisition({});
59      acquisitionInstalled = false;
60      return;
61    }
62    if (acquisitionInstalled) return;
63    try {
64      replaceAcquisition(api.installAcquisition(root, {
65        storageKey: ACQUISITION_STORAGE_KEY,
66        isConsentGranted: hasAdvertisingConsent,
67        rawAdIdentifiersEnabled: api.DEFAULT_RAW_AD_IDENTIFIERS_ENABLED,
68        onAcquisitionChanged: replaceAcquisition,
69      }));
70      acquisitionInstalled = true;
71    } catch (_) {
72      // Attribution is an enhancement. A restricted browser must still keep
73      // normal links and the site's existing event handling usable.
74    }
75  }
76
77  syncAcquisitionConsent();
78  if (typeof root.addEventListener === 'function') {
79    root.addEventListener('gt:consent-changed', syncAcquisitionConsent);
80  }
81
82  var PAID_VARIANTS = {
83    'ai-soap-note-generator-for-therapists': 'mn_paid_01',
84    'ai-progress-note-scribe-for-therapists': 'mn_paid_02',
85    'therapy-session-to-clinical-note': 'mn_paid_03',
86  };
87
88  function hasAnalyticsConsent() {
89    return !!(
90      root.GTConsent
91      && typeof root.GTConsent.hasAnalyticsConsent === 'function'
92      && root.GTConsent.hasAnalyticsConsent()
93    );
94  }
95
96  function safeEventPath() {
97    if (root.GTConsent && typeof root.GTConsent.safeAnalyticsPath === 'function') {
98      return root.GTConsent.safeAnalyticsPath(root.location.pathname);
99    }
100    return '/content';
101  }
102
103  function sendEvent(name, params) {
104    if (hasAnalyticsConsent() && typeof root.gtag === 'function') {
105      root.gtag('event', name, params);
106      return true;
107    }
108    return false;
109  }
110
111  function toolSlug() {
112    var match = root.location.pathname.match(/\/tools\/([a-z0-9-]+?)(?:\.html)?\/?$/);
113    return match && match[1] !== 'index' ? match[1] : 'unknown';
114  }
115
116  function onToolPage() {
117    return toolSlug() !== 'unknown';
118  }
119
120  function paidLandingContext() {
121    var body = root.document.body;
122    if (!body || !body.getAttribute) return null;
123    var landingSlug = api.safeCampaignValue(body.getAttribute('data-paid-landing') || '');
124    var landingVariant = PAID_VARIANTS[landingSlug] || '';
125    if (!landingVariant) return null;
126    return {
127      landing_variant: landingVariant,
128      campaign_variant: landingVariant.replace('paid', 'campaign'),
129    };
130  }
131
132  root.document.addEventListener('click', function (event) {
133    var anchor = event.target && event.target.closest && event.target.closest('a[href]');
134    if (!anchor) return;
135    var href = anchor.getAttribute('href') || '';
136    var originalHref = anchor.getAttribute('data-attribution-original-href') || href;
137
138    if (/(?:appsource|marketplace)\.microsoft\.com/.test(href)) {
139      sendEvent('install_cta_click', {
140        cta_location: safeEventPath(),
141        cta_href: api.safeTrackedHref(href, root.location.href),
142      });
143      return;
144    }
145
146    if (/^\/go\/webapp/.test(originalHref) || /^https:\/\/app\.mentalnote\.ai/.test(href)) {
147      var params = {
148        cta_location: safeEventPath(),
149        cta_href: api.safeTrackedHref(href, root.location.href),
150        acquisition_present: api.ACQUISITION_KEYS.some(function (key) {
151          return !!acquisition[key];
152        }) ? 'yes' : 'no',
153      };
154      var paidContext = paidLandingContext();
155      if (paidContext) {
156        params.landing_variant = paidContext.landing_variant;
157        params.campaign_variant = paidContext.campaign_variant;
158        params.cta_placement = api.safeCampaignValue(anchor.getAttribute('data-cta-placement') || '') || 'unknown';
159      }
160      sendEvent('webapp_cta_click', params);
161      if (paidContext) sendEvent('paid_landing_cta_click', params);
162    }
163  });
164
165  root.document.addEventListener('submit', function () {
166    if (onToolPage()) sendEvent('tool_form_submit', { tool_category: 'marketing_tool' });
167  }, true);
168
169  if (root.navigator.clipboard && root.navigator.clipboard.writeText) {
170    var originalCopy = root.navigator.clipboard.writeText.bind(root.navigator.clipboard);
171    root.navigator.clipboard.writeText = function (text) {
172      try {
173        if (onToolPage()) sendEvent('tool_result_copied', { tool_category: 'marketing_tool' });
174      } catch (_) {}
175      return originalCopy(text);
176    };
177  }
178
179  var paidLandingViewSent = false;
180
181  function sendPaidLandingView(context) {
182    if (paidLandingViewSent || !context) return;
183    paidLandingViewSent = sendEvent('paid_landing_view', context);
184  }
185
186  function installPaidLandingEvents() {
187    var context = paidLandingContext();
188    if (!context) return;
189
190    sendPaidLandingView(context);
191
192    var demo = root.document.querySelector('[data-paid-demo]');
193    if (demo) {
194      demo.addEventListener('play', function onFirstPlay() {
195        demo.removeEventListener('play', onFirstPlay);
196        sendEvent('paid_landing_demo_start', context);
197      });
198    }
199
200    var faqs = root.document.querySelectorAll('[data-paid-faq]');
201    Array.prototype.forEach.call(faqs, function (faq, index) {
202      faq.addEventListener('toggle', function () {
203        if (!faq.open) return;
204        sendEvent('paid_landing_faq_open', {
205          landing_variant: context.landing_variant,
206          campaign_variant: context.campaign_variant,
207          faq_variant: 'question_' + String(index + 1),
208        });
209      });
210    });
211  }
212
213  if (root.document.readyState === 'loading') {
214    root.document.addEventListener('DOMContentLoaded', installPaidLandingEvents, { once: true });
215  } else {
216    installPaidLandingEvents();
217  }
218
219  if (typeof root.addEventListener === 'function') {
220    root.addEventListener('gt:consent-changed', function () {
221      sendPaidLandingView(paidLandingContext());
222    });
223  }
224})(typeof window !== 'undefined' ? window : null, function () {
225  'use strict';
226
227  var CAMPAIGN_KEYS = [
228    'utm_source',
229    'utm_medium',
230    'utm_campaign',
231    'utm_content',
232  ];
233  var CLICK_KEYS = ['gclid', 'gbraid', 'wbraid', 'msclkid'];
234  var ENTITY_ID_KEYS = ['campaign_id', 'ad_group_id', 'ad_id'];
235  var ENTITY_KEYS = ENTITY_ID_KEYS.concat([
236    'target_id',
237    'match_type',
238    'network',
239    'device',
240  ]);
241  var RAW_AD_IDENTIFIER_KEYS = CLICK_KEYS.concat(ENTITY_KEYS);
242  var ACQUISITION_KEYS = CAMPAIGN_KEYS.concat(CLICK_KEYS, ENTITY_KEYS);
243  var CONTEXT_KEYS = ['gt_landing_path', 'gt_referrer'];
244  var ATTRIBUTION_KEYS = ACQUISITION_KEYS.concat(CONTEXT_KEYS);
245  var BRIDGE_KEYS = CLICK_KEYS.concat(ENTITY_KEYS, CONTEXT_KEYS);
246  var PAID_MEDIA = [
247    'cpc',
248    'ppc',
249    'paid',
250    'paid-search',
251    'paid_search',
252    'paidsearch',
253    'paid-social',
254    'paid_social',
255    'paidsocial',
256    'display',
257    'video',
258  ];
259  var CTA_PARAM_MAP = {
260    utm_source: 'cta_source',
261    utm_medium: 'cta_medium',
262    utm_campaign: 'cta_campaign',
263    utm_content: 'cta_content',
264  };
265  var TOUCH_BRIDGE_KEYS = [
266    'first_utm_source',
267    'first_utm_medium',
268    'first_utm_campaign',
269    'first_landing_path',
270    'first_referrer',
271    'first_captured_at',
272    'paid_first_utm_source',
273    'paid_first_utm_medium',
274    'paid_first_utm_campaign',
275    'paid_first_captured_at',
276    'paid_captured_at',
277  ].concat(Object.keys(CTA_PARAM_MAP).map(function (key) {
278    return CTA_PARAM_MAP[key];
279  }));
280  var FORBIDDEN_SEARCH_KEYS = [
281    'utm_term',
282    'keyword',
283    'query',
284    'search_term',
285    'searchterm',
286    'search_query',
287  ];
288  var DEFAULT_TTL_MS = 90 * 24 * 60 * 60 * 1000;
289  var RAW_AD_IDENTIFIER_TTL_MS = 30 * 24 * 60 * 60 * 1000;
290  var MAX_TIMER_DELAY_MS = 2147483647;
291  // Production passes this flag only from syncAcquisitionConsent(), after the
292  // consent manager proves an active advertising-measurement grant. The helper
293  // APIs still require an explicit true option, so direct/test use fails closed.
294  var DEFAULT_RAW_AD_IDENTIFIERS_ENABLED = true;
295  var ADDIN_ATTRIBUTION_KEYS = ['mktcmpid', 'src'];
296
297  function sanitizeValue(value) {
298    if (typeof value !== 'string') return '';
299    return value.replace(/[\u0000-\u001f\u007f]/g, '').trim().slice(0, 500);
300  }
301
302  function safeCampaignValue(value) {
303    var candidate = sanitizeValue(value).slice(0, 100);
304    return /^[A-Za-z0-9](?:[A-Za-z0-9._-]{0,99})$/.test(candidate) ? candidate : '';
305  }
306
307  function safeClickId(value) {
308    var candidate = sanitizeValue(value).slice(0, 256);
309    return /^[A-Za-z0-9._~-]+$/.test(candidate) ? candidate : '';
310  }
311
312  function safeEntityId(value) {
313    var candidate = sanitizeValue(value);
314    return candidate.length <= 20 && /^[0-9]{1,20}$/.test(candidate) ? candidate : '';
315  }
316
317  function safeTargetId(value) {
318    var candidate = sanitizeValue(value).toLowerCase();
319    if (candidate.length > 256) return '';
320    return /^(?:aud|dat|dsa|hpi|kwd|kwl|loc|pla)-[0-9]+(?::(?:aud|dat|dsa|hpi|kwd|kwl|loc|pla)-[0-9]+)*$/.test(candidate)
321      ? candidate
322      : '';
323  }
324
325  function safeEnum(value, allowed) {
326    var candidate = sanitizeValue(value).toLowerCase();
327    return allowed.indexOf(candidate) !== -1 ? candidate : '';
328  }
329
330  function safeEntityValue(key, value) {
331    if (ENTITY_ID_KEYS.indexOf(key) !== -1) return safeEntityId(value);
332    if (key === 'target_id') return safeTargetId(value);
333    if (key === 'match_type') return safeEnum(value, ['a', 'b', 'c', 'e', 'p']);
334    if (key === 'network') return safeEnum(value, ['a', 'd', 'e', 'g', 'gtv', 'o', 's', 'vp', 'x', 'ytv']);
335    if (key === 'device') return safeEnum(value, ['c', 'm', 't']);
336    return '';
337  }
338
339  function readAcquisition(search, rawAdIdentifiersEnabled) {
340    var params = new URLSearchParams(search || '');
341    var values = {};
342    CAMPAIGN_KEYS.forEach(function (key) {
343      var value = safeCampaignValue(params.get(key) || '');
344      if (value) values[key] = value;
345    });
346    if (rawAdIdentifiersEnabled === true) {
347      CLICK_KEYS.forEach(function (key) {
348        var value = safeClickId(params.get(key) || '');
349        if (value) values[key] = value;
350      });
351      ENTITY_KEYS.forEach(function (key) {
352        var value = safeEntityValue(key, params.get(key) || '');
353        if (value) values[key] = value;
354      });
355    }
356    return values;
357  }
358
359  function safeLandingPath(pathname) {
360    if (typeof pathname !== 'string') return '';
361    var path = sanitizeValue(pathname.split(/[?#]/)[0]).slice(0, 300);
362    return path.charAt(0) === '/' && path.slice(0, 2) !== '//' ? path : '';
363  }
364
365  function safeReferrerOrigin(referrer, marketingHostname) {
366    if (!referrer) return '';
367    try {
368      var url = new URL(referrer);
369      if (url.protocol !== 'https:' && url.protocol !== 'http:') return '';
370      var referrerHost = url.hostname.toLowerCase().replace(/^www\./, '');
371      var siteHost = String(marketingHostname || '').toLowerCase().replace(/^www\./, '');
372      if (siteHost && (referrerHost === siteHost || referrerHost.slice(-(siteHost.length + 1)) === '.' + siteHost)) {
373        return '';
374      }
375      return sanitizeValue(url.origin).slice(0, 300);
376    } catch (_) {
377      return '';
378    }
379  }
380
381  function readTouchContext(pathname, referrer, marketingHostname) {
382    var values = {};
383    var landingPath = safeLandingPath(pathname);
384    var referrerOrigin = safeReferrerOrigin(referrer, marketingHostname);
385    if (landingPath) values.gt_landing_path = landingPath;
386    if (referrerOrigin) values.gt_referrer = referrerOrigin;
387    return values;
388  }
389
390  function cleanAttributionValue(key, value) {
391    if (key === 'gt_landing_path') return safeLandingPath(value);
392    if (key === 'gt_referrer') return safeReferrerOrigin(value, '');
393    if (CLICK_KEYS.indexOf(key) !== -1) return safeClickId(value);
394    if (ENTITY_KEYS.indexOf(key) !== -1) return safeEntityValue(key, value);
395    if (CAMPAIGN_KEYS.indexOf(key) !== -1) return safeCampaignValue(value);
396    return '';
397  }
398
399  function hasValues(values) {
400    return !!values && Object.keys(values).length > 0;
401  }
402
403  function hasPaidSignal(values) {
404    if (!values) return false;
405    if (CLICK_KEYS.some(function (key) { return !!safeClickId(values[key] || ''); })) {
406      return true;
407    }
408    var medium = safeCampaignValue(values.utm_medium || '').toLowerCase();
409    return PAID_MEDIA.indexOf(medium) !== -1;
410  }
411
412  function readStoredRecord(storage, storageKey, now, ttlMs, rawAdIdentifiersEnabled) {
413    if (!storage) return null;
414    try {
415      var parsed = JSON.parse(storage.getItem(storageKey) || 'null');
416      if (!parsed || typeof parsed.capturedAt !== 'number' || !parsed.values) return null;
417      var ageMs = now - parsed.capturedAt;
418      if (ageMs >= ttlMs || parsed.capturedAt > now + 5 * 60 * 1000) {
419        storage.removeItem(storageKey);
420        return null;
421      }
422      var clean = {};
423      var allowedKeys = CAMPAIGN_KEYS.concat(CONTEXT_KEYS);
424      var rawIdentifiersCurrent = rawAdIdentifiersEnabled === true
425        && ageMs < Math.min(ttlMs, RAW_AD_IDENTIFIER_TTL_MS);
426      if (rawIdentifiersCurrent) {
427        allowedKeys = allowedKeys.concat(RAW_AD_IDENTIFIER_KEYS);
428      }
429      allowedKeys.forEach(function (key) {
430        var value = cleanAttributionValue(key, parsed.values[key] || '');
431        if (value) clean[key] = value;
432      });
433      if (!hasValues(clean)) {
434        storage.removeItem(storageKey);
435        return null;
436      }
437      if (
438        !rawIdentifiersCurrent &&
439        RAW_AD_IDENTIFIER_KEYS.some(function (key) {
440          return Object.prototype.hasOwnProperty.call(parsed.values, key);
441        })
442      ) {
443        writeStoredRecord(storage, storageKey, parsed.capturedAt, clean);
444      }
445      return { capturedAt: parsed.capturedAt, values: clean };
446    } catch (_) {
447      return null;
448    }
449  }
450
451  function readStored(storage, storageKey, now, ttlMs, rawAdIdentifiersEnabled) {
452    var record = readStoredRecord(
453      storage,
454      storageKey,
455      now,
456      ttlMs,
457      rawAdIdentifiersEnabled,
458    );
459    return record ? record.values : {};
460  }
461
462  function ageFilterRecord(record, now, ttlMs, rawAdIdentifiersEnabled) {
463    if (!record || typeof record.capturedAt !== 'number' || !record.values) return null;
464    var ageMs = now - record.capturedAt;
465    if (ageMs >= ttlMs || record.capturedAt > now + 5 * 60 * 1000) return null;
466    var clean = {};
467    var allowedKeys = CAMPAIGN_KEYS.concat(CONTEXT_KEYS);
468    if (
469      rawAdIdentifiersEnabled === true
470      && ageMs < Math.min(ttlMs, RAW_AD_IDENTIFIER_TTL_MS)
471    ) {
472      allowedKeys = allowedKeys.concat(RAW_AD_IDENTIFIER_KEYS);
473    }
474    allowedKeys.forEach(function (key) {
475      var value = cleanAttributionValue(key, record.values[key] || '');
476      if (value) clean[key] = value;
477    });
478    return hasValues(clean)
479      ? { capturedAt: record.capturedAt, values: clean }
480      : null;
481  }
482
483  function writeStoredRecord(storage, storageKey, capturedAt, values) {
484    if (!storage || !hasValues(values)) return;
485    try {
486      storage.setItem(storageKey, JSON.stringify({ capturedAt: capturedAt, values: values }));
487    } catch (_) {
488      // Storage is best effort. The current navigation still carries the
489      // in-memory touch assembled below.
490    }
491  }
492
493  function buildIncomingTouch(search, context, rawAdIdentifiersEnabled) {
494    var incoming = readAcquisition(search, rawAdIdentifiersEnabled);
495    context = context || {};
496    CONTEXT_KEYS.forEach(function (key) {
497      if (context[key] && !incoming[key]) incoming[key] = cleanAttributionValue(key, context[key]);
498    });
499    return incoming;
500  }
501
502  function persistAcquisition(
503    storage,
504    storageKey,
505    search,
506    now,
507    ttlMs,
508    context,
509    rawAdIdentifiersEnabled,
510  ) {
511    // Match the backend's first-touch attribution contract. A later campaign
512    // must not silently replace the original acquisition record.
513    var firstTouch = readStored(
514      storage,
515      storageKey,
516      now,
517      ttlMs,
518      rawAdIdentifiersEnabled,
519    );
520    if (hasValues(firstTouch)) return firstTouch;
521    var incoming = buildIncomingTouch(search, context, rawAdIdentifiersEnabled);
522    if (!hasValues(incoming)) return {};
523    writeStoredRecord(storage, storageKey, now, incoming);
524    return incoming;
525  }
526
527  function persistPaidTouches(
528    storage,
529    storageKey,
530    search,
531    now,
532    ttlMs,
533    context,
534    rawAdIdentifiersEnabled,
535  ) {
536    var firstPaidKey = storageKey + '_paid_first';
537    var latestPaidKey = storageKey + '_paid_latest';
538    var incoming = buildIncomingTouch(search, context, rawAdIdentifiersEnabled);
539    var firstPaid = readStoredRecord(
540      storage,
541      firstPaidKey,
542      now,
543      ttlMs,
544      rawAdIdentifiersEnabled,
545    );
546    var latestPaid = readStoredRecord(
547      storage,
548      latestPaidKey,
549      now,
550      ttlMs,
551      rawAdIdentifiersEnabled,
552    );
553
554    if (firstPaid && !hasPaidSignal(firstPaid.values)) {
555      try { storage.removeItem(firstPaidKey); } catch (_) {}
556      firstPaid = null;
557    }
558    if (latestPaid && !hasPaidSignal(latestPaid.values)) {
559      try { storage.removeItem(latestPaidKey); } catch (_) {}
560      latestPaid = null;
561    }
562
563    if (hasPaidSignal(incoming)) {
564      if (!firstPaid) {
565        firstPaid = { capturedAt: now, values: incoming };
566        writeStoredRecord(storage, firstPaidKey, now, incoming);
567      }
568      // Preserve the most recent provider click independently from immutable
569      // first touch so conversion uploads can match the converting visit.
570      latestPaid = { capturedAt: now, values: incoming };
571      writeStoredRecord(storage, latestPaidKey, now, incoming);
572    }
573
574    return { firstPaid: firstPaid, latestPaid: latestPaid };
575  }
576
577  function applyDefaults(url) {
578    var defaults = {
579      utm_source: 'mentalnote.ai',
580      utm_medium: 'cta',
581      utm_campaign: 'webapp',
582    };
583    Object.keys(defaults).forEach(function (key) {
584      if (!url.searchParams.has(key)) url.searchParams.set(key, defaults[key]);
585    });
586  }
587
588  function isMentalNoteAddinUrl(url) {
589    if (!url) return false;
590    var host = String(url.hostname || '').toLowerCase();
591    return (host === 'appsource.microsoft.com' || host === 'marketplace.microsoft.com')
592      && /\/WA200006713(?:$|[/?])/i.test(url.pathname + url.search);
593  }
594
595  function addinDestination(source) {
596    if (source.hostname === 'mentalnote.ai' && /^\/go\/mentalnote\/?$/.test(source.pathname)) {
597      return new URL('https://marketplace.microsoft.com/en-us/product/office/WA200006713');
598    }
599    return isMentalNoteAddinUrl(source) ? new URL(source.toString()) : null;
600  }
601
602  function applyAddinDefaults(url) {
603    var defaults = {
604      utm_source: 'mentalnote.ai',
605      utm_medium: 'cta',
606      utm_campaign: 'install',
607    };
608    Object.keys(defaults).forEach(function (key) {
609      if (!url.searchParams.has(key)) url.searchParams.set(key, defaults[key]);
610    });
611  }
612
613  function copyAuthoredAttribution(source, destination, rawAdIdentifiersEnabled) {
614    CAMPAIGN_KEYS.forEach(function (key) {
615      var value = safeCampaignValue(source.searchParams.get(key) || '');
616      if (value) destination.searchParams.set(key, value);
617    });
618    if (rawAdIdentifiersEnabled === true) {
619      CLICK_KEYS.forEach(function (key) {
620        var value = safeClickId(source.searchParams.get(key) || '');
621        if (value) destination.searchParams.set(key, value);
622      });
623      ENTITY_KEYS.forEach(function (key) {
624        var value = safeEntityValue(key, source.searchParams.get(key) || '');
625        if (value) destination.searchParams.set(key, value);
626      });
627    }
628  }
629
630  function normalizeDestinationAttribution(url, rawAdIdentifiersEnabled) {
631    TOUCH_BRIDGE_KEYS.forEach(function (key) {
632      url.searchParams.delete(key);
633    });
634    CAMPAIGN_KEYS.forEach(function (key) {
635      if (!url.searchParams.has(key)) return;
636      var value = safeCampaignValue(url.searchParams.get(key) || '');
637      if (value) url.searchParams.set(key, value);
638      else url.searchParams.delete(key);
639    });
640    RAW_AD_IDENTIFIER_KEYS.forEach(function (key) {
641      if (!url.searchParams.has(key)) return;
642      if (rawAdIdentifiersEnabled !== true) {
643        url.searchParams.delete(key);
644        return;
645      }
646      var value = CLICK_KEYS.indexOf(key) !== -1
647        ? safeClickId(url.searchParams.get(key) || '')
648        : safeEntityValue(key, url.searchParams.get(key) || '');
649      if (value) url.searchParams.set(key, value);
650      else url.searchParams.delete(key);
651    });
652    FORBIDDEN_SEARCH_KEYS.forEach(function (key) {
653      url.searchParams.delete(key);
654    });
655  }
656
657  function applyBridge(url, acquisition, rawAdIdentifiersEnabled) {
658    var bridgeKeys = CONTEXT_KEYS.slice();
659    if (rawAdIdentifiersEnabled === true) {
660      bridgeKeys = bridgeKeys.concat(RAW_AD_IDENTIFIER_KEYS);
661    }
662    bridgeKeys.forEach(function (key) {
663      var value = cleanAttributionValue(key, acquisition[key] || '');
664      if (value && !url.searchParams.has(key)) url.searchParams.set(key, value);
665    });
666  }
667
668  function applyCampaignBridge(url, acquisition) {
669    if (!hasPaidSignal(acquisition)) return;
670    var source = safeCampaignValue(acquisition.utm_source || '');
671    var medium = safeCampaignValue(acquisition.utm_medium || '');
672    // A click id without reviewed provider channel labels must not create a
673    // hybrid source/CTA campaign. The click id itself remains available.
674    if (!source || !medium) return;
675    CAMPAIGN_KEYS.forEach(function (key) {
676      var value = safeCampaignValue(acquisition[key] || '');
677      if (value) url.searchParams.set(key, value);
678    });
679  }
680
681  function applyCtaContext(url) {
682    Object.keys(CTA_PARAM_MAP).forEach(function (key) {
683      var value = safeCampaignValue(url.searchParams.get(key) || '');
684      if (value) url.searchParams.set(CTA_PARAM_MAP[key], value);
685    });
686  }
687
688  function applyTouchBridge(url, touches) {
689    touches = touches || {};
690    var first = touches.first && touches.first.values;
691    var firstPaid = touches.firstPaid && touches.firstPaid.values;
692    if (first) {
693      [
694        ['utm_source', 'first_utm_source'],
695        ['utm_medium', 'first_utm_medium'],
696        ['utm_campaign', 'first_utm_campaign'],
697        ['gt_landing_path', 'first_landing_path'],
698        ['gt_referrer', 'first_referrer'],
699      ].forEach(function (pair) {
700        var value = cleanAttributionValue(pair[0], first[pair[0]] || '');
701        if (value) url.searchParams.set(pair[1], value);
702      });
703      if (Number.isSafeInteger(touches.first.capturedAt)) {
704        url.searchParams.set('first_captured_at', String(touches.first.capturedAt));
705      }
706    }
707    if (firstPaid) {
708      [
709        ['utm_source', 'paid_first_utm_source'],
710        ['utm_medium', 'paid_first_utm_medium'],
711        ['utm_campaign', 'paid_first_utm_campaign'],
712      ].forEach(function (pair) {
713        var value = safeCampaignValue(firstPaid[pair[0]] || '');
714        if (value) url.searchParams.set(pair[1], value);
715      });
716      if (Number.isSafeInteger(touches.firstPaid.capturedAt)) {
717        url.searchParams.set('paid_first_captured_at', String(touches.firstPaid.capturedAt));
718      }
719    }
720    if (touches.latestPaid && Number.isSafeInteger(touches.latestPaid.capturedAt)) {
721      url.searchParams.set('paid_captured_at', String(touches.latestPaid.capturedAt));
722    }
723  }
724
725  function decorateHref(
726    href,
727    baseHref,
728    acquisition,
729    touches,
730    rawAdIdentifiersEnabled,
731  ) {
732    var source;
733    try {
734      source = new URL(href, baseHref);
735    } catch (_) {
736      return href;
737    }
738    var isRoute = source.hostname === 'mentalnote.ai' && /^\/go\/webapp\/?$/.test(source.pathname);
739    var isApp = source.hostname === 'app.mentalnote.ai';
740    var addin = addinDestination(source);
741    if (!isRoute && !isApp && !addin) return href;
742
743    var destination = addin || (isRoute ? new URL('https://app.mentalnote.ai/') : new URL(source.toString()));
744    if (isRoute) {
745      copyAuthoredAttribution(source, destination, rawAdIdentifiersEnabled);
746    }
747    normalizeDestinationAttribution(destination, rawAdIdentifiersEnabled);
748    if (addin) applyAddinDefaults(destination);
749    else {
750      applyDefaults(destination);
751      applyCtaContext(destination);
752    }
753    // Provider acquisition and CTA placement are separate dimensions. Paid
754    // UTMs remain intact for attribution, while authored CTA semantics travel
755    // in cta_* fields. Search text is never stored or bridged.
756    applyCampaignBridge(destination, acquisition || {});
757    applyBridge(destination, acquisition || {}, rawAdIdentifiersEnabled);
758    applyTouchBridge(destination, touches);
759    return destination.toString();
760  }
761
762  function stripAttributionHref(href, baseHref) {
763    var source;
764    try {
765      source = new URL(href, baseHref);
766    } catch (_) {
767      return href;
768    }
769    var isRoute = source.hostname === 'mentalnote.ai'
770      && /^\/go\/(?:webapp|mentalnote|gentext|lexdraft)\/?$/.test(source.pathname);
771    var isTarget = source.hostname === 'app.mentalnote.ai' || isMentalNoteAddinUrl(source);
772    if (!isRoute && !isTarget) return href;
773
774    CAMPAIGN_KEYS
775      .concat(RAW_AD_IDENTIFIER_KEYS, CONTEXT_KEYS, TOUCH_BRIDGE_KEYS, FORBIDDEN_SEARCH_KEYS)
776      .concat(Object.keys(CTA_PARAM_MAP).map(function (key) { return CTA_PARAM_MAP[key]; }))
777      .concat(ADDIN_ATTRIBUTION_KEYS)
778      .forEach(function (key) { source.searchParams.delete(key); });
779
780    if (source.hostname === 'mentalnote.ai') {
781      return source.pathname + source.search + source.hash;
782    }
783    return source.toString();
784  }
785
786  function authoredAttributionHref(anchor, href, baseHref) {
787    if (!anchor || typeof anchor.getAttribute !== 'function') return href;
788    var source;
789    try {
790      source = new URL(href, baseHref);
791    } catch (_) {
792      return href;
793    }
794    [
795      ['utm_source', 'data-attribution-source'],
796      ['utm_medium', 'data-attribution-medium'],
797      ['utm_campaign', 'data-attribution-campaign'],
798      ['utm_content', 'data-attribution-content'],
799    ].forEach(function (pair) {
800      var value = safeCampaignValue(anchor.getAttribute(pair[1]) || '');
801      if (value) source.searchParams.set(pair[0], value);
802    });
803    return source.toString();
804  }
805
806  function safeTrackedHref(href, baseHref) {
807    try {
808      var url = new URL(href, baseHref);
809      return url.origin + url.pathname;
810    } catch (_) {
811      return String(href || '').split('?')[0].split('#')[0];
812    }
813  }
814
815  function safeStorage(browser, options) {
816    try {
817      if (Object.prototype.hasOwnProperty.call(options, 'storage')) return options.storage;
818      return browser.localStorage;
819    } catch (_) {
820      // Some privacy modes throw while merely reading window.localStorage.
821      // Continue with current-page attribution and do not affect navigation.
822      return null;
823    }
824  }
825
826  function purgeAcquisition(storage, storageKey) {
827    if (!storage || typeof storage.removeItem !== 'function' || typeof storage.getItem !== 'function') return false;
828    var allPurged = true;
829    [storageKey, storageKey + '_paid_first', storageKey + '_paid_latest'].forEach(function (key) {
830      try {
831        storage.removeItem(key);
832        if (storage.getItem(key) === null) return;
833        if (typeof storage.setItem !== 'function') {
834          allPurged = false;
835          return;
836        }
837        storage.setItem(key, '');
838        if (storage.getItem(key) !== '') allPurged = false;
839      } catch (_) {
840        allPurged = false;
841      }
842    });
843    return allPurged;
844  }
845
846  function clearAttributionTargets(browser) {
847    if (!browser || !browser.document || !browser.document.querySelectorAll) return;
848    var anchors = browser.document.querySelectorAll('[data-attribution-original-href]');
849    Array.prototype.forEach.call(anchors, function (anchor) {
850      var original = anchor.getAttribute('data-attribution-original-href') || '';
851      if (original) anchor.setAttribute('href', original);
852      if (typeof anchor.removeAttribute === 'function') {
853        anchor.removeAttribute('data-attribution-original-href');
854      }
855    });
856  }
857
858  function installCurrentPageHandoff(browser) {
859    if (!browser || !browser.document || !browser.location) return {};
860    // Before advertising consent, outbound product links remain functional but
861    // carry no acquisition, click, CTA, or marketplace campaign parameters.
862
863    function decorateAnchor(anchor) {
864      if (!anchor || !anchor.getAttribute) return;
865      var href = anchor.getAttribute('href') || '';
866      var originalHref = anchor.getAttribute('data-attribution-original-href') || href;
867      var decorated = stripAttributionHref(originalHref, browser.location.href);
868      if (decorated === href) return;
869      if (!anchor.hasAttribute('data-attribution-original-href')) {
870        anchor.setAttribute('data-attribution-original-href', originalHref);
871      }
872      anchor.setAttribute('href', decorated);
873    }
874
875    function decorateAll() {
876      var anchors = browser.document.querySelectorAll(
877        'a[href^="/go/webapp"], a[href^="/go/mentalnote"], '
878          + 'a[href^="/go/gentext"], a[href^="/go/lexdraft"], '
879          + 'a[href^="https://app.mentalnote.ai"], '
880          + 'a[href*="appsource.microsoft.com"], a[href*="marketplace.microsoft.com"]',
881      );
882      Array.prototype.forEach.call(anchors, decorateAnchor);
883    }
884
885    if (browser.document.readyState === 'loading') {
886      browser.document.addEventListener('DOMContentLoaded', decorateAll, { once: true });
887    } else {
888      decorateAll();
889    }
890    browser.document.addEventListener('click', function (event) {
891      var anchor = event.target && event.target.closest && event.target.closest('a[href]');
892      decorateAnchor(anchor);
893    }, true);
894    return {};
895  }
896
897  function passthroughAttributionHref(href, baseHref, pageHref, referrer, rawAdIdentifiersEnabled) {
898    var source;
899    try {
900      source = new URL(href, baseHref);
901    } catch (_) {
902      return href;
903    }
904    var isRoute = source.hostname === 'mentalnote.ai' && /^\/go\/webapp\/?$/.test(source.pathname);
905    var isApp = source.hostname === 'app.mentalnote.ai';
906    // The Word add-in marketplace is a third party and never receives
907    // pass-through attribution; only the owned web-app handoff does.
908    if (!isRoute && !isApp) return href;
909
910    var live;
911    try {
912      live = new URL(pageHref);
913    } catch (_) {
914      return href;
915    }
916    var carriesAttribution = CAMPAIGN_KEYS.concat(RAW_AD_IDENTIFIER_KEYS).some(function (key) {
917      return !!live.searchParams.get(key);
918    });
919    if (!carriesAttribution) return href;
920
921    var destination = isRoute ? new URL('https://app.mentalnote.ai/') : new URL(source.toString());
922    if (isRoute) copyAuthoredAttribution(source, destination, rawAdIdentifiersEnabled);
923    normalizeDestinationAttribution(destination, rawAdIdentifiersEnabled);
924    applyDefaults(destination);
925    applyCtaContext(destination);
926    // The live copy only sets allowlisted, individually sanitized keys, so no
927    // second normalization pass is needed (it would scrub the cta_* context).
928    copyAuthoredAttribution(live, destination, rawAdIdentifiersEnabled);
929    var landingPath = safeLandingPath(live.pathname);
930    if (landingPath && !destination.searchParams.has('gt_landing_path')) {
931      destination.searchParams.set('gt_landing_path', landingPath);
932    }
933    var referrerOrigin = safeReferrerOrigin(referrer || '', 'mentalnote.ai');
934    if (referrerOrigin && !destination.searchParams.has('gt_referrer')) {
935      destination.searchParams.set('gt_referrer', referrerOrigin);
936    }
937    return destination.toString();
938  }
939
940  function installUndecidedPassthrough(browser, options) {
941    if (!browser || !browser.document || !browser.location) return {};
942    options = options || {};
943    var rawAdIdentifiersEnabled = options.rawAdIdentifiersEnabled === true;
944    var shouldPassThrough = typeof options.shouldPassThrough === 'function'
945      ? options.shouldPassThrough
946      : function () { return true; };
947
948    function decorateAnchor(anchor) {
949      if (!anchor || !anchor.getAttribute) return;
950      if (!shouldPassThrough()) return;
951      var href = anchor.getAttribute('href') || '';
952      var originalHref = anchor.getAttribute('data-attribution-original-href') || href;
953      var decorated = passthroughAttributionHref(
954        originalHref,
955        browser.location.href,
956        browser.location.href,
957        browser.document.referrer || '',
958        rawAdIdentifiersEnabled,
959      );
960      if (decorated === href) return;
961      if (!anchor.hasAttribute('data-attribution-original-href')) {
962        anchor.setAttribute('data-attribution-original-href', originalHref);
963      }
964      anchor.setAttribute('href', decorated);
965    }
966
967    function decorateAll() {
968      var anchors = browser.document.querySelectorAll(
969        'a[href^="/go/webapp"], a[href^="https://app.mentalnote.ai"]',
970      );
971      Array.prototype.forEach.call(anchors, decorateAnchor);
972    }
973
974    if (browser.document.readyState === 'loading') {
975      browser.document.addEventListener('DOMContentLoaded', decorateAll, { once: true });
976    } else {
977      decorateAll();
978    }
979    browser.document.addEventListener('click', function (event) {
980      var anchor = event.target && event.target.closest && event.target.closest('a[href]');
981      decorateAnchor(anchor);
982    }, true);
983    return {};
984  }
985
986  function installAcquisition(browser, options) {
987    options = options || {};
988    var storageKey = options.storageKey || 'mentalnote_acquisition_v1';
989    var ttlMs = options.ttlMs || DEFAULT_TTL_MS;
990    var installedAtCandidate = typeof options.now === 'function'
991      ? options.now()
992      : (typeof options.now === 'number' ? options.now : Date.now());
993    var installedAt = typeof installedAtCandidate === 'number' && isFinite(installedAtCandidate)
994      ? installedAtCandidate
995      : Date.now();
996    var rawAdIdentifiersEnabled = options.rawAdIdentifiersEnabled === true;
997    var storage = safeStorage(browser, options);
998    var isConsentGranted = typeof options.isConsentGranted === 'function'
999      ? options.isConsentGranted
1000      : function () { return true; };
1001    var onAcquisitionChanged = typeof options.onAcquisitionChanged === 'function'
1002      ? options.onAcquisitionChanged
1003      : function () {};
1004    var installToken = {};
1005    var installSlot = '__mentalNoteAttributionInstall';
1006    browser[installSlot] = installToken;
1007    var refreshTimer = null;
1008
1009    function currentTime() {
1010      var value = typeof options.now === 'function' ? options.now() : Date.now();
1011      if (typeof options.now === 'number') value = options.now;
1012      return typeof value === 'number' && isFinite(value) ? value : Date.now();
1013    }
1014
1015    function isActive() {
1016      return browser[installSlot] === installToken;
1017    }
1018
1019    function clearRefreshTimer() {
1020      if (refreshTimer !== null && typeof browser.clearTimeout === 'function') {
1021        browser.clearTimeout(refreshTimer);
1022      }
1023      refreshTimer = null;
1024    }
1025    if (!isConsentGranted()) {
1026      purgeAcquisition(storage, storageKey);
1027      clearAttributionTargets(browser);
1028      return {};
1029    }
1030    var context = readTouchContext(
1031      browser.location.pathname,
1032      browser.document.referrer,
1033      browser.location.hostname,
1034    );
1035    var firstTouch = persistAcquisition(
1036      storage,
1037      storageKey,
1038      browser.location.search,
1039      installedAt,
1040      ttlMs,
1041      context,
1042      rawAdIdentifiersEnabled,
1043    );
1044    var paidTouches = persistPaidTouches(
1045      storage,
1046      storageKey,
1047      browser.location.search,
1048      installedAt,
1049      ttlMs,
1050      context,
1051      rawAdIdentifiersEnabled,
1052    );
1053    var firstRecord = readStoredRecord(
1054      storage,
1055      storageKey,
1056      installedAt,
1057      ttlMs,
1058      rawAdIdentifiersEnabled,
1059    )
1060      || (hasValues(firstTouch) ? { capturedAt: installedAt, values: firstTouch } : null);
1061    var initialTouches = {
1062      first: firstRecord,
1063      firstPaid: paidTouches.firstPaid,
1064      latestPaid: paidTouches.latestPaid,
1065    };
1066    var initialAcquisition = paidTouches.latestPaid ? paidTouches.latestPaid.values : firstTouch;
1067
1068    function currentRecord(key, fallback, now) {
1069      return readStoredRecord(
1070        storage,
1071        key,
1072        now,
1073        ttlMs,
1074        rawAdIdentifiersEnabled,
1075      ) || ageFilterRecord(fallback, now, ttlMs, rawAdIdentifiersEnabled);
1076    }
1077
1078    function currentState() {
1079      var now = currentTime();
1080      var touches = {
1081        first: currentRecord(storageKey, initialTouches.first, now),
1082        firstPaid: currentRecord(
1083          storageKey + '_paid_first',
1084          initialTouches.firstPaid,
1085          now,
1086        ),
1087        latestPaid: currentRecord(
1088          storageKey + '_paid_latest',
1089          initialTouches.latestPaid,
1090          now,
1091        ),
1092      };
1093      var acquisition = touches.latestPaid
1094        ? touches.latestPaid.values
1095        : (touches.first ? touches.first.values : {});
1096      try { onAcquisitionChanged(acquisition); } catch (_) {}
1097      return { now: now, touches: touches, acquisition: acquisition };
1098    }
1099
1100    function nextExpiryDelay(state) {
1101      var deadlines = [];
1102      ['first', 'firstPaid', 'latestPaid'].forEach(function (key) {
1103        var record = state.touches[key];
1104        if (!record) return;
1105        deadlines.push(record.capturedAt + ttlMs);
1106        if (
1107          rawAdIdentifiersEnabled
1108          && RAW_AD_IDENTIFIER_KEYS.some(function (rawKey) {
1109            return !!record.values[rawKey];
1110          })
1111        ) {
1112          deadlines.push(
1113            record.capturedAt + Math.min(ttlMs, RAW_AD_IDENTIFIER_TTL_MS),
1114          );
1115        }
1116      });
1117      var future = deadlines.filter(function (deadline) {
1118        return deadline > state.now;
1119      });
1120      if (!future.length) return 0;
1121      return Math.min.apply(Math, future) - state.now;
1122    }
1123
1124    function scheduleRefresh(state) {
1125      clearRefreshTimer();
1126      if (!isActive() || !isConsentGranted()) return;
1127      var remainingMs = nextExpiryDelay(state || currentState());
1128      if (remainingMs <= 0 || typeof browser.setTimeout !== 'function') return;
1129      refreshTimer = browser.setTimeout(function () {
1130        refreshTimer = null;
1131        refreshAll();
1132      }, Math.min(remainingMs, MAX_TIMER_DELAY_MS));
1133    }
1134
1135    function decorateAnchor(anchor) {
1136      if (!isActive() || !isConsentGranted()) return;
1137      if (!anchor || !anchor.getAttribute) return;
1138      var state = currentState();
1139      var href = anchor.getAttribute('href') || '';
1140      var originalHref = anchor.getAttribute('data-attribution-original-href') || href;
1141      var decorated = decorateHref(
1142        authoredAttributionHref(anchor, originalHref, browser.location.href),
1143        browser.location.href,
1144        state.acquisition,
1145        state.touches,
1146        rawAdIdentifiersEnabled,
1147      );
1148      if (decorated === href) return;
1149      if (!anchor.hasAttribute('data-attribution-original-href')) {
1150        anchor.setAttribute('data-attribution-original-href', originalHref);
1151      }
1152      anchor.setAttribute('href', decorated);
1153    }
1154
1155    function decorateAll() {
1156      if (!isActive() || !isConsentGranted()) {
1157        clearRefreshTimer();
1158        clearAttributionTargets(browser);
1159        return;
1160      }
1161      var anchors = browser.document.querySelectorAll(
1162        'a[href^="/go/webapp"], a[href^="/go/mentalnote"], '
1163          + 'a[href^="/go/gentext"], a[href^="/go/lexdraft"], '
1164          + 'a[href^="https://app.mentalnote.ai"], '
1165          + 'a[href*="appsource.microsoft.com"], a[href*="marketplace.microsoft.com"]',
1166      );
1167      Array.prototype.forEach.call(anchors, decorateAnchor);
1168    }
1169
1170    function refreshAll() {
1171      decorateAll();
1172      scheduleRefresh(currentState());
1173    }
1174
1175    if (browser.document.readyState === 'loading') {
1176      browser.document.addEventListener('DOMContentLoaded', refreshAll, { once: true });
1177    } else {
1178      refreshAll();
1179    }
1180    browser.document.addEventListener('click', function (event) {
1181      if (!isActive() || !isConsentGranted()) return;
1182      var anchor = event.target && event.target.closest && event.target.closest('a[href]');
1183      decorateAnchor(anchor);
1184    }, true);
1185    if (typeof browser.addEventListener === 'function') {
1186      browser.addEventListener('focus', refreshAll);
1187    }
1188    if (typeof browser.document.addEventListener === 'function') {
1189      browser.document.addEventListener('visibilitychange', function () {
1190        if (browser.document.hidden !== true) refreshAll();
1191      });
1192    }
1193    return initialAcquisition;
1194  }
1195
1196  return {
1197    CAMPAIGN_KEYS: CAMPAIGN_KEYS,
1198    CLICK_KEYS: CLICK_KEYS,
1199    ENTITY_KEYS: ENTITY_KEYS,
1200    RAW_AD_IDENTIFIER_KEYS: RAW_AD_IDENTIFIER_KEYS,
1201    ACQUISITION_KEYS: ACQUISITION_KEYS,
1202    CONTEXT_KEYS: CONTEXT_KEYS,
1203    ATTRIBUTION_KEYS: ATTRIBUTION_KEYS,
1204    BRIDGE_KEYS: BRIDGE_KEYS,
1205    PAID_MEDIA: PAID_MEDIA,
1206    CTA_PARAM_MAP: CTA_PARAM_MAP,
1207    TOUCH_BRIDGE_KEYS: TOUCH_BRIDGE_KEYS,
1208    DEFAULT_TTL_MS: DEFAULT_TTL_MS,
1209    RAW_AD_IDENTIFIER_TTL_MS: RAW_AD_IDENTIFIER_TTL_MS,
1210    MAX_TIMER_DELAY_MS: MAX_TIMER_DELAY_MS,
1211    DEFAULT_RAW_AD_IDENTIFIERS_ENABLED: DEFAULT_RAW_AD_IDENTIFIERS_ENABLED,
1212    sanitizeValue: sanitizeValue,
1213    safeCampaignValue: safeCampaignValue,
1214    safeClickId: safeClickId,
1215    safeEntityId: safeEntityId,
1216    safeTargetId: safeTargetId,
1217    readAcquisition: readAcquisition,
1218    safeLandingPath: safeLandingPath,
1219    safeReferrerOrigin: safeReferrerOrigin,
1220    readTouchContext: readTouchContext,
1221    hasPaidSignal: hasPaidSignal,
1222    readStoredRecord: readStoredRecord,
1223    readStored: readStored,
1224    ageFilterRecord: ageFilterRecord,
1225    persistAcquisition: persistAcquisition,
1226    persistPaidTouches: persistPaidTouches,
1227    decorateHref: decorateHref,
1228    stripAttributionHref: stripAttributionHref,
1229    authoredAttributionHref: authoredAttributionHref,
1230    safeTrackedHref: safeTrackedHref,
1231    safeStorage: safeStorage,
1232    purgeAcquisition: purgeAcquisition,
1233    clearAttributionTargets: clearAttributionTargets,
1234    installCurrentPageHandoff: installCurrentPageHandoff,
1235    passthroughAttributionHref: passthroughAttributionHref,
1236    installUndecidedPassthrough: installUndecidedPassthrough,
1237    installAcquisition: installAcquisition,
1238  };
1239});

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.