PageSourceSearch

https://vrachokiposaegina.gr/wp-content/plugins/embedpress/assets/js/analytics-tracker.js?rnd=23998

js vrachokiposaegina.gr collected 2026-10-09 00:23:09 UTC 17,951 bytes, 478 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                try {
231                    document.dispatchEvent(new CustomEvent('embedpress:view', {
232                        detail: { contentId: data.contentId, embedType: data.embedType }
233                    }));
234                } catch (e) { /* IE11 etc. — badge will refresh on next page load */ }
235            }, config.viewDuration);
236        }
237    }
238
239    function trackView(element, data) {
240        // Check if tracking is enabled
241        if (!isTrackingEnabled()) return;
242
243        sendTrackingData({
244            content_id: data.contentId,
245            interaction_type: 'view',
246            user_id: config.userId,
247            session_id: config.sessionId,
248            page_url: config.pageUrl,
249            view_duration: data.viewDuration || config.viewDuration || 0,
250            interaction_data: {
251                embed_type: data.embedType,
252                embed_url: data.embedUrl,
253                viewport_percentage: data.viewportPercentage,
254                view_duration: data.viewDuration || config.viewDuration || 0,
255                browser_fingerprint: getBrowserFingerprint()
256            }
257        });
258    }
259
260    function setupClickTracking() {
261        trackedElements.forEach((data, element) => {
262            // Only track actual user clicks
263            element.addEventListener('click', (event) => {
264                // Make sure this is a real user click
265                if (!event.isTrusted) return;
266
267                const now = Date.now();
268                const last = sessionData.clickedContent.get(data.contentId) || 0;
269
270                // Prevent duplicate clicks within cooldown period
271                if (now - last < config.clickCooldown) return;
272
273                // Check if tracking is enabled
274                if (!isTrackingEnabled()) return;
275
276                sessionData.clickedContent.set(data.contentId, now);
277
278                sendTrackingData({
279                    content_id: data.contentId,
280                    interaction_type: 'click',
281                    user_id: config.userId,
282                    session_id: config.sessionId,
283                    page_url: config.pageUrl,
284                    interaction_data: {
285                        embed_type: data.embedType,
286                        embed_url: data.embedUrl,
287                        browser_fingerprint: getBrowserFingerprint()
288                    }
289                });
290            });
291        });
292    }
293
294    function prepareElementForTracking(element) {
295        if (trackedElements.has(element)) return;
296
297        const embedType = element.getAttribute('data-embed-type');
298        if (!embedType) return;
299
300        // Get a stable content ID based on embed URL or existing attributes
301        let contentId = element.getAttribute('data-embedpress-content') ||
302            element.getAttribute('data-source-id') ||
303            element.getAttribute('data-emid');
304
305        if (!contentId) {
306            // Generate stable ID based on embed URL or iframe src
307            const embedUrl = element.getAttribute('data-embed-url') ||
308                element.querySelector('iframe')?.src ||
309                element.querySelector('embed')?.src ||
310                element.querySelector('object')?.data ||
311                window.location.href;
312
313            // Create a hash-based ID that will be consistent for the same content
314            contentId = 'ep-' + embedType + '-' + btoa(embedUrl).replace(/[^a-zA-Z0-9]/g, '').substring(0, 10);
315        }
316
317        element.setAttribute('data-embedpress-content', contentId);
318
319        const data = {
320            contentId,
321            embedType,
322            embedUrl: getEmbedUrl(element),
323            inViewport: false,
324            viewTimer: null,
325            viewTracked: false,
326            lastImpressionTime: 0,
327            lastViewTime: 0,
328            viewportPercentage: 0
329        };
330
331        trackedElements.set(element, data);
332        observer.observe(element);
333    }
334
335    function getEmbedUrl(element) {
336        const iframe = element.querySelector('iframe');
337        const video = element.querySelector('video source');
338        const audio = element.querySelector('audio source');
339        const embed = element.querySelector('embed');
340        const object = element.querySelector('object');
341
342        return iframe?.src || video?.src || audio?.src || embed?.src || object?.data ||
343            element.getAttribute('data-url') ||
344            element.getAttribute('data-src') ||
345            element.getAttribute('href') || '';
346    }
347
348    function findAndTrackEmbeds() {
349        document.querySelectorAll('[data-embed-type]:not([data-embed-type] [data-embed-type])')
350            .forEach(prepareElementForTracking);
351    }
352
353
354    function setupMutationObserver() {
355        if (!('MutationObserver' in window)) return;
356        const mo = new MutationObserver((mutations) => {
357            mutations.forEach(m => {
358                m.addedNodes.forEach(n => {
359                    if (n.nodeType !== 1) return;
360                    if (n.getAttribute?.('data-embed-type')) prepareElementForTracking(n);
361                    n.querySelectorAll?.('[data-embed-type]').forEach(prepareElementForTracking);
362                });
363            });
364        });
365        mo.observe(document.body, { childList: true, subtree: true });
366    }
367
368    function sendTrackingData(data) {
369        // Skip tracking if embed type is unknown or empty
370        if (!data.interaction_data?.embed_type ||
371            data.interaction_data.embed_type === 'unknown' ||
372            data.interaction_data.embed_type === '') {
373            return;
374        }
375
376        const trackingData = {
377            ...data,
378            // Only set these if not already provided in data
379            user_id: data.user_id || config.userId,
380            session_id: data.session_id || config.sessionId,
381            page_url: data.page_url || config.pageUrl,
382            post_id: data.post_id || config.postId,
383            // Include original referrer if available
384            original_referrer: embedpress_analytics?.original_referrer || ''
385        };
386
387        fetch(config.restUrl + 'track', {
388            method: 'POST',
389            headers: {
390                'Content-Type': 'application/json',
391                'X-WP-Nonce': embedpress_analytics?.nonce || ''
392            },
393            body: JSON.stringify(trackingData),
394            credentials: 'same-origin'
395        }).catch(err => {
396            if (navigator.sendBeacon) {
397                const blob = new Blob([JSON.stringify(trackingData)], { type: 'application/json' });
398                navigator.sendBeacon(config.restUrl + 'track', blob);
399            }
400        });
401    }
402
403    async function getIPLocationData() {
404        try {
405            const res = await fetch('https://ipinfo.io/json');
406            const data = await res.json();
407            config.ipLocationData = {
408                country: data.country,
409                city: data.city,
410                timezone: data.timezone,
411                source: 'ip'
412            };
413        } catch {
414            return null;
415        }
416    }
417
418    function sendBrowserInfo() {
419        const fingerprint = getBrowserFingerprint();
420        const info = {
421            user_id: config.userId,
422            session_id: config.sessionId,
423            browser_fingerprint: fingerprint,
424            screen_resolution: window.screen.width + 'x' + window.screen.height,
425            language: navigator.language,
426            timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
427            user_agent: navigator.userAgent
428        };
429        getIPLocationData().then(() => {
430            if (config.ipLocationData) {
431                info.country = config.ipLocationData.country;
432                info.city = config.ipLocationData.city;
433            }
434            fetch(config.restUrl + 'browser-info', {
435                method: 'POST',
436                headers: {
437                    'Content-Type': 'application/json',
438                    'X-WP-Nonce': embedpress_analytics?.nonce || ''
439                },
440                body: JSON.stringify(info),
441                credentials: 'same-origin'
442            });
443        });
444    }
445
446    function isTrackingEnabled() {
447        // Check the updated global variable first (updated by React component)
448        if (window.embedpressAnalyticsData?.trackingEnabled !== undefined) {
449            return Boolean(window.embedpressAnalyticsData.trackingEnabled);
450        }
451        // Fallback to original localized value
452        return Boolean(embedpress_analytics?.tracking_enabled);
453    }
454
455    function init() {
456        // Check if tracking is enabled before initializing
457        if (!isTrackingEnabled()) {
458            return;
459        }
460
461
462        // Check if page has embedded content (if provided by server)
463        if (embedpress_analytics?.has_embedded_content === false) {
464            return;
465        }
466
467        findAndTrackEmbeds();
468        setupClickTracking();
469        setupMutationObserver();
470        sendBrowserInfo();
471    }
472
473    if (document.readyState === 'loading') {
474        document.addEventListener('DOMContentLoaded', init);
475    } else {
476        init();
477    }
478})();

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.