PageSourceSearch

https://caskson.com/assets/tracker.js

js caskson.com collected 2026-10-07 19:29:56 UTC 6,705 bytes, 214 lines download raw bytes

1/**
2 * CMS Analytics Tracker (追踪脚本)
3 */
4(function() {
5    'use strict';
6    
7    const script = document.currentScript || document.querySelector('script[data-site]');
8    if (!script) return;
9    
10    const SITE_ID = script.getAttribute('data-site');
11    const TRACK_URL = script.getAttribute('data-endpoint') || '/track.php';
12    const HEATMAP = script.getAttribute('data-heatmap') !== 'false';
13    
14    if (!SITE_ID) return;
15
16    // Cookie utilities
17    function setCookie(name, val, days) {
18        const d = new Date();
19        d.setTime(d.getTime() + days * 86400000);
20        document.cookie = name + '=' + val + ';expires=' + d.toUTCString() + ';path=/;SameSite=Lax';
21    }
22    
23    function getCookie(name) {
24        const match = document.cookie.match(new RegExp('(^| )' + name + '=([^;]+)'));
25        return match ? match[2] : null;
26    }
27
28    // UUID generator
29    function uuid() {
30        return 'xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx'.replace(/[xy]/g, c => {
31            const r = Math.random() * 16 | 0;
32            return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16);
33        });
34    }
35
36    // Send data (prefer sendBeacon)
37    function send(action, data) {
38        data.site_id = SITE_ID;
39        const url = TRACK_URL + '?a=' + action;
40        const body = JSON.stringify(data);
41        
42        try {
43            if (navigator.sendBeacon) {
44                navigator.sendBeacon(url, new Blob([body], {type: 'application/json'}));
45            } else {
46                fetch(url, {
47                    method: 'POST',
48                    headers: {'Content-Type': 'application/json'},
49                    body: body,
50                    keepalive: true
51                }).catch(() => {});
52            }
53        } catch (e) {}
54    }
55
56    // Initialize session
57    let VID = getCookie('_cms_vid');
58    if (!VID) {
59        VID = uuid().replace(/-/g, '');
60        setCookie('_cms_vid', VID, 365);
61    }
62    
63    let SID = sessionStorage.getItem('_cms_sid');
64    if (!SID) {
65
66        SID = uuid().replace(/-/g, '');
67        sessionStorage.setItem('_cms_sid', SID);
68    }
69
70    // Pageview tracking
71    const pageData = {
72        vid: VID,
73        sid: SID,
74        url: location.href,
75        ref: document.referrer,
76        post_id: window.__POST_ID__ || 0,
77        lang: navigator.language,
78        sw: screen.width,
79        sh: screen.height
80    };
81    
82    if (document.readyState === 'complete') {
83        send('pv', pageData);
84    } else {
85        window.addEventListener('load', () => send('pv', pageData));
86    }
87
88    // Engagement tracking
89    const startTime = Date.now();
90    let maxScroll = 0;
91    let lastSent = 0;
92
93    function getScrollPercent() {
94        const doc = document.documentElement;
95        const body = document.body;
96        const scrollTop = window.pageYOffset || doc.scrollTop;
97        const scrollHeight = Math.max(doc.scrollHeight, body.scrollHeight);
98        const clientHeight = doc.clientHeight;
99        if (scrollHeight <= clientHeight) return 100;
100        return Math.round((scrollTop + clientHeight) / scrollHeight * 100);
101    }
102
103    function throttle(fn, delay) {
104        let last = 0, timer;
105        return function(...args) {
106            const now = Date.now();
107            if (now - last >= delay) {
108                last = now;
109                fn.apply(this, args);
110            } else {
111                clearTimeout(timer);
112                timer = setTimeout(() => { last = Date.now(); fn.apply(this, args); }, delay);
113            }
114        };
115    }
116
117    window.addEventListener('scroll', throttle(() => {
118        const pct = getScrollPercent();
119        if (pct > maxScroll) maxScroll = pct;
120    }, 500));
121
122    function sendEngagement() {
123        const duration = Math.round((Date.now() - startTime) / 1000);
124        if (duration < 2 || duration - lastSent < 5) return;
125        lastSent = duration;
126        send('eng', {
127            vid: VID,
128            url: location.href,
129            duration: duration,
130            scroll: maxScroll
131        });
132    }
133
134    setInterval(sendEngagement, 15000);
135    window.addEventListener('beforeunload', sendEngagement);
136    document.addEventListener('visibilitychange', () => {
137        if (document.visibilityState === 'hidden') sendEngagement();
138    });
139
140    // Public event tracking API
141    window.cmsTrack = function(eventName, category, data) {
142        send('ev', {
143            vid: VID, sid: SID,
144            name: eventName,
145            category: category || 'general',
146            data: data || {},
147            url: location.href
148        });
149    };
150
151    // Auto-track outbound links & downloads
152    document.addEventListener('click', function(e) {
153        const link = e.target.closest('a');
154        if (!link) return;
155        
156        const href = link.href;
157        if (href && link.hostname && link.hostname !== location.hostname) {
158            send('ev', {
159                vid: VID, sid: SID,
160                name: 'outbound_click',
161                category: 'link',
162                data: {url: href, text: link.innerText.substring(0, 100)},
163                url: location.href
164            });
165        }
166        
167        if (href && /\.(pdf|zip|doc|docx|xls|xlsx|mp3|mp4|rar|7z)$/i.test(href)) {
168            send('ev', {
169                vid: VID, sid: SID,
170                name: 'download',
171                category: 'file',
172                data: {url: href},
173                url: location.href
174            });
175        }
176    });
177
178    // Form submit tracking
179    document.addEventListener('submit', function(e) {
180        const form = e.target;
181        send('ev', {
182            vid: VID, sid: SID,
183            name: 'form_submit',
184            category: 'form',
185            data: {id: form.id || '', action: form.action || ''},
186            url: location.href
187        });
188    });
189
190    // Heatmap tracking
191    if (HEATMAP) {
192        const device = /Mobile|Android|iPhone/.test(navigator.userAgent) ? 'mobile' : 'desktop';
193        
194        document.addEventListener('click', function(e) {
195            const target = e.target;
196            let selector = target.tagName.toLowerCase();
197            if (target.id) selector += '#' + target.id;
198            else if (target.className && typeof target.className === 'string') {
199                selector += '.' + target.className.split(' ').slice(0, 2).join('.');
200            }
201            
202            send('hm', {
203                url: location.href,
204                type: 'click',
205                x: Math.round(e.pageX),
206                y: Math.round(e.pageY),
207                vw: window.innerWidth,
208                vh: window.innerHeight,
209                element: selector.substring(0, 255),
210                device: device
211            });
212        });
213    }
214})();

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.