Diff -- /var/www/vhosts/3dshawn.com/crm.3dshawn.com/t.js
Diff

/var/www/vhosts/3dshawn.com/crm.3dshawn.com/t.js

added on local at 2026-07-01 15:03:05

Added
+274
lines
Removed
-0
lines
Context
0
unchanged
Blobs
from
to 28ce9c172419
Restore this content →
Unified diff
Naive LCS-based line diff. Additions in emerald, deletions in rose. Both sides decompressed live from BLOB_STORE.
1/* =====================================================================
2 ContactForge tracker -- embedded on crm.3dshawn.com marketing pages:
3 <script async src="/t.js"></script>
4
5 Single-tenant: every event posts to this CRM's own ingest endpoints.
6 No public key, no site filter -- the back end stores everything as
7 "this site's" traffic.
8
9 What it does:
10 1. Reads/creates cf_track (long-lived visitor cookie) and cf_sess
11 (session cookie). UUID v4.
12 2. Sends a pageview to /c.cgi on load (sendBeacon when possible).
13 3. Tracks clicks (heatmap), max scroll depth, and form submits.
14 4. Sends a heartbeat every 15s with active time on page + scroll
15 depth so we can compute time-on-page accurately.
16 5. Exposes a tiny global API: window.cftrack.event(name, value)
17 and window.cftrack.goal(name, value) for fire-and-forget events.
18===================================================================== */
19(function () {
20 'use strict';
21
22 if (window.__cftrack_loaded) return;
23 window.__cftrack_loaded = true;
24
25 /* ---- Endpoints (relative; same-origin) -------------------------- */
26 var COLLECT = '/c.cgi';
27 var HEAT = '/h.cgi';
28 var GOAL = '/g.cgi';
29
30 /* ---- Cookie helpers --------------------------------------------- */
31 function getCookie(name) {
32 var m = document.cookie.match(new RegExp('(?:^|; )' + name + '=([^;]*)'));
33 return m ? decodeURIComponent(m[1]) : '';
34 }
35 function setCookie(name, value, days) {
36 var exp = '';
37 if (days) {
38 var d = new Date();
39 d.setTime(d.getTime() + days * 86400000);
40 exp = '; expires=' + d.toUTCString();
41 }
42 document.cookie = name + '=' + encodeURIComponent(value) + exp + '; path=/; SameSite=Lax';
43 }
44 function uuid() {
45 return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
46 var r = Math.random() * 16 | 0;
47 return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16);
48 });
49 }
50 var UID_COOKIE = 'cf_track';
51 var SID_COOKIE = 'cf_sess';
52 var UID = getCookie(UID_COOKIE);
53 if (!/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/.test(UID)) {
54 UID = uuid(); setCookie(UID_COOKIE, UID, 365 * 2);
55 }
56 var SID = getCookie(SID_COOKIE);
57 if (!/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/.test(SID)) {
58 SID = uuid(); setCookie(SID_COOKIE, SID, 1); // 1 day; server enforces 30-min idle
59 }
60
61 /* ---- Fingerprint ------------------------------------------------- */
62 function detect() {
63 var ua = navigator.userAgent || '';
64 var os = '', osv = '';
65 if (/Windows NT 10/i.test(ua)) { os = 'Windows'; osv = '10/11'; }
66 else if (/Windows NT ([\d.]+)/i.test(ua)) { os = 'Windows'; osv = RegExp.$1; }
67 else if (/Mac OS X ([\d_]+)/i.test(ua)) { os = 'macOS'; osv = RegExp.$1.replace(/_/g, '.'); }
68 else if (/iPhone|iPad|iPod/i.test(ua)) { os = 'iOS'; osv = (ua.match(/OS ([\d_]+)/) || [,''])[1].replace(/_/g, '.'); }
69 else if (/Android ([\d.]+)/i.test(ua)) { os = 'Android'; osv = RegExp.$1; }
70 else if (/Linux/i.test(ua)) { os = 'Linux'; }
71 var br = '', brv = '';
72 if (/Edg\/([\d.]+)/.test(ua)) { br = 'Edge'; brv = RegExp.$1; }
73 else if (/OPR\/([\d.]+)/.test(ua)) { br = 'Opera'; brv = RegExp.$1; }
74 else if (/Firefox\/([\d.]+)/.test(ua)){ br = 'Firefox'; brv = RegExp.$1; }
75 else if (/Chrome\/([\d.]+)/.test(ua)) { br = 'Chrome'; brv = RegExp.$1; }
76 else if (/Version\/([\d.]+).*Safari/.test(ua)) { br = 'Safari'; brv = RegExp.$1; }
77 var dev = 'desktop';
78 if (/bot|crawl|spider|google|bing|yahoo|slurp|facebookexternalhit/i.test(ua)) dev = 'bot';
79 else if (/iPad|Tablet/i.test(ua)) dev = 'tablet';
80 else if (/Mobi|iPhone|Android.*Mobile/i.test(ua)) dev = 'mobile';
81 var tz = '';
82 try { tz = Intl.DateTimeFormat().resolvedOptions().timeZone || ''; } catch (e) {}
83 return {
84 os_name: os, os_version: osv,
85 browser_name: br, browser_version: brv,
86 device_type: dev,
87 screen_w: (screen && screen.width) || 0,
88 screen_h: (screen && screen.height) || 0,
89 viewport_w: window.innerWidth || 0,
90 viewport_h: window.innerHeight || 0,
91 language: navigator.language || '',
92 timezone: tz
93 };
94 }
95 var FP = detect();
96
97 /* ---- UTM params ------------------------------------------------- */
98 function readUTM() {
99 var qs = location.search || '';
100 var out = {};
101 qs.replace(/[?&]([^=&]+)=([^&]*)/g, function (_, k, v) {
102 k = decodeURIComponent(k); v = decodeURIComponent(v.replace(/\+/g, ' '));
103 if (/^utm_/.test(k)) out[k] = v;
104 });
105 return out;
106 }
107 var UTM = readUTM();
108
109 /* ---- Lightweight transport (sendBeacon or fetch keepalive) ------ */
110 function send(url, payload) {
111 var body = JSON.stringify(payload);
112 if (navigator.sendBeacon) {
113 try { return navigator.sendBeacon(url, new Blob([body], { type: 'application/json' })); } catch (e) {}
114 }
115 try {
116 fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' },
117 body: body, keepalive: true, credentials: 'omit', mode: 'cors' });
118 } catch (e) {}
119 }
120
121 /* ---- Selector path for heatmap clicks --------------------------- */
122 function selectorPath(el) {
123 if (!el || el.nodeType !== 1) return '';
124 var parts = [], depth = 0;
125 while (el && el.nodeType === 1 && depth < 8) {
126 var tag = (el.tagName || '').toLowerCase();
127 if (!tag || tag === 'html') break;
128 if (el.id && /^[A-Za-z][\w\-:.]*$/.test(el.id)) {
129 parts.unshift('#' + el.id); break;
130 }
131 var part = tag;
132 var parent = el.parentNode;
133 if (parent && parent.children && parent.children.length > 1) {
134 var n = 1;
135 for (var i = 0; i < parent.children.length; i++) {
136 var sib = parent.children[i];
137 if (sib === el) break;
138 if (sib.tagName === el.tagName) n++;
139 }
140 part += ':nth-of-type(' + n + ')';
141 }
142 parts.unshift(part);
143 if (tag === 'body') break;
144 el = parent; depth++;
145 }
146 return parts.join('>').slice(0, 500);
147 }
148
149 /* ---- Payload builders ------------------------------------------- */
150 function basePayload(extras) {
151 var p = {
152 u: UID, s: SID,
153 p: location.pathname + location.search,
154 url: location.href,
155 r: document.referrer || ''
156 };
157 if (extras) for (var k in extras) if (extras.hasOwnProperty(k)) p[k] = extras[k];
158 return p;
159 }
160
161 function sendPageview() {
162 var payload = basePayload({
163 t: 'pv', title: document.title || '',
164 referrer: document.referrer || ''
165 });
166 for (var k in FP) if (FP.hasOwnProperty(k)) payload[k] = FP[k];
167 for (var u in UTM) if (UTM.hasOwnProperty(u)) payload[u] = UTM[u];
168 send(COLLECT, payload);
169 }
170
171 /* ---- Heartbeat + scroll depth ----------------------------------- */
172 var ACTIVE_MS = 0, LAST_TICK = Date.now(), IS_ACTIVE = true, MAX_SCROLL = 0;
173 function tick() {
174 var now = Date.now();
175 if (IS_ACTIVE && document.visibilityState === 'visible') {
176 ACTIVE_MS += (now - LAST_TICK);
177 }
178 LAST_TICK = now;
179 }
180 function fireHeartbeat() {
181 tick();
182 var secs = Math.round(ACTIVE_MS / 1000);
183 if (secs <= 0) return;
184 send(COLLECT, basePayload({ t: 'hb', d: secs, sd: MAX_SCROLL }));
185 // Also send a scroll-depth heatmap row so we can build the histogram.
186 send(HEAT, basePayload({
187 t: 'scroll', sd: MAX_SCROLL,
188 vw: window.innerWidth || 0,
189 vh: window.innerHeight || 0,
190 ph: document.body ? document.body.scrollHeight : 0
191 }));
192 ACTIVE_MS = 0;
193 }
194 document.addEventListener('visibilitychange', function () {
195 IS_ACTIVE = (document.visibilityState === 'visible');
196 LAST_TICK = Date.now();
197 });
198 window.addEventListener('scroll', function () {
199 var h = Math.max(document.body.scrollHeight - window.innerHeight, 1);
200 var pct = Math.min(100, Math.round((window.scrollY / h) * 100));
201 if (pct > MAX_SCROLL) MAX_SCROLL = pct;
202 }, { passive: true });
203 setInterval(fireHeartbeat, 15000);
204 window.addEventListener('pagehide', fireHeartbeat);
205 window.addEventListener('beforeunload', fireHeartbeat);
206
207 /* ---- Click + form_submit + rage clicks -------------------------- */
208 var clickStamps = [];
209 document.addEventListener('click', function (e) {
210 var t = e.target; var sel = selectorPath(t);
211 var xpct = (e.clientX / Math.max(window.innerWidth, 1)) * 100;
212 var ypct = ((e.clientY + window.scrollY) / Math.max(document.body.scrollHeight, 1)) * 100;
213 send(COLLECT, basePayload({
214 t: 'click', sel: sel,
215 text: ((t.innerText || t.value || '') + '').slice(0, 80),
216 x: xpct.toFixed(2), y: ypct.toFixed(2)
217 }));
218 // Heatmap row.
219 send(HEAT, basePayload({
220 t: 'hm', sel: sel,
221 x: xpct.toFixed(2),
222 y: ypct.toFixed(2),
223 vw: window.innerWidth || 0,
224 vh: window.innerHeight || 0
225 }));
226 // Rage-click detection: 3+ clicks on same element within 1s.
227 var now = Date.now();
228 clickStamps = clickStamps.filter(function (c) { return now - c.ts < 1000; });
229 clickStamps.push({ ts: now, sel: sel });
230 var same = 0;
231 for (var i = 0; i < clickStamps.length; i++) { if (clickStamps[i].sel === sel) same++; }
232 if (same >= 3) {
233 send(COLLECT, basePayload({ t: 'rage_click', sel: sel }));
234 clickStamps = [];
235 }
236 }, true);
237
238 document.addEventListener('submit', function (e) {
239 var f = e.target;
240 var sel = selectorPath(f);
241 send(COLLECT, basePayload({
242 t: 'form_submit', sel: sel,
243 name: (f.getAttribute && f.getAttribute('name')) || ''
244 }));
245 }, true);
246
247 /* ---- Public API ------------------------------------------------- */
248 window.cftrack = {
249 event: function (name, value, meta) {
250 send(COLLECT, basePayload({
251 t: 'custom', name: String(name).slice(0, 120),
252 v: typeof value === 'number' ? value : null,
253 meta: meta ? JSON.stringify(meta).slice(0, 480) : ''
254 }));
255 },
256 goal: function (name, value) {
257 send(GOAL, basePayload({
258 name: String(name).slice(0, 120),
259 v: typeof value === 'number' ? value : null
260 }));
261 },
262 identify: function (props) {
263 this.event('identify', null, props);
264 }
265 };
266
267 /* ---- Boot ------------------------------------------------------- */
268 function boot() { sendPageview(); }
269 if (document.readyState === 'loading') {
270 document.addEventListener('DOMContentLoaded', boot);
271 } else {
272 boot();
273 }
274})();
Keyboard: j next diff k previous diff g top G bottom r restore c compile-check ? help