PageSourceSearch

https://pristinesoftwash.net/api/lamp.js

js pristinesoftwash.net collected 2026-10-07 05:08:20 UTC 7,297 bytes, 186 lines download raw bytes

1(function () {
2  var d = document, w = window, s = d.currentScript;
3  if (!s || w.__lamp) return;
4  w.__lamp = 1;
5
6  var site = s.getAttribute("data-site");
7  var ep = s.getAttribute("data-endpoint") || "/api/e";
8  if (!site) return;
9  if (navigator.webdriver) return;
10  if (location.hostname === "localhost" || location.hostname === "127.0.0.1") return;
11
12  var maxScroll = 0, flushed = false;
13
14  function send(body, beacon) {
15    body.s = site;
16    body.p = location.pathname + location.search;
17    var json = JSON.stringify(body);
18    // text/plain, never application/json: a site with no /api proxy (Shopify)
19    // posts to the Worker cross-origin, and a JSON content type makes the
20    // browser preflight it and drop the pageview. collect() parses either.
21    try {
22      if (beacon && navigator.sendBeacon) {
23        navigator.sendBeacon(ep, new Blob([json], { type: "text/plain" }));
24      } else {
25        fetch(ep, {
26          method: "POST",
27          body: json,
28          keepalive: true,
29          headers: { "Content-Type": "text/plain" }
30        })["catch"](function () {});
31      }
32    } catch (e) {}
33  }
34
35  function pageview() {
36    maxScroll = 0;
37    flushed = false;
38    send({ t: "pageview", r: d.referrer || "" });
39  }
40
41  function track(name, meta) {
42    send({ t: "event", n: name, m: meta || null });
43  }
44
45  w.lamp = track;
46
47  addEventListener("scroll", function () {
48    var h = d.documentElement.scrollHeight - innerHeight;
49    var pct = h > 0 ? Math.min(100, Math.round((scrollY / h) * 100)) : 100;
50    if (pct > maxScroll) maxScroll = pct;
51  }, { passive: true });
52
53  function flush() {
54    if (flushed || maxScroll <= 0) return;
55    flushed = true;
56    send({ t: "scroll", v: maxScroll }, true);
57  }
58
59  addEventListener("pagehide", flush);
60  d.addEventListener("visibilitychange", function () {
61    if (d.visibilityState === "hidden") flush();
62  });
63
64  d.addEventListener("click", function (e) {
65    var t = e.target;
66    if (!t || !t.closest) return;
67    var el = t.closest("a, button, [data-track]");
68    if (!el) return;
69
70    var named = el.getAttribute("data-track");
71    if (named) { track(named); return; }
72
73    if (el.tagName !== "A") return;
74    var href = el.getAttribute("href") || "";
75
76    if (/^tel:/i.test(href)) track("Phone number clicked", href.slice(4));
77    else if (/^mailto:/i.test(href)) track("Email address clicked");
78    else if (/^https?:/i.test(href) && el.hostname && el.hostname !== location.hostname) {
79      track("Outbound link", el.hostname);
80    }
81  }, true);
82
83  d.addEventListener("submit", function (e) {
84    var f = e.target;
85    if (!f || f.tagName !== "FORM") return;
86    track(f.getAttribute("data-track") || "Form submitted",
87          f.getAttribute("name") || f.getAttribute("id") || null);
88  }, true);
89
90  // Third-party booking and form widgets sit in cross-origin iframes, so a
91  // completed booking is invisible from out here — except that several providers
92  // announce one by postMessage. Listen for those, keyed on the sending origin.
93  //
94  // Deliberately always on rather than a per-site flag: a site with no Calendly
95  // never receives a Calendly message, so this costs nothing on the other 66 and
96  // needs no snippet change to switch on. The origin is the whole security model,
97  // so it is matched exactly and never by substring — "calendly.com.evil.net"
98  // must not pass. Only known event names are honoured, and only the provider
99  // name and its own record id ever ride along in meta: no form contents.
100  var BRIDGES = {
101    "https://calendly.com": function (m) {
102      // widget.js emits calendly.* through the booking flow; only the last one
103      // means an appointment actually exists.
104      if (m && m.event === "calendly.event_scheduled") return ["Booking scheduled", "Calendly"];
105      return null;
106    },
107    "https://go.movegistics.com": function (m) {
108      if (m && m.event === "movegistics_generate_lead") {
109        var id = m.lead_data && m.lead_data.id;
110        return ["Quote request sent", id ? "Movegistics · " + id : "Movegistics"];
111      }
112      return null;
113    },
114    // Off The Couch (clueclock's booking) emits a GA4-shaped ecommerce funnel on
115    // e.data.eventType: page_view, add_to_cart, begin_checkout, purchase, success.
116    //
117    // Only "purchase" is taken. It is the standard name for a completed
118    // transaction, and "success" appears to be its twin — recording both would
119    // count one booking as two leads, which is the one error worth avoiding in a
120    // number a client is shown. If real bookings turn out to announce themselves
121    // only as "success", swap the name here rather than accepting both.
122    "https://offthecouch.io": function (m) {
123      if (m && m.eventType === "purchase") {
124        var v = m.eventValue;
125        return ["Booking paid", "Off The Couch" +
126          (v ? " · " + (m.currency || "") + " " + v : "")];
127      }
128      return null;
129    },
130    // GoHighLevel / LeadConnector form embeds. Unlike the others this arrives as
131    // an ARRAY, not an object:
132    //
133    //   ["set-sticky-contacts", "_ud", "<the whole contact as JSON>",
134    //    "<location id>", "<form id>"]
135    //
136    // A companion message follows immediately with "embedded_iframe_<id>" in
137    // slot 1 instead of "_ud". Matching on slot 0 alone would record one
138    // submission as two, so the "_ud" check is load-bearing rather than cosmetic.
139    //
140    // **Slot 2 is the entire submitted record** — name, email, phone, address and
141    // every answer on the form. None of it is read. The rule against putting form
142    // contents in meta exists for exactly this shape, and this is the strongest
143    // case for it on the fleet: it is the same material we refuse to copy out of
144    // Netlify, offered for free by a widget.
145    //
146    // The name is alarming and was checked rather than assumed:
147    // "set-sticky-contacts" is GoHighLevel's form pre-fill feature, so if it also
148    // fired on page *load* for a contact the platform recognises, every returning
149    // visitor opening the page would record an enquiry they never sent.
150    //
151    // **Measured on allthingscakeco 2026-08-02**, against the live widget: two
152    // submissions each emitted it, and a reload of the same page in the same
153    // browser — by then a known contact, which is the case that would trigger a
154    // pre-fill — emitted nothing at all. It fires on submit only.
155    //
156    // Worth re-checking if GoHighLevel ever changes the embed, because the
157    // failure would be silent and upward: a lead figure that climbs on page views
158    // looks like success.
159    "https://api.leadconnectorhq.com": function (m) {
160      if (m && m.length === 5 && m[0] === "set-sticky-contacts" && m[1] === "_ud") {
161        return ["Quote request sent", "GoHighLevel"];
162      }
163      return null;
164    }
165  };
166
167  addEventListener("message", function (e) {
168    var read = BRIDGES[e.origin];
169    if (!read) return;
170    var hit;
171    try { hit = read(e.data); } catch (err) { return; }
172    if (hit) track(hit[0], hit[1]);
173  });
174
175  var push = history.pushState;
176  if (push) {
177    history.pushState = function () {
178      push.apply(this, arguments);
179      flush();
180      pageview();
181    };
182    addEventListener("popstate", function () { flush(); pageview(); });
183  }
184
185  pageview();
186})();

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.