PageSourceSearch

https://ibccertificados.netlify.app/private-analytics.js

js ibccertificados.netlify.app collected 2026-10-03 08:17:24 UTC 11,510 bytes, 324 lines download raw bytes

1/* ============================================================
2   privateQuizAnalytics — Tracker privado isolado (v1.0)
3   ============================================================
4   REGRAS:
5   - NÃO referencia fbq, gtag, dataLayer, NexoFy, Meta CAPI
6   - NÃO envia dados a terceiros
7   - Falha silenciosamente — nunca quebra o quiz
8   - Desativa com PRIVATE_ANALYTICS_ENABLED = false
9   ============================================================ */
10(function () {
11  'use strict';
12
13  /* ---- Kill switch ---- */
14  if (typeof window.PRIVATE_ANALYTICS_ENABLED === 'undefined') {
15    window.PRIVATE_ANALYTICS_ENABLED = true;
16  }
17
18  /* ---- Configuração ---- */
19  var CFG = {
20    endpoint:       '/api/private-analytics/events',
21    flushInterval:  5000,
22    batchSize:      10,
23    maxRetries:     2,
24    retryDelays:    [1000, 3000],
25    sessionKey:     'private_quiz_session_id',
26    originKey:      'private_analytics_origin',
27    firedKey:       'private_analytics_fired'
28  };
29
30  /* Permite override do endpoint antes do carregamento ou fallback para porta 3100 no localhost */
31  if (window.PRIVATE_ANALYTICS_ENDPOINT) {
32    CFG.endpoint = window.PRIVATE_ANALYTICS_ENDPOINT;
33  } else if (typeof window !== 'undefined' && window.location && (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1') && window.location.port !== '3100' && window.location.port !== '') {
34    CFG.endpoint = 'http://localhost:3100/api/private-analytics/events';
35  }
36
37  /* ---- Eventos one-shot (disparam no máximo uma vez por sessão) ---- */
38  var ONE_SHOT = [
39    'internal_landing_view',
40    'internal_quiz_start',
41    'internal_quiz_complete',
42    'internal_quiz_passed',
43    'internal_quiz_failed',
44    'internal_price_view',
45    'internal_scroll_25',
46    'internal_scroll_50',
47    'internal_scroll_75',
48    'internal_scroll_90',
49    'internal_scroll_100',
50    'internal_thank_you_page_view'
51  ];
52
53  /* ============================================================
54     Helpers — UUID v4
55     ============================================================ */
56  function uuid() {
57    try {
58      if (typeof crypto !== 'undefined' && crypto.getRandomValues) {
59        var b = new Uint8Array(16);
60        crypto.getRandomValues(b);
61        b[6] = (b[6] & 0x0f) | 0x40;
62        b[8] = (b[8] & 0x3f) | 0x80;
63        var h = '', i;
64        for (i = 0; i < 16; i++) {
65          h += ('0' + b[i].toString(16)).slice(-2);
66          if (i === 3 || i === 5 || i === 7 || i === 9) h += '-';
67        }
68        return h;
69      }
70    } catch (e) { /* fallback abaixo */ }
71    return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
72      var r = Math.random() * 16 | 0;
73      return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16);
74    });
75  }
76
77  /* ============================================================
78     Helpers — localStorage com fallback para cookie
79     ============================================================ */
80  function lsGet(k) { try { return localStorage.getItem(k); } catch (e) { return null; } }
81  function lsSet(k, v) { try { localStorage.setItem(k, v); } catch (e) { /* silencioso */ } }
82
83  function cookieGet(n) {
84    var m = document.cookie.match('(?:^|; )' + n + '=([^;]*)');
85    return m ? decodeURIComponent(m[1]) : null;
86  }
87  function cookieSet(n, v, days) {
88    var d = new Date();
89    d.setTime(d.getTime() + (days || 30) * 86400000);
90    try {
91      document.cookie = n + '=' + encodeURIComponent(v) +
92        ';path=/;expires=' + d.toUTCString() + ';SameSite=Lax';
93    } catch (e) { /* silencioso */ }
94  }
95
96  /* ============================================================
97     Session ID — gera UUID, persiste em localStorage + cookie
98     ============================================================ */
99  var _sid = null;
100
101  function getSessionId() {
102    if (_sid) return _sid;
103    _sid = lsGet(CFG.sessionKey);
104    if (!_sid) _sid = cookieGet(CFG.sessionKey);
105    if (!_sid) {
106      _sid = uuid();
107    }
108    lsSet(CFG.sessionKey, _sid);
109    cookieSet(CFG.sessionKey, _sid, 30);
110    return _sid;
111  }
112
113  /* ============================================================
114     Captura de origem (UTMs) — SOMENTE na primeira visita
115     ============================================================ */
116  function captureOrigin() {
117    var existing = lsGet(CFG.originKey);
118    if (existing) {
119      try { return JSON.parse(existing); } catch (e) { /* recriar */ }
120    }
121    var params, origin = {};
122    try { params = new URLSearchParams(window.location.search); } catch (e) { return origin; }
123    ['utm_source', 'utm_medium', 'utm_campaign', 'utm_content', 'utm_term', 'fbclid']
124      .forEach(function (k) { var v = params.get(k); if (v) origin[k] = v; });
125    origin.referrer = document.referrer || '';
126    origin.landing_page = window.location.pathname + window.location.search;
127    lsSet(CFG.originKey, JSON.stringify(origin));
128    return origin;
129  }
130
131  var _origin = captureOrigin();
132
133  /* ============================================================
134     Device type
135     ============================================================ */
136  function deviceType() {
137    var ua = navigator.userAgent || '';
138    if (/Mobi|Android/i.test(ua)) return 'mobile';
139    if (/Tablet|iPad/i.test(ua)) return 'tablet';
140    return 'desktop';
141  }
142
143  /* ============================================================
144     One-shot protection — armazena eventos já disparados
145     ============================================================ */
146  function firedList() {
147    try { var d = lsGet(CFG.firedKey); return d ? JSON.parse(d) : []; } catch (e) { return []; }
148  }
149  function markFired(ev) {
150    try {
151      var f = firedList();
152      if (f.indexOf(ev) === -1) { f.push(ev); lsSet(CFG.firedKey, JSON.stringify(f)); }
153    } catch (e) { /* silencioso */ }
154  }
155  function hasFired(ev) { return firedList().indexOf(ev) !== -1; }
156
157  /* ============================================================
158     Event queue — acumula eventos e envia em batches
159     ============================================================ */
160  var _queue = [];
161  var _flushTimer = null;
162
163  function buildPayload(events) {
164    return {
165      session_id: getSessionId(),
166      events: events,
167      session_data: {
168        utm_source:    _origin.utm_source    || null,
169        utm_medium:    _origin.utm_medium    || null,
170        utm_campaign:  _origin.utm_campaign  || null,
171        utm_content:   _origin.utm_content   || null,
172        utm_term:      _origin.utm_term      || null,
173        fbclid:        _origin.fbclid        || null,
174        referrer:      _origin.referrer      || null,
175        landing_page:  _origin.landing_page  || null,
176        device_type:   deviceType()
177      }
178    };
179  }
180
181  function doSend(body, attempt) {
182    try {
183      fetch(CFG.endpoint, {
184        method: 'POST',
185        headers: { 'Content-Type': 'application/json' },
186        body: body,
187        keepalive: true
188      }).then(function (r) {
189        if (!r.ok && attempt < CFG.maxRetries) {
190          setTimeout(function () { doSend(body, attempt + 1); },
191            CFG.retryDelays[attempt] || 3000);
192        }
193      }).catch(function () {
194        if (attempt < CFG.maxRetries) {
195          setTimeout(function () { doSend(body, attempt + 1); },
196            CFG.retryDelays[attempt] || 3000);
197        }
198        /* else: falha silenciosa */
199      });
200    } catch (e) { /* nunca quebrar o quiz */ }
201  }
202
203  function flush(useBeacon) {
204    try {
205      if (_flushTimer) { clearTimeout(_flushTimer); _flushTimer = null; }
206      if (!_queue.length || !window.PRIVATE_ANALYTICS_ENABLED) return;
207
208      var batch = _queue.splice(0);
209      var body = JSON.stringify(buildPayload(batch));
210
211      if (useBeacon && navigator.sendBeacon) {
212        try {
213          var blob = new Blob([body], { type: 'application/json' });
214          navigator.sendBeacon(CFG.endpoint, blob);
215          return;
216        } catch (e) { /* fallback para fetch */ }
217      }
218      doSend(body, 0);
219    } catch (e) { /* silencioso */ }
220  }
221
222  function scheduleFlush() {
223    if (!_flushTimer) {
224      _flushTimer = setTimeout(function () { flush(false); }, CFG.flushInterval);
225    }
226  }
227
228  /* ============================================================
229     API pública — send()
230     ============================================================ */
231  function send(eventName, properties) {
232    try {
233      if (!window.PRIVATE_ANALYTICS_ENABLED) return;
234
235      /* One-shot check */
236      if (ONE_SHOT.indexOf(eventName) !== -1) {
237        if (hasFired(eventName)) return;
238        markFired(eventName);
239      }
240
241      var ev = {
242        event_id:  uuid(),
243        event:     eventName,
244        timestamp: new Date().toISOString(),
245        page:      window.location.pathname
246      };
247
248      if (properties && typeof properties === 'object') {
249        var k;
250        for (k in properties) {
251          if (properties.hasOwnProperty(k)) ev[k] = properties[k];
252        }
253      }
254
255      _queue.push(ev);
256
257      if (_queue.length >= CFG.batchSize) {
258        flush(false);
259      } else {
260        scheduleFlush();
261      }
262    } catch (e) { /* silencioso */ }
263  }
264
265  /* ============================================================
266     Page lifecycle — flush antes de sair
267     ============================================================ */
268  try {
269    document.addEventListener('visibilitychange', function () {
270      if (document.visibilityState === 'hidden') flush(true);
271    });
272    window.addEventListener('pagehide', function () { flush(true); });
273  } catch (e) { /* silencioso */ }
274
275  /* ============================================================
276     Error tracking
277     ============================================================ */
278  try {
279    var _origOnError = window.onerror;
280    window.onerror = function (msg, src, line, col) {
281      try {
282        if (window.PRIVATE_ANALYTICS_ENABLED) {
283          send('internal_javascript_error', {
284            message: String(msg || '').substring(0, 500),
285            source:  String(src || '').substring(0, 200),
286            line:    line || null,
287            column:  col || null
288          });
289        }
290      } catch (e) { /* silencioso */ }
291      if (typeof _origOnError === 'function') {
292        return _origOnError.apply(this, arguments);
293      }
294    };
295  } catch (e) { /* silencioso */ }
296
297  /* ============================================================
298     Timer helpers — mede durações por etapa
299     ============================================================ */
300  var _timers = {};
301  function startTimer(name) { _timers[name] = Date.now(); }
302  function getTimer(name) { return _timers[name] ? Date.now() - _timers[name] : null; }
303
304  /* ============================================================
305     Exposição da API pública
306     ============================================================ */
307  window.privateQuizAnalytics = {
308    send:         send,
309    flush:        function () { flush(false); },
310    flushBeacon:  function () { flush(true); },
311    getSessionId: getSessionId,
312    getOrigin:    function () { var o = {}; for (var k in _origin) { if (_origin.hasOwnProperty(k)) o[k] = _origin[k]; } return o; },
313    startTimer:   startTimer,
314    getTimer:     getTimer,
315    hasFired:     hasFired,
316    disable:      function () { window.PRIVATE_ANALYTICS_ENABLED = false; },
317    enable:       function () { window.PRIVATE_ANALYTICS_ENABLED = true; },
318    _cfg:         CFG
319  };
320
321  /* Inicializa sessão imediatamente */
322  getSessionId();
323
324})();

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.