PageSourceSearch

https://forms.techtoschool.com/js/tts-web-context.js

js techtoschool.com collected 2026-10-10 00:42:13 UTC 6,091 bytes, 150 lines download raw bytes

1/* tts-web-context.js — first-party web attribution capture (Phase 2).
2 *
3 * Runs on any *.techtoschool.com page (storefront theme, forms.techtoschool.com
4 * form pages, mkl-hosted embeds). Collects, without any external calls:
5 *   client_id — GA4 client id parsed from the first-party `_ga` cookie
6 *               (domain .techtoschool.com, so it crosses storefront ↔ forms).
7 *   first     — FIRST-touch snapshot, stamped once into the `_tts_ft` cookie
8 *               (.techtoschool.com, ~400 days): source/medium/campaign/gclid/
9 *               referrer/landing/ts of the visitor's first-ever arrival.
10 *   last      — the current (converting) context: utm params + gclid from this
11 *               page's URL (or, inside an iframe, the parent page's URL via
12 *               document.referrer), external referrer, landing page.
13 *
14 * Usage: window.ttsWebContext() → object|null. Include the file and pass the
15 * result as `web_context` with any form/quote/checkout POST. Self-executing:
16 * stamps the first-touch cookie on load so mere browsing (no conversion)
17 * still anchors first-touch for a later visit. No PII is read or stored.
18 * Server side sanitizes via core/lib/attribution/live.js captureWebContext —
19 * this file is capture, not truth.
20 */
21(function () {
22  if (window.ttsWebContext) return;
23
24  var COOKIE_DOMAIN = '.techtoschool.com';
25  var FT_COOKIE = '_tts_ft';
26  var FT_MAX_AGE = 60 * 60 * 24 * 400; // ~400 days
27
28  function getCookie(name) {
29    try {
30      var m = document.cookie.match(new RegExp('(?:^|; )' + name + '=([^;]*)'));
31      return m ? decodeURIComponent(m[1]) : null;
32    } catch (_) { return null; }
33  }
34
35  function setCookie(name, value) {
36    try {
37      var v = encodeURIComponent(value);
38      var base = name + '=' + v + '; path=/; max-age=' + FT_MAX_AGE + '; SameSite=Lax';
39      // Only stamp the shared domain when we're actually on it (localhost/dev-tunnel safe).
40      if (location.hostname.indexOf('techtoschool.com') !== -1) base += '; domain=' + COOKIE_DOMAIN;
41      if (location.protocol === 'https:') base += '; Secure';
42      document.cookie = base;
43    } catch (_) { /* cookie-blocked browsers: capture still works per-page */ }
44  }
45
46  // GA4 client id from the _ga cookie: "GA1.1.123456789.1712345678" → "123456789.1712345678"
47  function gaClientId() {
48    var raw = getCookie('_ga');
49    if (!raw) return null;
50    var parts = raw.split('.');
51    if (parts.length < 4) return null;
52    return parts[parts.length - 2] + '.' + parts[parts.length - 1];
53  }
54
55  function parseParams(url) {
56    var out = {};
57    try {
58      var q = new URL(url).searchParams;
59      ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'gclid'].forEach(function (k) {
60        var v = q.get(k);
61        if (v) out[k.replace('utm_', '')] = v;
62      });
63      if (out.gclid == null && q.get('gclid')) out.gclid = q.get('gclid');
64    } catch (_) {}
65    return out;
66  }
67
68  function isInternal(url) {
69    try { return new URL(url).hostname.indexOf('techtoschool.com') !== -1; } catch (_) { return false; }
70  }
71
72  // The "current arrival" context, seen from wherever this script runs.
73  // Inside a TTS iframe, document.referrer is the EMBEDDING storefront page
74  // (with its query string) — that page's utm params + URL are the real context.
75  function currentVisit() {
76    var inIframe = false;
77    try { inIframe = window.parent && window.parent !== window; } catch (_) { inIframe = true; }
78    var parentUrl = document.referrer || '';
79    var useParent = inIframe && parentUrl && isInternal(parentUrl);
80
81    var params = parseParams(location.href);
82    var landing = location.href.split('#')[0];
83    var referrer = document.referrer || '';
84    if (useParent) {
85      var pp = parseParams(parentUrl);
86      // Parent params win when present (the iframe URL rarely carries utm).
87      Object.keys(pp).forEach(function (k) { if (!params[k]) params[k] = pp[k]; });
88      landing = parentUrl.split('#')[0];
89      referrer = ''; // parent's own referrer is unknowable from here
90    } else if (isInternal(referrer)) {
91      referrer = ''; // internal navigation is not a source
92    }
93
94    var v = {};
95    if (params.source) v.source = params.source;
96    if (params.medium) v.medium = params.medium;
97    if (params.campaign) v.campaign = params.campaign;
98    if (params.term) v.term = params.term;
99    if (params.content) v.content = params.content;
100    if (params.gclid) v.gclid = params.gclid;
101    if (referrer) v.referrer = referrer.slice(0, 500);
102    if (landing) v.landing = landing.slice(0, 500);
103    v.ts = Date.now();
104    return v;
105  }
106
107  function readFirstTouch() {
108    var raw = getCookie(FT_COOKIE);
109    if (!raw) return null;
110    try {
111      var o = JSON.parse(raw);
112      return (o && typeof o === 'object') ? o : null;
113    } catch (_) { return null; }
114  }
115
116  // Stamp first-touch ONCE. A meaningful first touch = has a source signal
117  // (utm/gclid/external referrer); a bare direct visit still stamps landing+ts
118  // so "first seen" is anchored, but a later visit that DOES carry a source may
119  // upgrade a source-less stamp within 24h (the ad-click-after-direct case).
120  function stampFirstTouch(visit) {
121    var existing = readFirstTouch();
122    var hasSource = !!(visit.source || visit.gclid || visit.referrer);
123    if (existing) {
124      var existingHasSource = !!(existing.source || existing.gclid || existing.referrer);
125      var fresh = existing.ts && (Date.now() - existing.ts) < 86400000;
126      if (!existingHasSource && hasSource && fresh) {
127        setCookie(FT_COOKIE, JSON.stringify(visit));
128        return visit;
129      }
130      return existing;
131    }
132    setCookie(FT_COOKIE, JSON.stringify(visit));
133    return visit;
134  }
135
136  var _visit = currentVisit();
137  var _first = stampFirstTouch(_visit);
138
139  window.ttsWebContext = function () {
140    try {
141      var out = {};
142      var cid = gaClientId();
143      if (cid) out.client_id = cid;
144      var first = readFirstTouch() || _first;
145      if (first) out.first = first;
146      out.last = _visit;
147      return out;
148    } catch (_) { return null; }
149  };
150})();

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.