1'use strict'; 2/** 3 * ga4-tag.js â Centralized Google Analytics 4 loader for Universidad.io 4 * 5 * Handles: 6 * 1. Google Consent Mode v2 (default denied for analytics_storage) 7 * 2. gtag.js async load with measurement ID 8 * 3. SPA page_view tracking via router:navigate event 9 * 4. NO PII is ever sent to GA4 (no email, no name, no user id) 10 * 11 * Loaded in <head> of every page (SPA shell + public pages + EJS landings). 12 */ 13(function () { 14 // Guard: only install once 15 if (window.__ga4TagInstalled) return; 16 window.__ga4TagInstalled = true; 17 18 var GA4_ID = window.__GA4_ID || 'G-HBMW9NXTZ0'; 19 20 // ââ 1. Consent Mode v2: default denied âââââââââââââââââââââââââââââ 21 window.dataLayer = window.dataLayer || []; 22 function gtag() { window.dataLayer.push(arguments); } 23 window.gtag = gtag; 24 25 gtag('consent', 'default', { 26 analytics_storage: 'denied', 27 ad_storage: 'denied', 28 wait_for_update: 500 29 }); 30 31 // ââ 2. Load gtag.js async ââââââââââââââââââââââââââââââââââââââââââ 32 var script = document.createElement('script'); 33 script.async = true; 34 script.src = 'https://www.googletagmanager.com/gtag/js?id=' + GA4_ID; 35 var firstScript = document.getElementsByTagName('script')[0]; 36 if (firstScript && firstScript.parentNode) { 37 firstScript.parentNode.insertBefore(script, firstScript); 38 } else { 39 document.head.appendChild(script); 40 } 41 42 gtag('js', new Date()); 43 gtag('config', GA4_ID, { 44 send_page_view: true, // send initial page_view on load 45 anonymize_ip: true, 46 cookie_flags: 'SameSite=Lax;Secure' 47 }); 48 49 // ââ 3. SPA tracking: listen for router:navigate ââââââââââââââââââââ 50 // The SPA router in /js/router.js fires this CustomEvent on every 51 // navigation with detail.route = slug (e.g. 'dashboard', 'chat'). 52 var _lastTrackedPath = location.pathname; 53 54 document.addEventListener('router:navigate', function (e) { 55 var route = (e.detail && e.detail.route) || ''; 56 var path = '/app/' + route; 57 // Avoid double-firing for the same path 58 if (path === _lastTrackedPath) return; 59 _lastTrackedPath = path; 60 61 gtag('event', 'page_view', { 62 page_path: path, 63 page_location: location.origin + path, 64 page_title: document.title 65 }); 66 }); 67 68 // ââ 4. Consent helper: call this when user accepts analytics âââââââ 69 // Usage: window.ga4GrantConsent() 70 window.ga4GrantConsent = function () { 71 gtag('consent', 'update', { 72 analytics_storage: 'granted' 73 }); 74 }; 75 76 // Deny (revoke) helper 77 window.ga4DenyConsent = function () { 78 gtag('consent', 'update', { 79 analytics_storage: 'denied' 80 }); 81 }; 82})();
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.