(function (root, factory) { if (typeof define === 'function' && define.amd) { define([], factory); } else if (typeof module === 'object' && module.exports) { module.exports = factory(); } else { root.pixelrr = factory(); } }(typeof self !== 'undefined' ? self : this, function () { var CONFIG = { name: "Máxima escala", projectID: "b4cfcd2b-58ce-4457-9b24-ffd49b9fb4f9", projectSecret: "0i59czmk21t", testEventCode: "TEST23850", syncLeadUrl: "https://synclead-navk3l3xwq-uc.a.run.app", trackEventUrl: "https://trackevent-navk3l3xwq-uc.a.run.app", observeThreshold: 0.1, cookieDomain: null }; // Flag global de bot var isBot = false; // --- Modo interno: ?rr_internal=1 ativa dry-run + logs --- (function configureInternalFlags(){ try { var qs = new URLSearchParams(location.search); if (qs.get('rr_internal') === '1') { localStorage.setItem('rr_dryrun', '1'); localStorage.setItem('rr_test', '1'); if (typeof console !== 'undefined') { console.log('🔴 pixelrr internal mode enabled: dry-run + test logs'); } } } catch(e){} })(); var isDryRun = (function(){ try { var qs = new URLSearchParams(location.search); var t = qs.get('test'); if (t && t.toLowerCase() === 'sim') return true; return localStorage.getItem('rr_dryrun') === '1'; } catch(e){ return false; } })(); var isTestMode = (function(){ try { var qs = new URLSearchParams(location.search); if (qs.get('rr_test') === '1') return true; var t = qs.get('test'); if (t && t.toLowerCase() === 'sim') return true; return localStorage.getItem('rr_test') === '1'; } catch(e){ return false; } })(); function log(){ if (isTestMode && typeof console !== 'undefined') console.log.apply(console, arguments); } function error(){ if (isTestMode && typeof console !== 'undefined') console.error.apply(console, arguments); } function uuidv4() { return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c){ var r = Math.random()*16|0, v = c === 'x' ? r : (r&0x3|0x8); return v.toString(16); }); } function getCookie(name) { var value = '; ' + document.cookie; var parts = value.split('; ' + name + '='); if (parts.length === 2) return parts.pop().split(';').shift() || null; return null; } function setCookie(name, value, days, opts) { opts = opts || {}; var domain = opts.domain || CONFIG.cookieDomain || null; var secure = (typeof opts.secure === 'boolean') ? opts.secure : true; var sameSite = (typeof opts.sameSite === 'string') ? opts.sameSite : 'Lax'; var date = new Date(); date.setTime(date.getTime() + (days*24*60*60*1000)); var parts = [ name + '=' + encodeURIComponent(value), 'expires=' + date.toUTCString(), 'path=/' ]; if (domain) parts.push('domain=' + domain); if (secure) parts.push('Secure'); if (sameSite) parts.push('SameSite=' + sameSite); document.cookie = parts.join(';'); } function ensureFBCFromFbclid(fbclid, cookieDomain) { if (!fbclid) return null; var existing = getCookie('_fbc'); if (existing) return existing; var ts = Date.now(); var fbc = "fb.1." + ts + "." + fbclid; setCookie('_fbc', fbc, 90, { domain: cookieDomain, secure: true, sameSite: 'Lax' }); return fbc; } function ensureFBP(cookieDomain) { var existing = getCookie('_fbp'); if (existing) return existing; var ts = Date.now(); var rand = Math.floor(Math.random()*1e10); var fbp = "fb.1." + ts + "." + rand; setCookie('_fbp', fbp, 90, { domain: cookieDomain, secure: true, sameSite: 'Lax' }); return fbp; } function getUrlParams() { var out = {}; try { var s = new URLSearchParams(window.location.search); ['utm_source','utm_medium','utm_campaign','utm_content','utm_term','fbclid'].forEach(function(k){ var v = s.get(k); if (v) { out[k] = v; setCookie(k, v, 30); if (k === 'fbclid') { setCookie('_fbclid', v, 7); ensureFBCFromFbclid(v, CONFIG.cookieDomain); } } }); } catch(e){} return out; } function getStoredLead() { try { var raw = localStorage.getItem('_pixelLeadData'); if (!raw) return null; return JSON.parse(raw); } catch(e){ return null; } } function storeLead(internalID) { try { localStorage.setItem('_pixelLeadData', JSON.stringify({ internalID: internalID, timestamp: Date.now() })); } catch(e){ log('localStorage blocked (WebView mode)'); } } function getInternalID() { var stored = getStoredLead(); var cookieID = getCookie('_leadID'); // Prioridade 1: localStorage if (stored && stored.internalID) return stored.internalID; // Prioridade 2: Cookie if (cookieID) return cookieID; // Prioridade 3: fbclid da URL (WebView Instagram/Facebook) var urlParams = new URLSearchParams(window.location.search); var fbclid = urlParams.get('fbclid') || getCookie('_fbclid'); if (fbclid) { log('🔵 WebView detected - using fbclid as internalID:', fbclid); // Tenta salvar, mas pode falhar no WebView - sem problema try { storeLead(fbclid); setCookie('_leadID', fbclid, 365); } catch(e) { log('Could not persist fbclid (WebView restrictions)'); } return fbclid; } // Prioridade 4: Gera UUID novo var id = uuidv4(); storeLead(id); setCookie('_leadID', id, 365); return id; } function appendQueryParam(url, key, val) { try { var u = new URL(url, location.href); if (u.searchParams.has(key)) { u.searchParams.set(key, val); } else { u.searchParams.append(key, val); } return u.toString(); } catch(e) { return url + (url.indexOf('?')>-1 ? '&' : '?') + encodeURIComponent(key) + '=' + encodeURIComponent(val); } } // --- Detecção simples de bots pelo user-agent --- function isLikelyBot(ua) { if (!ua) return false; ua = ua.toLowerCase(); var botKeywords = [ 'bot', 'spider', 'crawl', 'slurp', 'facebookexternalhit', 'whatsapp', 'telegrambot', 'preview', 'headless', 'phantomjs', 'googlebot', 'inspect', 'crawler' ]; for (var i = 0; i < botKeywords.length; i++) { if (ua.indexOf(botKeywords[i]) !== -1) return true; } return false; } var sentEventKeys = {}; function makeEventKey(name) { return name + '|' + location.pathname; } var state = { internalID: null, leadData: null, initialized: false, readyResolve: null, ready: null }; state.ready = new Promise(function(res){ state.readyResolve = res; }); function collectLeadData() { var urlParams = getUrlParams(); var fbp = getCookie('_fbp') || ensureFBP(CONFIG.cookieDomain); var fbc = getCookie('_fbc'); var fbclid = urlParams.fbclid || getCookie('_fbclid'); var leadData = { projectID: CONFIG.projectID, project_secret: CONFIG.projectSecret, internalID: state.internalID, user_data: { client_user_agent: navigator.userAgent, fbclid: fbclid || undefined, fbc: fbc || undefined, fbp: fbp || undefined }, custom_data: { utm_source: urlParams.utm_source || getCookie('utm_source') || undefined, utm_medium: urlParams.utm_medium || getCookie('utm_medium') || undefined, utm_campaign: urlParams.utm_campaign || getCookie('utm_campaign') || undefined, utm_content: urlParams.utm_content || getCookie('utm_content') || undefined, utm_term: urlParams.utm_term || getCookie('utm_term') || undefined } }; state.leadData = leadData; log('Lead collected:', leadData); } async function syncLead() { if (!state.leadData) return; if (isBot) { log('[BOT] syncLead SKIPPED', state.leadData); return { status: 'bot-skip', skipped: 'syncLead' }; } if (isDryRun) { log('[DRY-RUN] syncLead SKIPPED', state.leadData); return { status: 'dry-run', skipped: 'syncLead' }; } try { log('Syncing lead...', state.leadData); var resp = await fetch(CONFIG.syncLeadUrl, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(state.leadData) }); var json = await resp.json().catch(function(){ return {}; }); log('Sync response:', json); return json; } catch(e) { error('Sync error:', e); } } async function trackEvent(eventName, extraCustomData) { if (!state.leadData) { error('No leadData for trackEvent'); return; } if (isBot) { log('[BOT] trackEvent SKIPPED:', eventName); return { status: 'bot-skip', event_name: eventName }; } var key = makeEventKey(eventName); if (sentEventKeys[key]) { log('Deduped event', eventName); return; } sentEventKeys[key] = Date.now(); var payload = { projectID: state.leadData.projectID, project_secret: state.leadData.project_secret, internalID: state.leadData.internalID, user_data: state.leadData.user_data, custom_data: Object.assign({}, state.leadData.custom_data, { event_source_url: window.location.href }, extraCustomData || {}), event_name: eventName, event_id: uuidv4() }; if (isTestMode) payload.test_event_code = CONFIG.testEventCode; if (isDryRun) { log('[DRY-RUN] trackEvent SKIPPED:', payload); return { status: 'dry-run', skipped: 'trackEvent', event_name: eventName }; } try { log('Sending event:', payload); var resp = await fetch(CONFIG.trackEventUrl, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }); var json = await resp.json().catch(function(){ return {}; }); log('Event response:', json); return json; } catch(e) { error('Track error:', e); } } var api = { ready: state.ready, getInternalID: function(){ return state.internalID; }, setTestMode: function(enabled){ try { localStorage.setItem('rr_test', enabled ? '1' : '0'); } catch(e){} log('Test mode set:', enabled); }, setDryRun: function(enabled){ try { localStorage.setItem('rr_dryrun', enabled ? '1' : '0'); } catch(e){} log('Dry-run set:', enabled); }, track: function(name, customData){ return trackEvent(name, customData); }, updateLead: async function(form) { if (!state.leadData) { error('No leadData to update'); return; } var first = '', last = ''; if (form.name && typeof form.name === 'string') { var parts = form.name.trim().split(/\s+/); first = parts[0] || ''; last = parts.slice(1).join(' ') || ''; } state.leadData.user_data = Object.assign({}, state.leadData.user_data, { first_name: first || undefined, last_name: last || undefined, email: form.email || undefined, phone: form.phone || undefined }); state.leadData.user_misc_data = { possuiRestricao: form.negativeRecord === 'yes', scoreAcima400: form.score === 'above400', possuiEntrada: form.downPayment === 'yes', valorEntrada: form.downPaymentValue || undefined, rendaCompativel: form.income === 'yes' }; await syncLead(); } }; function bindClickEvents() { document.addEventListener('click', function(ev){ var el = ev.target; if (!el) return; while (el && el !== document) { var evtName = el.getAttribute && el.getAttribute('data-rr-event'); if (evtName) { var href = el.getAttribute('href'); if (href) ev.preventDefault(); api.track(evtName).finally(function(){ if (href) window.open(href, el.getAttribute('target') || '_self'); }); break; } var qparam = el.getAttribute && el.getAttribute('data-rr-append-internalid'); if (qparam && el.tagName === 'A') { var curr = el.getAttribute('href') || ''; var updated = appendQueryParam(curr, qparam, state.internalID); if (updated !== curr) el.setAttribute('href', updated); break; } el = el.parentNode; } }, true); } function bindViewContentObserver() { if (!('IntersectionObserver' in window)) return; var nodes = document.querySelectorAll('[data-rr-observe="viewcontent"]'); if (!nodes || nodes.length === 0) return; var fired = false; var io = new IntersectionObserver(function(entries){ entries.forEach(function(entry){ if (fired) return; if (entry.isIntersecting && entry.intersectionRatio >= CONFIG.observeThreshold) { fired = true; api.track('ViewContent'); io.disconnect(); } }); }, { threshold: CONFIG.observeThreshold }); nodes.forEach(function(n){ io.observe(n); }); } async function init() { if (state.initialized) return; state.initialized = true; // Detecta bot logo no início try { var ua = navigator.userAgent || ''; if (isLikelyBot(ua)) { isBot = true; if (typeof console !== 'undefined') { console.log('🤖 pixelrr: bot detected, skipping sync & tracking'); } } } catch(e){} state.internalID = getInternalID(); collectLeadData(); // Bots não sincronizam nem disparam PageView if (!isBot) { await syncLead(); await trackEvent('PageView'); } bindClickEvents(); bindViewContentObserver(); if (state.readyResolve) state.readyResolve(true); log('pixelrr initialized', { dryRun: isDryRun, testMode: isTestMode, isBot: isBot }); } if (document.readyState === 'complete' || document.readyState === 'interactive') { setTimeout(init, 0); } else { document.addEventListener('DOMContentLoaded', init); } return api; }));