PageSourceSearch

https://primamf.com/app/modules/website/assets/js/analytics-tracker.js

js primamf.com collected 2026-10-02 01:40:02 UTC 5,965 bytes, 162 lines download raw bytes

1(function () {
2    'use strict';
3
4    if (typeof window.sharedSocket === 'undefined') return;
5
6    var socket = window.sharedSocket;
7    var sessionId = null;
8    var startTime = Date.now();
9    var maxScroll = 0;
10    var clickBuffer = [];
11    var heartbeatInterval = null;
12    var clickFlushInterval = null;
13    var scrollTimeout = null;
14
15    // --- PAGE VIEW ---
16    function trackPageView() {
17        var sourceID = typeof getOrCreateSourceID === 'function' ? getOrCreateSourceID() : localStorage.getItem('sourceID');
18        var payload = {
19            sourceID: sourceID,
20            url: window.location.pathname + window.location.search,
21            title: document.title,
22            referrer: document.referrer || null,
23            viewportW: window.innerWidth,
24            viewportH: window.innerHeight
25        };
26
27        socket.emit('analytics:pageview', payload, function (response) {
28            if (response && response.sessionId) {
29                sessionId = response.sessionId;
30                console.log('[Analytics] Session started:', sessionId);
31                startHeartbeat();
32                startClickFlush();
33            }
34        });
35    }
36
37    // --- HEARTBEAT (toutes les 15s) ---
38    function startHeartbeat() {
39        if (heartbeatInterval) clearInterval(heartbeatInterval);
40        heartbeatInterval = setInterval(function () {
41            if (!sessionId) return;
42            socket.emit('analytics:heartbeat', {
43                sessionId: sessionId,
44                durationMs: Date.now() - startTime,
45                scrollPct: maxScroll
46            });
47        }, 15000);
48    }
49
50    // --- SCROLL TRACKING (throttle 500ms) ---
51    function getScrollPercent() {
52        var scrollTop = window.pageYOffset || document.documentElement.scrollTop;
53        var docHeight = Math.max(
54            document.body.scrollHeight,
55            document.documentElement.scrollHeight
56        ) - window.innerHeight;
57        if (docHeight <= 0) return 100;
58        return Math.min(Math.round((scrollTop / docHeight) * 100), 100);
59    }
60
61    window.addEventListener('scroll', function () {
62        if (scrollTimeout) return;
63        scrollTimeout = setTimeout(function () {
64            scrollTimeout = null;
65            var pct = getScrollPercent();
66            if (pct > maxScroll) {
67                maxScroll = pct;
68                if (sessionId) {
69                    socket.emit('analytics:scroll', {
70                        sessionId: sessionId,
71                        scrollPct: maxScroll
72                    });
73                }
74            }
75        }, 500);
76    }, { passive: true });
77
78    // --- CLICK TRACKING (capture phase = intercepte avant stopPropagation) ---
79    // Remonte au parent significatif (a, button, img, element avec id/onclick)
80    function findMeaningfulTarget(el) {
81        var node = el;
82        while (node && node !== document.body && node !== document.documentElement) {
83            var tag = (node.tagName || '').toLowerCase();
84            if (tag === 'a' || tag === 'button' || tag === 'input' || tag === 'select' ||
85                tag === 'img' || tag === 'video' || node.id ||
86                (node.getAttribute && (node.getAttribute('role') === 'button' || node.getAttribute('onclick')))) {
87                return node;
88            }
89            node = node.parentElement;
90        }
91        return el; // fallback: element original
92    }
93
94    document.addEventListener('click', function (e) {
95        if (!sessionId) return;
96
97        var meaningful = findMeaningfulTarget(e.target);
98
99        // Centre de l'element (pas la souris) = position stable et coherente
100        var rect = meaningful.getBoundingClientRect();
101        var scrollX = window.pageXOffset || 0;
102        var scrollY = window.pageYOffset || 0;
103        var pageW = document.documentElement.scrollWidth || window.innerWidth;
104        var pageH = document.documentElement.scrollHeight || window.innerHeight;
105
106        var centerX = rect.left + rect.width / 2 + scrollX;
107        var centerY = rect.top + rect.height / 2 + scrollY;
108
109        var clickData = {
110            xPct: pageW > 0 ? parseFloat(((centerX / pageW) * 100).toFixed(1)) : 0,
111            yPct: pageH > 0 ? parseFloat(((centerY / pageH) * 100).toFixed(1)) : 0,
112            tag: (meaningful.tagName || '').toLowerCase(),
113            id: meaningful.id || '',
114            cls: (meaningful.className && typeof meaningful.className === 'string') ? meaningful.className.substring(0, 255) : '',
115            text: (meaningful.innerText || meaningful.textContent || '').trim().substring(0, 50)
116        };
117
118        clickBuffer.push(clickData);
119
120        // Si clic sur un lien, flush immediatement (navigation imminente)
121        if (meaningful.tagName === 'A' || (meaningful.closest && meaningful.closest('a[href]'))) {
122            flushClicks();
123        }
124    }, true); // <-- capture: true
125
126    // Flush clics toutes les 2s
127    function startClickFlush() {
128        if (clickFlushInterval) clearInterval(clickFlushInterval);
129        clickFlushInterval = setInterval(flushClicks, 2000);
130    }
131
132    function flushClicks() {
133        if (!sessionId || clickBuffer.length === 0) return;
134        var batch = clickBuffer.splice(0, clickBuffer.length);
135        socket.emit('analytics:click-batch', {
136            sessionId: sessionId,
137            clicks: batch
138        });
139    }
140
141    // --- CLEANUP au changement de page ---
142    window.addEventListener('beforeunload', function () {
143        flushClicks();
144        if (sessionId) {
145            socket.emit('analytics:heartbeat', {
146                sessionId: sessionId,
147                durationMs: Date.now() - startTime,
148                scrollPct: maxScroll
149            });
150        }
151    });
152
153    // --- INIT ---
154    if (socket.connected) {
155        trackPageView();
156    } else {
157        socket.on('connect', function onAnalyticsConnect() {
158            socket.off('connect', onAnalyticsConnect);
159            trackPageView();
160        });
161    }
162})();

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.