PageSourceSearch

https://acpsmd.org/wp-content/plugins/acps-site-toolkit/assets/js/tracking.js?ver=5.0.0

js acpsmd.org collected 2026-10-02 05:35:39 UTC 5,399 bytes, 166 lines download raw bytes

1/**
2 * Cayden Form Manager — journey tracking beacon.
3 *
4 * CRITICAL (spec §4.2 / §13): this is the ONLY place a page visit is recorded.
5 * The site runs behind WP Engine Global Edge Security, which serves cached
6 * HTML; a PHP write during render never fires for a cached page. So tracking
7 * must be a client-side beacon to an uncached REST endpoint. If you ever move
8 * visit-recording server-side, the journey feature silently dies in production.
9 */
10( function () {
11	'use strict';
12
13	var cfg = window.ACPS_ST || {};
14	if ( ! cfg.restUrl ) {
15		return;
16	}
17
18	// Master off switch: when analytics/visitor/page tracking is disabled, do
19	// nothing at all — no beacon, heartbeat, presence, cookies or network calls.
20	// Forms still work: they read the config below and degrade without a session.
21	if ( ! cfg.analytics ) {
22		window.ACPS_ST_RT = window.ACPS_ST_RT || { token: '', active: false };
23		return;
24	}
25
26	var SID_COOKIE = 'acps_st_sid';
27	var CONSENT_COOKIE = 'acps_st_consent';
28
29	/* ---- tiny cookie helpers (first-party only) ---------------------- */
30	function readCookie( name ) {
31		var m = document.cookie.match( '(^|;)\\s*' + name + '\\s*=\\s*([^;]+)' );
32		return m ? decodeURIComponent( m.pop() ) : '';
33	}
34	function writeCookie( name, value, minutes ) {
35		var expires = '';
36		if ( minutes ) {
37			var d = new Date();
38			d.setTime( d.getTime() + minutes * 60 * 1000 );
39			expires = '; expires=' + d.toUTCString();
40		}
41		document.cookie = name + '=' + encodeURIComponent( value ) + expires +
42			'; path=/; SameSite=Lax' + ( location.protocol === 'https:' ? '; Secure' : '' );
43	}
44
45	/* ---- session token (session-scoped, first-party) ----------------- */
46	function makeToken() {
47		var bytes = new Uint8Array( 20 );
48		( window.crypto || window.msCrypto ).getRandomValues( bytes );
49		var hex = '';
50		for ( var i = 0; i < bytes.length; i++ ) {
51			hex += ( '0' + bytes[ i ].toString( 16 ) ).slice( -2 );
52		}
53		return hex;
54	}
55	function getToken() {
56		var t = readCookie( SID_COOKIE );
57		if ( ! /^[a-f0-9]{40}$/.test( t ) ) {
58			t = makeToken();
59		}
60		// Refresh the idle window on every activity.
61		writeCookie( SID_COOKIE, t, cfg.idleMinutes || 30 );
62		return t;
63	}
64
65	/* Unique-user identity is now derived SERVER-SIDE from the anonymized IP +
66	   browser (the same signal the spam guard uses), so there is deliberately no
67	   client-side visitor id here — clearing cookies/cache can't mint a new one. */
68
69	/* ---- consent ----------------------------------------------------- */
70	function hasConsent() {
71		return readCookie( CONSENT_COOKIE ) === '1';
72	}
73	function trackingActive() {
74		if ( ! cfg.tracking ) {
75			return false;
76		}
77		// Logged-in admins are excluded from analytics entirely.
78		if ( cfg.suppress ) {
79			return false;
80		}
81		if ( cfg.consentMode && ! hasConsent() ) {
82			return false;
83		}
84		return true;
85	}
86
87	/* Public: let a cookie banner grant/revoke consent. Forms keep working
88	   regardless (spec §4.4). */
89	window.acpsStGrantConsent = function () {
90		writeCookie( CONSENT_COOKIE, '1', 60 * 24 * 365 );
91		beacon();
92	};
93	window.acpsStRevokeConsent = function () {
94		writeCookie( CONSENT_COOKIE, '0', 60 * 24 * 365 );
95	};
96
97	/* Shared runtime other scripts read (forms/feedback). */
98	var runtime = window.ACPS_ST_RT = {
99		token: '',
100		active: false,
101		restUrl: cfg.restUrl
102	};
103
104	function send( path, payload, useBeacon ) {
105		var url = cfg.restUrl.replace( /\/$/, '' ) + path;
106		var body = JSON.stringify( payload );
107		if ( useBeacon && navigator.sendBeacon ) {
108			var blob = new Blob( [ body ], { type: 'application/json' } );
109			navigator.sendBeacon( url, blob );
110			return;
111		}
112		fetch( url, {
113			method: 'POST',
114			headers: { 'Content-Type': 'application/json' },
115			body: body,
116			keepalive: true,
117			credentials: 'same-origin'
118		} ).catch( function () {} );
119	}
120
121	/* MINIMAL-REQUEST DESIGN: exactly ONE non-blocking request per pageview.
122	   navigator.sendBeacon hands the request to the browser to send in the
123	   background — it never delays the page or competes with rendering. There is
124	   deliberately NO heartbeat, NO unload beacon and NO polling: those were the
125	   source of the request flood. Time-on-page is still filled in server-side
126	   from the next pageview, and "active now" means a pageview in the last few
127	   minutes. */
128	function beacon() {
129		if ( ! trackingActive() ) {
130			return;
131		}
132		// Keep the session token available to forms even on pageviews we don't
133		// record, so form submissions still link to a session.
134		runtime.token = getToken();
135		runtime.active = true;
136
137		// Sampling: only a share of pageviews actually send a beacon. On a
138		// high-traffic cached site this is the biggest lever on origin load —
139		// e.g. 25 means one origin request for every four pageviews.
140		var rate = cfg.sampleRate || 100;
141		if ( rate < 100 && Math.random() * 100 >= rate ) {
142			return;
143		}
144
145		send( '/beacon', {
146			session: runtime.token,
147			consent: cfg.consentMode ? ( hasConsent() ? 1 : 0 ) : 1,
148			post_id: cfg.postId || 0,
149			url: location.href,
150			title: document.title,
151			referrer: document.referrer || '',
152			viewport: window.innerWidth + 'x' + window.innerHeight
153		}, true );
154	}
155
156	// Expose a session token to forms even before the beacon fires.
157	if ( trackingActive() ) {
158		runtime.token = getToken();
159	}
160
161	if ( document.readyState === 'loading' ) {
162		document.addEventListener( 'DOMContentLoaded', beacon );
163	} else {
164		beacon();
165	}
166} )();

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.