PageSourceSearch

https://ipan.nl/tracking.js

js ipan.nl collected 2026-10-05 20:44:07 UTC 5,973 bytes, 156 lines download raw bytes

1
2(function () {
3    'use strict';
4
5    const SCRIPT = document.currentScript;
6    const DOMAIN = SCRIPT?.getAttribute('data-domain') || window.location.hostname;
7    const ENDPOINT = '/api/track';
8    const SESSION_TIMEOUT = 30 * 60 * 1000;
9
10    if (localStorage.getItem('agc_optout') === '1') return;
11
12    function getSessionId() {
13        const stored = sessionStorage.getItem('agc_session');
14        if (stored) {
15            const data = JSON.parse(stored);
16            if (Date.now() - data.lastActivity < SESSION_TIMEOUT) {
17                data.lastActivity = Date.now();
18                sessionStorage.setItem('agc_session', JSON.stringify(data));
19                return data.id;
20            }
21        }
22        const newSession = {
23            id: Math.random().toString(36).substring(2) + Date.now().toString(36),
24            lastActivity: Date.now(),
25            pageViews: 0,
26        };
27        sessionStorage.setItem('agc_session', JSON.stringify(newSession));
28        return newSession.id;
29    }
30
31    function parseUserAgent() {
32        const ua = navigator.userAgent;
33        let browser = 'Unknown';
34        if (ua.includes('Firefox/')) browser = 'Firefox';
35        else if (ua.includes('Edg/')) browser = 'Edge';
36        else if (ua.includes('Chrome/')) browser = 'Chrome';
37        else if (ua.includes('Safari/') && !ua.includes('Chrome')) browser = 'Safari';
38        else if (ua.includes('Opera') || ua.includes('OPR/')) browser = 'Opera';
39
40        let os = 'Unknown';
41        if (ua.includes('Windows')) os = 'Windows';
42        else if (ua.includes('Mac OS')) os = 'macOS';
43        else if (ua.includes('Linux')) os = 'Linux';
44        else if (ua.includes('Android')) os = 'Android';
45        else if (ua.includes('iOS') || ua.includes('iPhone') || ua.includes('iPad')) os = 'iOS';
46
47        let device = 'Desktop';
48        if (/Mobile|Android|iPhone/i.test(ua)) device = 'Mobile';
49        else if (/iPad|Tablet/i.test(ua)) device = 'Tablet';
50
51        return { browser, os, device };
52    }
53
54    function getReferrer() {
55        const ref = document.referrer;
56        if (!ref) return { url: '', type: 'direct' };
57        try {
58            const refUrl = new URL(ref);
59            if (refUrl.hostname === window.location.hostname) return { url: ref, type: 'internal' };
60            const searchEngines = ['google', 'bing', 'yahoo', 'duckduckgo', 'baidu', 'yandex'];
61            for (const se of searchEngines) {
62                if (refUrl.hostname.includes(se)) return { url: ref, type: 'search', engine: se };
63            }
64            const social = ['facebook', 'twitter', 'instagram', 'linkedin', 'pinterest', 'reddit', 'tiktok'];
65            for (const s of social) {
66                if (refUrl.hostname.includes(s)) return { url: ref, type: 'social', platform: s };
67            }
68            return { url: ref, type: 'referral' };
69        } catch (e) {
70            return { url: ref, type: 'referral' };
71        }
72    }
73
74    async function getCountryFromCloudflare() {
75        try {
76            const response = await fetch('https://1.1.1.1/cdn-cgi/trace');
77            const text = await response.text();
78            const match = text.match(/loc=([A-Z]{2})/);
79            return match ? match[1] : 'Unknown';
80        } catch (e) {
81            return getCountryFromTimezone();
82        }
83    }
84
85    function getCountryFromTimezone() {
86        try {
87            const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
88            const tzToCountry = {
89                'Asia/Jakarta': 'ID', 'Asia/Makassar': 'ID', 'Asia/Jayapura': 'ID',
90                'Asia/Singapore': 'SG', 'Asia/Kuala_Lumpur': 'MY', 'Asia/Bangkok': 'TH',
91                'America/New_York': 'US', 'America/Chicago': 'US', 'America/Los_Angeles': 'US',
92                'Europe/London': 'GB', 'Europe/Paris': 'FR', 'Europe/Berlin': 'DE',
93                'Australia/Sydney': 'AU',
94            };
95            return tzToCountry[tz] || 'Unknown';
96        } catch (e) {
97            return 'Unknown';
98        }
99    }
100
101    async function trackPageView() {
102        const session = getSessionId();
103        const { browser, os, device } = parseUserAgent();
104        const referrer = getReferrer();
105        const country = await getCountryFromCloudflare();
106
107        const sessionData = JSON.parse(sessionStorage.getItem('agc_session'));
108        sessionData.pageViews++;
109        sessionStorage.setItem('agc_session', JSON.stringify(sessionData));
110
111        const data = {
112            domain: DOMAIN,
113            path: window.location.pathname,
114            title: document.title,
115            referrer: referrer.url,
116            referrerType: referrer.type,
117            browser: browser,
118            os: os,
119            device: device,
120            screenWidth: window.screen.width,
121            screenHeight: window.screen.height,
122            language: navigator.language,
123            sessionId: session,
124            isNewSession: sessionData.pageViews === 1,
125            timestamp: Date.now(),
126            country: country,
127        };
128
129        if (navigator.sendBeacon) {
130            navigator.sendBeacon(ENDPOINT, JSON.stringify(data));
131        } else {
132            fetch(ENDPOINT, {
133                method: 'POST',
134                body: JSON.stringify(data),
135                headers: { 'Content-Type': 'application/json' },
136                keepalive: true,
137            }).catch(() => { });
138        }
139    }
140
141    if (document.readyState === 'complete') trackPageView();
142    else window.addEventListener('load', trackPageView);
143
144    let startTime = Date.now();
145    window.addEventListener('beforeunload', () => {
146        const timeOnPage = Math.round((Date.now() - startTime) / 1000);
147        const data = {
148            domain: DOMAIN,
149            type: 'duration',
150            sessionId: getSessionId(),
151            duration: timeOnPage,
152            path: window.location.pathname,
153        };
154        if (navigator.sendBeacon) navigator.sendBeacon(ENDPOINT, JSON.stringify(data));
155    });
156})();

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.