PageSourceSearch

https://goodladunscripted.com/wp-content/plugins/embedpress/assets/js/analytics-tracker.js?ver=1758571708

js goodladunscripted.com collected 2026-10-02 15:46:52 UTC 17,646 bytes, 473 lines download raw bytes

1(function () {
2    'use strict';
3
4    // Cookie utility functions
5    function setCookie(name, value, days, isSession = false) {
6        let expires = '';
7        if (!isSession && days) {
8            const date = new Date();
9            date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
10            expires = '; expires=' + date.toUTCString();
11        }
12        document.cookie = name + '=' + (value || '') + expires + '; path=/; SameSite=Lax';
13    }
14
15    function getCookie(name) {
16        const nameEQ = name + '=';
17        const ca = document.cookie.split(';');
18        for (let i = 0; i < ca.length; i++) {
19            let c = ca[i];
20            while (c.charAt(0) === ' ') c = c.substring(1, c.length);
21            if (c.indexOf(nameEQ) === 0) return c.substring(nameEQ.length, c.length);
22        }
23        return null;
24    }
25
26    // Generate or load user ID from cookie (persistent across sessions)
27    function getOrCreateUserId() {
28        const COOKIE_NAME = 'ep_user_id';
29
30        // Try to get existing user ID from cookie
31        let userId = getCookie(COOKIE_NAME);
32
33        if (!userId) {
34            // Generate new persistent user ID
35            userId = 'ep-user-' + Date.now() + '-' + Math.random().toString(36).substring(2, 15);
36            // Set cookie to expire in 30 days (1 month)
37            setCookie(COOKIE_NAME, userId, 30);
38        }
39
40        return userId;
41    }
42
43    // Generate session ID for current browser session (for deduplication within session)
44    function getOrCreateSessionId() {
45        const COOKIE_NAME = 'ep_session_id';
46        let id = getCookie(COOKIE_NAME);
47        if (!id) {
48            id = 'ep-sess-' + Date.now() + '-' + Math.random().toString(36).substring(2, 10);
49            // Set as session cookie (expires when browser closes)
50            setCookie(COOKIE_NAME, id, null, true);
51        }
52        return id;
53    }
54
55
56    // Configuration
57    const config = {
58        viewThreshold: 49,
59        viewDuration: 3000,
60        viewResetCooldown: 60000, // Optional: allow re-counting views after 60s
61        impressionCooldown: 5000, // Throttle repeated impressions
62        clickCooldown: 2000, // Throttle repeated clicks
63        debug: false,
64        restUrl: embedpress_analytics?.rest_url || '/wp-json/embedpress/v1/analytics/',
65        userId: getOrCreateUserId(),
66        sessionId: getOrCreateSessionId(),
67        pageUrl: embedpress_analytics?.page_url || window.location.href,
68        postId: embedpress_analytics?.post_id || 0,
69        ipLocationData: null
70    };
71
72    const trackedElements = new Map();
73    const sessionData = {
74        viewedContent: new Set(),
75        clickedContent: new Map(), // contentId -> lastClickTime
76        impressedContent: new Map() // contentId -> lastImpressionTime
77    };
78
79    // Get browser fingerprint for deduplication
80    function getBrowserFingerprint() {
81        const canvas = document.createElement('canvas');
82        const ctx = canvas.getContext('2d');
83        ctx.textBaseline = 'top';
84        ctx.font = '14px Arial';
85        ctx.fillText('Browser fingerprint', 2, 2);
86
87        // Create a more comprehensive fingerprint
88        const fingerprintData = {
89            userAgent: navigator.userAgent,
90            language: navigator.language,
91            platform: navigator.platform,
92            screen: `${screen.width}x${screen.height}`,
93            timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
94            canvas: canvas.toDataURL(),
95            // Add more unique browser characteristics
96            cookieEnabled: navigator.cookieEnabled,
97            doNotTrack: navigator.doNotTrack,
98            hardwareConcurrency: navigator.hardwareConcurrency || 0,
99            maxTouchPoints: navigator.maxTouchPoints || 0,
100            colorDepth: screen.colorDepth,
101            pixelDepth: screen.pixelDepth,
102            availWidth: screen.availWidth,
103            availHeight: screen.availHeight,
104            // Add WebGL fingerprint for better uniqueness
105            webgl: getWebGLFingerprint()
106        };
107
108        // Use a simple hash function instead of truncating base64
109        return simpleHash(JSON.stringify(fingerprintData));
110    }
111
112    // Simple hash function to create consistent fingerprints
113    function simpleHash(str) {
114        let hash = 0;
115        if (str.length === 0) return hash.toString();
116        for (let i = 0; i < str.length; i++) {
117            const char = str.charCodeAt(i);
118            hash = ((hash << 5) - hash) + char;
119            hash = hash & hash; // Convert to 32-bit integer
120        }
121        // Convert to positive hex string and pad to ensure consistent length
122        return Math.abs(hash).toString(16).padStart(8, '0');
123    }
124
125    // Get WebGL fingerprint for additional uniqueness
126    function getWebGLFingerprint() {
127        try {
128            const canvas = document.createElement('canvas');
129            const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
130            if (!gl) return 'no-webgl';
131
132            const debugInfo = gl.getExtension('WEBGL_debug_renderer_info');
133            return {
134                vendor: gl.getParameter(gl.VENDOR),
135                renderer: gl.getParameter(gl.RENDERER),
136                version: gl.getParameter(gl.VERSION),
137                shadingLanguageVersion: gl.getParameter(gl.SHADING_LANGUAGE_VERSION),
138                unmaskedVendor: debugInfo ? gl.getParameter(debugInfo.UNMASKED_VENDOR_WEBGL) : 'unknown',
139                unmaskedRenderer: debugInfo ? gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL) : 'unknown'
140            };
141        } catch (e) {
142            return 'webgl-error';
143        }
144    }
145
146    const observer = new IntersectionObserver((entries) => {
147        entries.forEach(entry => {
148            const element = entry.target;
149            const data = trackedElements.get(element);
150            if (!data) return;
151
152            let visiblePercentage = Math.floor(entry.intersectionRatio * 100);
153            if (visiblePercentage < config.viewThreshold && entry.isIntersecting) {
154                const rect = entry.boundingClientRect;
155                const vh = window.innerHeight, vw = window.innerWidth;
156                const visibleH = Math.min(rect.bottom, vh) - Math.max(rect.top, 0);
157                const visibleW = Math.min(rect.right, vw) - Math.max(rect.left, 0);
158                const viewportCoverage = (visibleH * visibleW) / (vh * vw) * 100;
159                if (viewportCoverage >= 30) {
160                    visiblePercentage = Math.max(visiblePercentage, config.viewThreshold);
161                }
162            }
163
164            data.viewportPercentage = visiblePercentage;
165            data.inViewport = entry.isIntersecting;
166
167            if (entry.isIntersecting) {
168                trackImpression(element, data);
169            }
170
171            handleViewTracking(element, data, visiblePercentage);
172        });
173    }, {
174        threshold: Array.from({ length: 11 }, (_, i) => i / 10)
175    });
176
177    function trackImpression(element, data) {
178        // Check if tracking is enabled
179        if (!isTrackingEnabled()) return;
180
181        const now = Date.now();
182        const last = sessionData.impressedContent.get(data.contentId) || 0;
183
184        if (now - last < config.impressionCooldown) return;
185        sessionData.impressedContent.set(data.contentId, now);
186        data.lastImpressionTime = now;
187
188
189        sendTrackingData({
190            content_id: data.contentId,
191            interaction_type: 'impression',
192            user_id: config.userId,
193            session_id: config.sessionId,
194            page_url: config.pageUrl,
195            interaction_data: {
196                embed_type: data.embedType,
197                embed_url: data.embedUrl,
198                viewport_percentage: data.viewportPercentage,
199                location_data: config.ipLocationData,
200                browser_fingerprint: getBrowserFingerprint()
201            }
202        });
203    }
204
205    function handleViewTracking(element, data, visiblePercentage) {
206        if (!data.inViewport || visiblePercentage < config.viewThreshold) {
207            if (data.viewTimer) {
208                clearTimeout(data.viewTimer);
209                data.viewTimer = null;
210            }
211            return;
212        }
213
214        const now = Date.now();
215
216        // Optional: allow re-tracking views if cooldown passed
217        if (data.viewTracked && now - (data.lastViewTime || 0) > config.viewResetCooldown) {
218            data.viewTracked = false;
219        }
220
221        if (data.viewTracked || sessionData.viewedContent.has(data.contentId)) return;
222
223        if (!data.viewTimer) {
224            data.viewTimer = setTimeout(() => {
225                trackView(element, data);
226                data.viewTracked = true;
227                data.lastViewTime = Date.now();
228                sessionData.viewedContent.add(data.contentId);
229                data.viewTimer = null;
230            }, config.viewDuration);
231        }
232    }
233
234    function trackView(element, data) {
235        // Check if tracking is enabled
236        if (!isTrackingEnabled()) return;
237
238        sendTrackingData({
239            content_id: data.contentId,
240            interaction_type: 'view',
241            user_id: config.userId,
242            session_id: config.sessionId,
243            page_url: config.pageUrl,
244            view_duration: data.viewDuration || config.viewDuration || 0,
245            interaction_data: {
246                embed_type: data.embedType,
247                embed_url: data.embedUrl,
248                viewport_percentage: data.viewportPercentage,
249                view_duration: data.viewDuration || config.viewDuration || 0,
250                browser_fingerprint: getBrowserFingerprint()
251            }
252        });
253    }
254
255    function setupClickTracking() {
256        trackedElements.forEach((data, element) => {
257            // Only track actual user clicks
258            element.addEventListener('click', (event) => {
259                // Make sure this is a real user click
260                if (!event.isTrusted) return;
261
262                const now = Date.now();
263                const last = sessionData.clickedContent.get(data.contentId) || 0;
264
265                // Prevent duplicate clicks within cooldown period
266                if (now - last < config.clickCooldown) return;
267
268                // Check if tracking is enabled
269                if (!isTrackingEnabled()) return;
270
271                sessionData.clickedContent.set(data.contentId, now);
272
273                sendTrackingData({
274                    content_id: data.contentId,
275                    interaction_type: 'click',
276                    user_id: config.userId,
277                    session_id: config.sessionId,
278                    page_url: config.pageUrl,
279                    interaction_data: {
280                        embed_type: data.embedType,
281                        embed_url: data.embedUrl,
282                        browser_fingerprint: getBrowserFingerprint()
283                    }
284                });
285            });
286        });
287    }
288
289    function prepareElementForTracking(element) {
290        if (trackedElements.has(element)) return;
291
292        const embedType = element.getAttribute('data-embed-type');
293        if (!embedType) return;
294
295        // Get a stable content ID based on embed URL or existing attributes
296        let contentId = element.getAttribute('data-embedpress-content') ||
297            element.getAttribute('data-source-id') ||
298            element.getAttribute('data-emid');
299
300        if (!contentId) {
301            // Generate stable ID based on embed URL or iframe src
302            const embedUrl = element.getAttribute('data-embed-url') ||
303                element.querySelector('iframe')?.src ||
304                element.querySelector('embed')?.src ||
305                element.querySelector('object')?.data ||
306                window.location.href;
307
308            // Create a hash-based ID that will be consistent for the same content
309            contentId = 'ep-' + embedType + '-' + btoa(embedUrl).replace(/[^a-zA-Z0-9]/g, '').substring(0, 10);
310        }
311
312        element.setAttribute('data-embedpress-content', contentId);
313
314        const data = {
315            contentId,
316            embedType,
317            embedUrl: getEmbedUrl(element),
318            inViewport: false,
319            viewTimer: null,
320            viewTracked: false,
321            lastImpressionTime: 0,
322            lastViewTime: 0,
323            viewportPercentage: 0
324        };
325
326        trackedElements.set(element, data);
327        observer.observe(element);
328    }
329
330    function getEmbedUrl(element) {
331        const iframe = element.querySelector('iframe');
332        const video = element.querySelector('video source');
333        const audio = element.querySelector('audio source');
334        const embed = element.querySelector('embed');
335        const object = element.querySelector('object');
336
337        return iframe?.src || video?.src || audio?.src || embed?.src || object?.data ||
338            element.getAttribute('data-url') ||
339            element.getAttribute('data-src') ||
340            element.getAttribute('href') || '';
341    }
342
343    function findAndTrackEmbeds() {
344        document.querySelectorAll('[data-embed-type]:not([data-embed-type] [data-embed-type])')
345            .forEach(prepareElementForTracking);
346    }
347
348
349    function setupMutationObserver() {
350        if (!('MutationObserver' in window)) return;
351        const mo = new MutationObserver((mutations) => {
352            mutations.forEach(m => {
353                m.addedNodes.forEach(n => {
354                    if (n.nodeType !== 1) return;
355                    if (n.getAttribute?.('data-embed-type')) prepareElementForTracking(n);
356                    n.querySelectorAll?.('[data-embed-type]').forEach(prepareElementForTracking);
357                });
358            });
359        });
360        mo.observe(document.body, { childList: true, subtree: true });
361    }
362
363    function sendTrackingData(data) {
364        // Skip tracking if embed type is unknown or empty
365        if (!data.interaction_data?.embed_type ||
366            data.interaction_data.embed_type === 'unknown' ||
367            data.interaction_data.embed_type === '') {
368            return;
369        }
370
371        const trackingData = {
372            ...data,
373            // Only set these if not already provided in data
374            user_id: data.user_id || config.userId,
375            session_id: data.session_id || config.sessionId,
376            page_url: data.page_url || config.pageUrl,
377            post_id: data.post_id || config.postId,
378            // Include original referrer if available
379            original_referrer: embedpress_analytics?.original_referrer || ''
380        };
381
382        fetch(config.restUrl + 'track', {
383            method: 'POST',
384            headers: {
385                'Content-Type': 'application/json',
386                'X-WP-Nonce': embedpress_analytics?.nonce || ''
387            },
388            body: JSON.stringify(trackingData),
389            credentials: 'same-origin'
390        }).catch(err => {
391            if (navigator.sendBeacon) {
392                const blob = new Blob([JSON.stringify(trackingData)], { type: 'application/json' });
393                navigator.sendBeacon(config.restUrl + 'track', blob);
394            }
395        });
396    }
397
398    async function getIPLocationData() {
399        try {
400            const res = await fetch('https://ipinfo.io/json');
401            const data = await res.json();
402            config.ipLocationData = {
403                country: data.country,
404                city: data.city,
405                timezone: data.timezone,
406                source: 'ip'
407            };
408        } catch {
409            return null;
410        }
411    }
412
413    function sendBrowserInfo() {
414        const fingerprint = getBrowserFingerprint();
415        const info = {
416            user_id: config.userId,
417            session_id: config.sessionId,
418            browser_fingerprint: fingerprint,
419            screen_resolution: window.screen.width + 'x' + window.screen.height,
420            language: navigator.language,
421            timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
422            user_agent: navigator.userAgent
423        };
424        getIPLocationData().then(() => {
425            if (config.ipLocationData) {
426                info.country = config.ipLocationData.country;
427                info.city = config.ipLocationData.city;
428            }
429            fetch(config.restUrl + 'browser-info', {
430                method: 'POST',
431                headers: {
432                    'Content-Type': 'application/json',
433                    'X-WP-Nonce': embedpress_analytics?.nonce || ''
434                },
435                body: JSON.stringify(info),
436                credentials: 'same-origin'
437            });
438        });
439    }
440
441    function isTrackingEnabled() {
442        // Check the updated global variable first (updated by React component)
443        if (window.embedpressAnalyticsData?.trackingEnabled !== undefined) {
444            return Boolean(window.embedpressAnalyticsData.trackingEnabled);
445        }
446        // Fallback to original localized value
447        return Boolean(embedpress_analytics?.tracking_enabled);
448    }
449
450    function init() {
451        // Check if tracking is enabled before initializing
452        if (!isTrackingEnabled()) {
453            return;
454        }
455
456
457        // Check if page has embedded content (if provided by server)
458        if (embedpress_analytics?.has_embedded_content === false) {
459            return;
460        }
461
462        findAndTrackEmbeds();
463        setupClickTracking();
464        setupMutationObserver();
465        sendBrowserInfo();
466    }
467
468    if (document.readyState === 'loading') {
469        document.addEventListener('DOMContentLoaded', init);
470    } else {
471        init();
472    }
473})();

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.