# betterdocs/4.9.3/assets/static/public/js/analytics-tracker.js

BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ &amp; Chatbot, version 4.9.3. 156 lines.

- Page: https://pluginprobe.com/plugins/betterdocs/4.9.3/code/assets/static/public/js/analytics-tracker.js
- Raw: https://pluginprobe.com/plugins/betterdocs/4.9.3/raw/assets/static/public/js/analytics-tracker.js
- Modified: 2026-08-04T07:29:08+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/betterdocs/4.9.3/code/assets/static/public/js/analytics-tracker.js#L10-L20`.

```javascript
/**
 * BetterDocs lightweight analytics tracker.
 *
 * On a single doc: records a view after DOMContentLoaded, and tracks max scroll
 * depth, sending a "scroll" beacon on page hide (reading-completion). Both go
 * via fetch + keepalive with the X-WP-Nonce header (see the 401 fix in DECISIONS).
 * No jQuery, no dependencies.
 */
( function () {
	'use strict';

	var cfg = window.betterDocsTracker;
	if ( ! cfg || ! cfg.rest_url || ! cfg.post_id ) {
		return;
	}

	var postId = parseInt( cfg.post_id, 10 );
	if ( ! postId ) {
		return;
	}

	function post( body ) {
		try {
			fetch( cfg.rest_url, {
				method: 'POST',
				headers: {
					'Content-Type': 'application/json',
					'X-WP-Nonce': cfg.nonce || ''
				},
				body: JSON.stringify( body ),
				keepalive: true,
				credentials: 'same-origin'
			} );
		} catch ( e ) {}
	}

	function sendView() {
		var unique = 0;
		try {
			var key = 'bd_v_' + postId;
			if ( ! window.localStorage.getItem( key ) ) {
				unique = 1;
				window.localStorage.setItem( key, '1' );
			}
		} catch ( e ) {
			unique = 0;
		}

		var referrer = '';
		try {
			referrer = document.referrer || '';
		} catch ( e ) {}

		post( { post_id: postId, u: unique, referrer: referrer } );

		forwardGA4( 'betterdocs_doc_view', {} );
	}

	// Forward an event to GA4 when the integration is on. cfg.ga4.method decides how:
	//   datalayer -> raw window.dataLayer push (for sites already using GTM)
	//   gtag      -> gtag('event', …) via the gtag.js BetterDocs injected
	//   mp        -> nothing here; the server sends it via the Measurement Protocol
	function forwardGA4( event, extra ) {
		var ga4 = cfg.ga4;
		if ( ! ga4 || ! ga4.enabled ) {
			return;
		}
		// Shared params — the event name is passed separately to gtag, embedded for dataLayer.
		var params = { doc_id: postId };
		for ( var k in extra ) {
			if ( Object.prototype.hasOwnProperty.call( extra, k ) ) {
				params[ k ] = extra[ k ];
			}
		}
		try {
			if ( ga4.method === 'gtag' ) {
				if ( typeof window.gtag === 'function' ) {
					window.gtag( 'event', event, params );
				}
			} else if ( ga4.method === 'datalayer' ) {
				window.dataLayer = window.dataLayer || [];
				var dl = { event: event, doc_id: postId };
				for ( var j in extra ) {
					if ( Object.prototype.hasOwnProperty.call( extra, j ) ) {
						dl[ j ] = extra[ j ];
					}
				}
				window.dataLayer.push( dl );
			}
			// method 'mp' is handled server-side; nothing to emit client-side.
		} catch ( e ) {}
	}

	// --- reading completion (max scroll depth) ---
	var maxDepth = 0;
	var scrollSent = false;

	function computeDepth() {
		try {
			var doc = document.documentElement;
			var body = document.body;
			var scrollTop = window.pageYOffset || doc.scrollTop || body.scrollTop || 0;
			var winH = window.innerHeight || doc.clientHeight;
			var docH = Math.max(
				body.scrollHeight, doc.scrollHeight,
				body.offsetHeight, doc.offsetHeight,
				doc.clientHeight
			);
			if ( docH <= winH ) {
				return 100; // fits on screen = fully read
			}
			var depth = Math.round( ( ( scrollTop + winH ) / docH ) * 100 );
			return Math.max( 0, Math.min( 100, depth ) );
		} catch ( e ) {
			return 0;
		}
	}

	function onScroll() {
		var d = computeDepth();
		if ( d > maxDepth ) {
			maxDepth = d;
		}
	}

	function sendScroll() {
		if ( scrollSent ) {
			return;
		}
		scrollSent = true;
		var depth = Math.max( maxDepth, computeDepth() );
		post( { post_id: postId, event: 'scroll', depth: depth } );
		if ( depth >= 90 ) {
			forwardGA4( 'betterdocs_doc_complete', { depth: depth } );
		}
	}

	function init() {
		sendView();
		onScroll();
		window.addEventListener( 'scroll', onScroll, { passive: true } );
		window.addEventListener( 'pagehide', sendScroll );
		document.addEventListener( 'visibilitychange', function () {
			if ( document.visibilityState === 'hidden' ) {
				sendScroll();
			}
		} );
	}

	if ( document.readyState === 'loading' ) {
		document.addEventListener( 'DOMContentLoaded', init );
	} else {
		init();
	}
} )();

```
