PageSourceSearch

https://www.webscale.com/wp-content/plugins/wsn-funnel-tracker/assets/js/tracker.js

js webscale.com collected 2026-10-01 11:35:21 UTC 9,290 bytes, 257 lines download raw bytes

1/**
2 * WSN Funnel Tracker — front-end.
3 *  1. Anonymous visitor_id (first-party cookie, 2 yrs).
4 *  2. First-touch source (UTM / referrer / AI-chat / social / organic).
5 *  3. GA4 client_id.
6 *  4. Visitor's browser timezone (Intl API).
7 *  5. Identity stitching: if an identity token is present in the URL (param name
8 *     from Settings, default `wsn_cid`), it is stored in a cookie and sent with
9 *     every hit, so the anonymous visitor becomes a known contact — retroactively,
10 *     since all history is keyed on visitor_id.
11 *
12 * No X-WP-Nonce on /track and /lead (public, CDN-safe).
13 */
14(function () {
15	'use strict';
16
17	var CFG = window.WSN_CFG || {};
18	var COOKIE = 'wsn_vid';
19	var SRC_COOKIE = 'wsn_src';
20	var CID_COOKIE = 'wsn_cid';
21	var ID_PARAM = CFG.id_param || 'wsn_cid';
22
23	function setCookie(name, value, days) {
24		var d = new Date();
25		d.setTime(d.getTime() + days * 864e5);
26		document.cookie = name + '=' + encodeURIComponent(value) + ';expires=' + d.toUTCString() + ';path=/;SameSite=Lax';
27	}
28	function getCookie(name) {
29		return document.cookie.split('; ').reduce(function (acc, c) {
30			var parts = c.split('=');
31			return parts[0] === name ? decodeURIComponent(parts[1]) : acc;
32		}, '');
33	}
34
35	function visitorId() {
36		var id = getCookie(COOKIE);
37		if (!id) {
38			id = 'v_' + Date.now().toString(36) + '_' + Math.random().toString(36).slice(2, 10);
39			setCookie(COOKIE, id, 730);
40		}
41		return id;
42	}
43
44	// Identity token: from the URL param on click-through, else the sticky cookie.
45	function contactId() {
46		var v = new URLSearchParams(window.location.search).get(ID_PARAM);
47		if (v) {
48			v = v.slice(0, 191);
49			setCookie(CID_COOKIE, v, 730);
50			return v;
51		}
52		return getCookie(CID_COOKIE);
53	}
54
55	function timezone() {
56		try {
57			var tz = Intl.DateTimeFormat().resolvedOptions().timeZone || '';
58			return tz === 'Asia/Calcutta' ? 'Asia/Kolkata' : tz;
59		} catch (e) {
60			return '';
61		}
62	}
63
64	function detectSource() {
65		var q = new URLSearchParams(window.location.search);
66		var ref = document.referrer || '';
67		var host = '';
68		try { host = ref ? new URL(ref).hostname : ''; } catch (e) {}
69
70		if (q.get('utm_source')) {
71			return { source: q.get('utm_source'), medium: q.get('utm_medium') || 'campaign', campaign: q.get('utm_campaign') || '', term: q.get('utm_term') || q.get('keyword') || '' };
72		}
73		var ai = /chatgpt\.com|openai\.com|perplexity\.ai|gemini\.google|bard\.google|copilot\.microsoft|claude\.ai/i;
74		if (ai.test(host)) { return { source: host, medium: 'ai_chat', campaign: '', term: '' }; }
75		if (/google\./i.test(host)) { return { source: 'google', medium: 'organic', campaign: '', term: q.get('q') || q.get('keyword') || '' }; }
76		var social = /facebook|instagram|linkedin|t\.co|twitter|x\.com|youtube|reddit|tiktok/i;
77		if (social.test(host)) { return { source: host, medium: 'social', campaign: '', term: '' }; }
78		if (host && host !== window.location.hostname) { return { source: host, medium: 'referral', campaign: '', term: '' }; }
79		return { source: 'direct', medium: 'none', campaign: '', term: '' };
80	}
81
82	function firstTouch() {
83		var saved = getCookie(SRC_COOKIE);
84		if (saved) { try { return JSON.parse(saved); } catch (e) {} }
85		var src = detectSource();
86		setCookie(SRC_COOKIE, JSON.stringify(src), 730);
87		return src;
88	}
89
90	function measurementId() {
91		if (CFG.ga4_id) return CFG.ga4_id;
92		var m = document.cookie.match(/(?:^|;\s*)_ga_([A-Za-z0-9]+)=/);
93		return m ? ('G-' + m[1].toUpperCase()) : '';
94	}
95
96	function gaClientId(cb) {
97		var mid = measurementId();
98		if (typeof window.gtag === 'function' && mid) {
99			try { window.gtag('get', mid, 'client_id', function (cid) { cb(cid || ''); }); return; } catch (e) {}
100		}
101		var ga = getCookie('_ga');
102		cb(ga ? ga.split('.').slice(-2).join('.') : '');
103	}
104
105	function post(path, payload) {
106		return fetch(CFG.rest + path, {
107			method: 'POST',
108			headers: { 'Content-Type': 'application/json' },
109			credentials: 'same-origin',
110			body: JSON.stringify(payload)
111		});
112	}
113
114	function basePayload() {
115		var s = firstTouch();
116		return {
117			visitor_id: visitorId(),
118			contact_id: contactId(),
119			path: window.location.pathname,
120			title: document.title,
121			referrer: document.referrer || '',
122			source: s.source,
123			medium: s.medium,
124			campaign: s.campaign,
125			term: s.term,
126			tz: timezone()
127		};
128	}
129
130	function trackPageView() {
131		gaClientId(function (cid) {
132			var p = basePayload();
133			p.event = 'page_view';
134			p.ga_client_id = cid;
135			post('track', p);
136			if (typeof window.gtag === 'function') {
137				window.gtag('event', 'page_view', { wsn_visitor_id: p.visitor_id, campaign_source: p.source, campaign_medium: p.medium });
138			}
139		});
140	}
141
142	window.WSN = {
143		visitorId: visitorId,
144		contactId: contactId,
145		firstTouch: firstTouch,
146		timezone: timezone,
147		trackLead: function (lead) {
148			gaClientId(function (cid) {
149				var p = basePayload();
150				var dp = CFG.demo_path || '';
151				p.event = ( dp && window.location.pathname.indexOf(dp) !== -1 ) ? 'demo_lead' : 'form_lead';
152				p.ga_client_id = cid;
153				p.email = lead.email;
154				p.name = lead.name || '';
155				p.company = lead.company || '';
156				post('lead', p);
157				if (typeof window.gtag === 'function') {
158					window.gtag('event', 'generate_lead', { wsn_visitor_id: p.visitor_id, campaign_source: p.source, campaign_medium: p.medium });
159				}
160			});
161		}
162	};
163
164	function trackEvent(name) {
165		gaClientId(function (cid) {
166			var p = basePayload();
167			p.event = name;
168			p.ga_client_id = cid;
169			post('track', p);
170		});
171	}
172	if (window.WSN) { window.WSN.trackEvent = trackEvent; }
173
174	// Bricks form success -> Demo Form Submit (step 4), scoped to the demo path.
175	var WSN_DEMO_PATH = CFG.demo_path || '';
176	if (WSN_DEMO_PATH) {
177		document.addEventListener('bricks/form/success', function () {
178			if (window.location.pathname.indexOf(WSN_DEMO_PATH) !== -1) { trackEvent('demo_lead'); }
179		});
180	}
181
182	// Post-submit redirect flag: the demo form redirects to the thank-you page
183	// with ?wsn_demo=1, so only real submissions (not direct visits) record demo_lead.
184	if (new URLSearchParams(window.location.search).get('wsn_demo') === '1') {
185		trackEvent('demo_lead');
186	}
187
188	// Fill any hidden field named visitor_id / wsn_visitor_id (e.g. on your
189	// Request-a-Demo form) with the anonymous ID, so it submits to your CRM/Zapier.
190	function fillVisitorFields() {
191		var vid = visitorId();
192		var els = document.querySelectorAll('input[name="visitor_id"], input[name="wsn_visitor_id"]');
193		for (var i = 0; i < els.length; i++) { els[i].value = vid; els[i].setAttribute('value', vid); }
194	}
195	if (document.readyState !== 'loading') { fillVisitorFields(); }
196	else { document.addEventListener('DOMContentLoaded', fillVisitorFields); }
197	setTimeout(fillVisitorFields, 1500);
198	document.addEventListener('bricks/form/submit', fillVisitorFields);
199
200	// --- Auto-capture form leads: read email on Bricks submit, send to /lead on success ---
201	function wsnIsEmail(v) { return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test((v || '').trim()); }
202	function wsnLabelText(el) {
203		var t = '';
204		if (el.id) { var l = document.querySelector('label[for="' + el.id + '"]'); if (l) { t = l.textContent; } }
205		if (!t && el.closest) { var p = el.closest('label'); if (p) { t = p.textContent; } }
206		if (!t) { t = el.getAttribute('placeholder') || el.name || ''; }
207		return (t || '').toLowerCase();
208	}
209	function wsnFieldByHint(form, hints) {
210		var els = form.querySelectorAll('input[type="text"], input:not([type])');
211		for (var i = 0; i < els.length; i++) {
212			var t = wsnLabelText(els[i]);
213			for (var h = 0; h < hints.length; h++) { if (t.indexOf(hints[h]) !== -1) { return (els[i].value || '').trim(); } }
214		}
215		return '';
216	}
217	function wsnFindEmail(form) {
218		var e = form.querySelector('input[type="email"]');
219		if (e && wsnIsEmail(e.value)) { return e.value.trim(); }
220		var all = form.querySelectorAll('input, textarea');
221		for (var i = 0; i < all.length; i++) {
222			var lab = wsnLabelText(all[i]);
223			if (wsnIsEmail(all[i].value) && lab.indexOf('email') !== -1 && lab.indexOf('emailto') === -1) { return all[i].value.trim(); }
224		}
225		for (var j = 0; j < all.length; j++) { if (wsnIsEmail(all[j].value)) { return all[j].value.trim(); } }
226		return '';
227	}
228	var wsnPendingLead = null;
229	function wsnCaptureFrom(form) {
230		if (!form || !form.querySelectorAll) { return; }
231		var email = wsnFindEmail(form);
232		if (!wsnIsEmail(email)) { return; }
233		wsnPendingLead = {
234			email: email,
235			name: (wsnFieldByHint(form, ['first name', 'first']) + ' ' + wsnFieldByHint(form, ['last name', 'last', 'surname'])).trim(),
236			company: wsnFieldByHint(form, ['company'])
237		};
238	}
239	document.addEventListener('submit', function (e) { fillVisitorFields(); wsnCaptureFrom(e.target); }, true);
240	document.addEventListener('bricks/form/submit', function () {
241		if (wsnPendingLead) { return; }
242		var forms = document.querySelectorAll('form.brxe-form, form');
243		for (var i = 0; i < forms.length; i++) { wsnCaptureFrom(forms[i]); if (wsnPendingLead) { break; } }
244	});
245	document.addEventListener('bricks/form/success', function () {
246		if (wsnPendingLead && window.WSN && typeof window.WSN.trackLead === 'function') {
247			window.WSN.trackLead(wsnPendingLead);
248			wsnPendingLead = null;
249		}
250	});
251
252	if (document.readyState !== 'loading') {
253		trackPageView();
254	} else {
255		document.addEventListener('DOMContentLoaded', trackPageView);
256	}
257})();

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.