Diff -- /var/www/vhosts/3dshawn.com/ptmatrix.3dshawn.com/assets/javascript/track.js
Diff

/var/www/vhosts/3dshawn.com/ptmatrix.3dshawn.com/assets/javascript/track.js

added on local at 2026-07-01 12:34:06

Added
+209
lines
Removed
-0
lines
Context
0
unchanged
Blobs
from
to ec25ef36e2ab
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 TaskForge -- Visitor tracking client.
3 Loaded on every page (marketing + in-app). No third-party deps.
4
5 What it does:
6 1. Generates / reads a UUID cookie (taskforge_track)
7 2. POSTs an "init" event with full client fingerprint
8 (OS, browser, screen, viewport, language, timezone, touch, ...)
9 3. Tracks page views, clicks (with x/y%), max scroll depth
10
11 Adapted from MODS::WebSTLs/track.js -- support chat + push-link
12 widget removed.
13===================================================================== */
14(function () {
15 'use strict';
16
17 /* ---- Admin / ingest-loop guard ---------------------------------- */
18 // Don't track admin/track endpoints (would create infinite recursion
19 // on /track.cgi itself if its 200 response somehow rendered HTML).
20 var __path = (window.location && window.location.pathname) || '';
21 if (/^\/(?:admin|track|_)/.test(__path)) return;
22
23 /* ---- Configuration ---------------------------------------------- */
24 var INGEST_URL = '/track.cgi';
25 var HEATMAP_SAMPLE_RATE = 1.0; // 1.0 = log every click
26
27 /* ---- UUID + cookie ---------------------------------------------- */
28 function uuid4() {
29 return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
30 var r = Math.random() * 16 | 0;
31 var v = c === 'x' ? r : (r & 0x3 | 0x8);
32 return v.toString(16);
33 });
34 }
35 function getCookie(name) {
36 var m = document.cookie.match(new RegExp('(?:^|; )' + name + '=([^;]*)'));
37 return m ? decodeURIComponent(m[1]) : '';
38 }
39 function setCookie(name, value, days) {
40 var expires = '';
41 if (days) {
42 var d = new Date();
43 d.setTime(d.getTime() + days * 86400000);
44 expires = '; expires=' + d.toUTCString();
45 }
46 document.cookie = name + '=' + encodeURIComponent(value) + expires + '; path=/; SameSite=Lax';
47 }
48 var TOKEN = getCookie('taskforge_track');
49 if (!/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/.test(TOKEN)) {
50 TOKEN = uuid4();
51 setCookie('taskforge_track', TOKEN, 365);
52 }
53
54 /* ---- Client fingerprint ----------------------------------------- */
55 function detectDevice() {
56 var ua = navigator.userAgent || '';
57 if (/iPad|Tablet/i.test(ua)) return 'tablet';
58 if (/Mobi/i.test(ua) || /iPhone|Android/i.test(ua)) return 'mobile';
59 return 'desktop';
60 }
61 function detectOS() {
62 var ua = navigator.userAgent || '';
63 if (/Windows NT 10/i.test(ua)) return { name: 'Windows', version: '10/11' };
64 if (/Windows NT ([\d.]+)/i.test(ua)) return { name: 'Windows', version: RegExp.$1 };
65 if (/Mac OS X ([\d_]+)/i.test(ua)) return { name: 'macOS', version: RegExp.$1.replace(/_/g, '.') };
66 if (/iPhone|iPad|iPod/i.test(ua)) return { name: 'iOS', version: (ua.match(/OS ([\d_]+)/) || [,''])[1].replace(/_/g, '.') };
67 if (/Android ([\d.]+)/i.test(ua)) return { name: 'Android', version: RegExp.$1 };
68 if (/Linux/i.test(ua)) return { name: 'Linux', version: '' };
69 return { name: '', version: '' };
70 }
71 function detectBrowser() {
72 var ua = navigator.userAgent || '';
73 if (/Edg\/([\d.]+)/.test(ua)) return { name: 'Edge', version: RegExp.$1 };
74 if (/OPR\/([\d.]+)/.test(ua)) return { name: 'Opera', version: RegExp.$1 };
75 if (/Firefox\/([\d.]+)/.test(ua)) return { name: 'Firefox', version: RegExp.$1 };
76 if (/Chrome\/([\d.]+)/.test(ua)) return { name: 'Chrome', version: RegExp.$1 };
77 if (/Safari\/.*Version\/([\d.]+)/.test(ua)) return { name: 'Safari', version: RegExp.$1 };
78 if (/Safari/.test(ua)) return { name: 'Safari', version: '' };
79 return { name: '', version: '' };
80 }
81
82 function buildFingerprint() {
83 var os = detectOS();
84 var br = detectBrowser();
85 var tz = '';
86 try { tz = Intl.DateTimeFormat().resolvedOptions().timeZone || ''; } catch (e) {}
87 return {
88 os_name: os.name,
89 os_version: os.version,
90 browser_name: br.name,
91 browser_version: br.version,
92 device_type: detectDevice(),
93 screen_w: (window.screen && window.screen.width) || 0,
94 screen_h: (window.screen && window.screen.height) || 0,
95 viewport_w: window.innerWidth || 0,
96 viewport_h: window.innerHeight || 0,
97 color_depth: (window.screen && window.screen.colorDepth) || 0,
98 pixel_ratio: window.devicePixelRatio || 1,
99 touch_capable: ('ontouchstart' in window || (navigator.maxTouchPoints > 0)) ? 1 : 0,
100 language: navigator.language || '',
101 timezone: tz,
102 referrer: document.referrer || '',
103 current_page_path: location.pathname + location.search
104 };
105 }
106
107 /* ---- Tiny JSON POST helper -------------------------------------- */
108 function post(url, obj) {
109 return fetch(url, {
110 method: 'POST',
111 headers: { 'Content-Type': 'application/json' },
112 body: JSON.stringify(obj),
113 credentials: 'same-origin',
114 keepalive: true
115 }).then(function (r) { return r.json(); }).catch(function () { return null; });
116 }
117
118 /* ---- 1. Init ---------------------------------------------------- */
119 var fp = buildFingerprint();
120 fp.kind = 'init';
121 fp.token = TOKEN;
122 fp.page = location.pathname + location.search;
123 post(INGEST_URL, fp).then(function () {
124 sendEvent('page_view', {});
125 });
126
127 /* ---- 2. Event helper ------------------------------------------- */
128 function sendEvent(type, extra) {
129 var body = {
130 kind: 'event',
131 token: TOKEN,
132 type: type,
133 page: location.pathname + location.search
134 };
135 if (extra) for (var k in extra) if (extra.hasOwnProperty(k)) body[k] = extra[k];
136 post(INGEST_URL, body);
137 }
138
139 /* ---- 3. Click tracking ----------------------------------------- */
140 function selectorPath(el) {
141 if (!el || el.nodeType !== 1) return '';
142 var parts = [];
143 var depth = 0;
144 while (el && el.nodeType === 1 && depth < 8) {
145 var tag = (el.tagName || '').toLowerCase();
146 if (!tag || tag === 'html') break;
147 if (el.id && /^[A-Za-z][\w\-:.]*$/.test(el.id)) {
148 parts.unshift('#' + el.id);
149 break;
150 }
151 var part = tag;
152 var parent = el.parentNode;
153 if (parent && parent.children && parent.children.length > 1) {
154 var n = 1;
155 for (var i = 0; i < parent.children.length; i++) {
156 var sib = parent.children[i];
157 if (sib === el) break;
158 if (sib.tagName === el.tagName) n++;
159 }
160 part += ':nth-of-type(' + n + ')';
161 }
162 parts.unshift(part);
163 if (tag === 'body') break;
164 el = parent;
165 depth++;
166 }
167 return parts.join('>').slice(0, 500);
168 }
169
170 document.addEventListener('click', function (e) {
171 if (Math.random() > HEATMAP_SAMPLE_RATE) return;
172 var t = e.target;
173 var sel = selectorPath(t);
174
175 var ox = 0.5, oy = 0.5, haveOffsets = 0;
176 try {
177 var r = t.getBoundingClientRect();
178 if (r && r.width > 0 && r.height > 0) {
179 ox = (e.clientX - r.left) / r.width;
180 oy = (e.clientY - r.top) / r.height;
181 if (ox < 0) ox = 0; else if (ox > 1) ox = 1;
182 if (oy < 0) oy = 0; else if (oy > 1) oy = 1;
183 haveOffsets = 1;
184 }
185 } catch (err) {}
186
187 sendEvent('click', {
188 selector: sel,
189 x_pct: ((e.clientX / Math.max(window.innerWidth, 1)) * 100).toFixed(2),
190 y_pct: (((e.clientY + window.scrollY) / Math.max(document.body.scrollHeight, 1)) * 100).toFixed(2),
191 offset_x_pct: haveOffsets ? ox.toFixed(4) : '',
192 offset_y_pct: haveOffsets ? oy.toFixed(4) : ''
193 });
194 }, true);
195
196 /* ---- 4. Scroll depth tracking ---------------------------------- */
197 var maxScrollPct = 0;
198 var scrollTimer = null;
199 window.addEventListener('scroll', function () {
200 var h = Math.max(document.body.scrollHeight - window.innerHeight, 1);
201 var pct = Math.min(100, Math.round((window.scrollY / h) * 100));
202 if (pct > maxScrollPct) maxScrollPct = pct;
203 clearTimeout(scrollTimer);
204 scrollTimer = setTimeout(function () {
205 sendEvent('scroll', { scroll_pct: maxScrollPct });
206 }, 1500);
207 }, { passive: true });
208
209})();
Keyboard: j next diff k previous diff g top G bottom r restore c compile-check ? help