PageSourceSearch

https://redesurftank.com.br/themes/wc_surf/wc_js/surf-tracking.js?v=1775522937

js redesurftank.com.br collected 2026-09-24 14:35:06 UTC 12,812 bytes, 376 lines download raw bytes

1/**
2 * SurfTank Attribution Tracking
3 *
4 * Captura fingerprint de hardware + UTM params e envia para API de tracking.
5 * O fingerprint é gerado de forma idêntica ao C# do Launcher para match.
6 */
7(function() {
8    'use strict';
9
10    const TRACKING_API = '/_cdn/widgets/tracking/tracking.ajax.php';
11    const COOKIE_NAME = 'surf_tracking';
12    const COOKIE_DAYS = 3;
13
14    /**
15     * Normaliza nome da GPU para match com C#
16     * Extrai o chip real de wrappers como ANGLE
17     */
18    function normalizeGpuName(gpu) {
19        if (!gpu) return 'unknown';
20
21        let normalized = gpu;
22        const upperOriginal = gpu.toUpperCase();
23
24        // SwiftShader = renderização por software (sem GPU real)
25        if (upperOriginal.includes('SWIFTSHADER')) {
26            return 'SwiftShader';
27        }
28
29        // Extrai GPU real de dentro do ANGLE wrapper
30        // Formato: ANGLE (Vendor, Renderer Details)
31        // Usa lastIndexOf para pegar o ) final e evitar problemas com parênteses aninhados
32        const angleStart = gpu.indexOf('ANGLE');
33        if (angleStart !== -1) {
34            const firstComma = gpu.indexOf(',', angleStart);
35            const lastParen = gpu.lastIndexOf(')');
36            if (firstComma !== -1 && lastParen > firstComma) {
37                normalized = gpu.substring(firstComma + 1, lastParen).trim();
38            }
39        }
40
41        const upper = normalized.toUpperCase();
42
43        // NVIDIA: RTX/GTX + modelo
44        const nvidiaMatch = upper.match(/(RTX|GTX)\s*(\d{3,4})(\s*TI)?/);
45        if (nvidiaMatch) {
46            return nvidiaMatch[0].replace(/\s/g, '');
47        }
48
49        // AMD: RX + modelo (placas discretas)
50        const amdRxMatch = upper.match(/RX\s*(\d{3,4})(\s*XT)?/);
51        if (amdRxMatch) {
52            return amdRxMatch[0].replace(/\s/g, '');
53        }
54
55        // AMD: Radeon integrado (APU) - sem número RX
56        if (upper.includes('RADEON') && !upper.includes('RX')) {
57            // Tenta extrair modelo Vega se existir
58            const vegaMatch = upper.match(/VEGA\s*(\d+)?/);
59            if (vegaMatch) {
60                return `RadeonVega${vegaMatch[1] || ''}`;
61            }
62            return 'RadeonIntegrated';
63        }
64
65        // Intel: extrai modelo HD/UHD/Iris
66        const intelMatch = upper.match(/(HD|UHD|IRIS)\s*(GRAPHICS)?\s*(\d+)?/);
67        if (intelMatch && upper.includes('INTEL')) {
68            const type = intelMatch[1];
69            const model = intelMatch[3] || '';
70            return `Intel${type}${model}`;
71        }
72
73        // Apple: Apple GPU, Apple M1/M2/M3
74        if (upper.includes('APPLE')) {
75            const appleMatch = upper.match(/APPLE\s*(M\d+)?\s*(PRO|MAX|ULTRA)?/);
76            if (appleMatch && appleMatch[1]) {
77                return `Apple${appleMatch[1]}${appleMatch[2] || ''}`;
78            }
79            return 'AppleGPU';
80        }
81
82        // Mali (Android)
83        const maliMatch = upper.match(/MALI[- ]?([A-Z]?\d+)/);
84        if (maliMatch) {
85            return `Mali${maliMatch[1]}`;
86        }
87
88        // Adreno (Android)
89        const adrenoMatch = upper.match(/ADRENO.*?(\d+)/);
90        if (adrenoMatch) {
91            return `Adreno${adrenoMatch[1]}`;
92        }
93
94        // PowerVR (iOS older)
95        const powervrMatch = upper.match(/POWERVR\s*([A-Z0-9]+)/);
96        if (powervrMatch) {
97            return `PowerVR${powervrMatch[1]}`;
98        }
99
100        // Vulkan genérico (quando não consegue identificar GPU)
101        if (upper.includes('VULKAN')) {
102            return 'VulkanGeneric';
103        }
104
105        // Fallback: primeiros 30 chars, remove espaços extras e caracteres problemáticos
106        normalized = normalized.replace(/\s+/g, ' ').replace(/[()]/g, '').trim();
107        return normalized.length > 30 ? normalized.substring(0, 30) : normalized;
108    }
109
110    /**
111     * Obtém nome da GPU via WebGL
112     */
113    function getGpuName() {
114        try {
115            const canvas = document.createElement('canvas');
116            const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
117            if (!gl) return 'unknown';
118
119            const debugInfo = gl.getExtension('WEBGL_debug_renderer_info');
120            if (!debugInfo) return 'unknown';
121
122            return gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL) || 'unknown';
123        } catch (e) {
124            return 'unknown';
125        }
126    }
127
128    /**
129     * Gera fingerprint de hardware idêntico ao C#
130     * Formato: cpuCores|gpu|screenRes|colorDepth|lang
131     */
132    function generateFingerprint() {
133        const cpuCores = navigator.hardwareConcurrency || 4;
134        const gpu = normalizeGpuName(getGpuName());
135        const screenRes = `${screen.width}x${screen.height}`;
136        const colorDepth = screen.colorDepth >= 24 ? 24 : screen.colorDepth;
137        const lang = (navigator.language || 'en').split('-')[0];
138
139        return `${cpuCores}|${gpu}|${screenRes}|${colorDepth}|${lang}`;
140    }
141
142    /**
143     * Extrai parâmetros UTM e click IDs da URL
144     */
145    function getAttributionParams() {
146        const params = new URLSearchParams(window.location.search);
147        return {
148            fbclid: params.get('fbclid'),
149            gclid: params.get('gclid'),
150            utm_source: params.get('utm_source'),
151            utm_medium: params.get('utm_medium'),
152            utm_campaign: params.get('utm_campaign'),
153            utm_content: params.get('utm_content'),
154            utm_term: params.get('utm_term')
155        };
156    }
157
158    /**
159     * Obtém cookie por nome
160     */
161    function getCookie(name) {
162        const value = `; ${document.cookie}`;
163        const parts = value.split(`; ${name}=`);
164        if (parts.length === 2) return parts.pop().split(';').shift();
165        return null;
166    }
167
168    /**
169     * Define cookie
170     */
171    function setCookie(name, value, days) {
172        const expires = new Date(Date.now() + days * 864e5).toUTCString();
173        document.cookie = `${name}=${value}; expires=${expires}; path=/; SameSite=Lax`;
174    }
175
176    /**
177     * Envia request para API de tracking
178     */
179    async function sendToApi(action, data) {
180        try {
181            const response = await fetch(TRACKING_API, {
182                method: 'POST',
183                headers: {
184                    'Content-Type': 'application/json'
185                },
186                body: JSON.stringify({ action: action, ...data })
187            });
188            return await response.json();
189        } catch (e) {
190            console.error('[SurfTracking] API error:', e);
191            return null;
192        }
193    }
194
195    /**
196     * Inicializa tracking - cria sessão se necessário
197     */
198    async function initTracking() {
199        // Se já tem tracking_code, não cria nova sessão
200        let trackingCode = getCookie(COOKIE_NAME);
201        if (trackingCode) {
202            console.log('[SurfTracking] Existing session:', trackingCode.substring(0, 8) + '...');
203            return trackingCode;
204        }
205
206        // Gera fingerprint e captura attribution
207        const fingerprint = generateFingerprint();
208        const attribution = getAttributionParams();
209
210        // Só cria sessão se tiver algum parâmetro de attribution
211        const hasAttribution = Object.values(attribution).some(v => v !== null);
212
213        if (!hasAttribution) {
214            console.log('[SurfTracking] No attribution params, skipping session');
215            return null;
216        }
217
218        console.log('[SurfTracking] Fingerprint:', fingerprint);
219        console.log('[SurfTracking] Attribution:', attribution);
220
221        // Captura cookies do Meta Pixel (_fbp e _fbc) para matching
222        const fbp = getCookie('_fbp');
223        const fbc = getCookie('_fbc');
224
225        // Cria sessão
226        const result = await sendToApi('session', {
227            fingerprint: fingerprint,
228            landing_page: window.location.pathname + window.location.search,
229            referer: document.referrer || null,
230            fbp: fbp || null,
231            fbc: fbc || null,
232            ...attribution
233        });
234
235        if (result && result.success && result.tracking_code) {
236            trackingCode = result.tracking_code;
237            setCookie(COOKIE_NAME, trackingCode, COOKIE_DAYS);
238            console.log('[SurfTracking] Session created:', trackingCode.substring(0, 8) + '...');
239            return trackingCode;
240        }
241
242        console.warn('[SurfTracking] Failed to create session');
243        return null;
244    }
245
246    /**
247     * Marca download iniciado
248     */
249    async function trackDownload(platform) {
250        const resolved = platform || detectPlatform();
251        const trackingCode = getCookie(COOKIE_NAME);
252
253        // GTM dataLayer: empurra download_initiated pra TODOS os cliques (anônimos ou com sessão).
254        // O event_id server-side correspondente é `dl_{tracking_code}_{platform}`.
255        window.dataLayer = window.dataLayer || [];
256        window.dataLayer.push({
257            'event': 'download_initiated',
258            'platform': resolved,
259            'tracking_code': trackingCode || null
260        });
261
262        if (!trackingCode) {
263            console.warn('[SurfTracking] No tracking session for download');
264            return false;
265        }
266
267        const result = await sendToApi('download', {
268            tracking_code: trackingCode,
269            platform: resolved
270        });
271
272        if (result && result.success) {
273            console.log('[SurfTracking] Download tracked:', resolved);
274            return true;
275        }
276        return false;
277    }
278
279    /**
280     * Registra evento customizado
281     */
282    async function trackEvent(eventName, eventData) {
283        const trackingCode = getCookie(COOKIE_NAME);
284        if (!trackingCode) return false;
285
286        const result = await sendToApi('event', {
287            tracking_code: trackingCode,
288            event_name: eventName,
289            event_data: eventData || {}
290        });
291
292        return result && result.success;
293    }
294
295    /**
296     * Detecta plataforma do usuário
297     */
298    function detectPlatform() {
299        const ua = navigator.userAgent.toLowerCase();
300        if (ua.includes('win')) return 'windows';
301        if (ua.includes('mac')) return 'mac';
302        if (ua.includes('iphone') || ua.includes('ipad')) return 'ios';
303        if (ua.includes('android')) return 'android';
304        return 'unknown';
305    }
306
307    /**
308     * Obtém tracking code atual
309     */
310    function getTrackingCode() {
311        return getCookie(COOKIE_NAME);
312    }
313
314    /**
315     * Obtém fingerprint (para debug)
316     */
317    function getFingerprint() {
318        return generateFingerprint();
319    }
320
321    // Expõe API global
322    window.SurfTracking = {
323        init: initTracking,
324        trackDownload: trackDownload,
325        trackEvent: trackEvent,
326        getTrackingCode: getTrackingCode,
327        getFingerprint: getFingerprint,
328        detectPlatform: detectPlatform
329    };
330
331    /**
332     * Bind download tracking via delegação de eventos (funciona com elementos dinâmicos)
333     */
334    function bindDownloadTracking() {
335        document.addEventListener('click', function(e) {
336            const link = e.target.closest('.surf-download');
337            if (link) {
338                const platform = link.getAttribute('data-platform') || detectPlatform();
339
340                // Reescrita SÍNCRONA do href pra Play Store: injeta surf_session no install_referrer
341                // Roda antes do navegador seguir o link, então o referrer chega no app via Google Play
342                if (platform === 'android') {
343                    const href = link.getAttribute('href') || '';
344                    if (href.indexOf('play.google.com') !== -1) {
345                        const trackingCode = getCookie(COOKIE_NAME);
346                        if (trackingCode) {
347                            const referrerValue = encodeURIComponent('surf_session=' + trackingCode);
348                            let newHref;
349                            if (/[?&]referrer=/.test(href)) {
350                                newHref = href.replace(/([?&]referrer=)[^&]*/, '$1' + referrerValue);
351                            } else if (href.indexOf('?') !== -1) {
352                                newHref = href + '&referrer=' + referrerValue;
353                            } else {
354                                newHref = href + '?referrer=' + referrerValue;
355                            }
356                            link.setAttribute('href', newHref);
357                        }
358                    }
359                }
360
361                trackDownload(platform);
362            }
363        });
364    }
365
366    // Auto-init quando DOM estiver pronto
367    if (document.readyState === 'loading') {
368        document.addEventListener('DOMContentLoaded', initTracking);
369    } else {
370        initTracking();
371    }
372
373    // Bind de eventos sempre (delegação no document)
374    bindDownloadTracking();
375
376})();

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.