cdp-edge 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +367 -0
- package/bin/cdp-edge.js +61 -0
- package/contracts/api-versions.json +368 -0
- package/dist/commands/analyze.js +52 -0
- package/dist/commands/infra.js +54 -0
- package/dist/commands/install.js +168 -0
- package/dist/commands/server.js +174 -0
- package/dist/commands/setup.js +123 -0
- package/dist/commands/validate.js +84 -0
- package/dist/index.js +12 -0
- package/docs/CI-CD-SETUP.md +217 -0
- package/docs/PixelBuilder-Documentacao-Completa (2).docx +0 -0
- package/docs/events-reference.md +359 -0
- package/docs/installation.md +155 -0
- package/docs/quick-start.md +185 -0
- package/docs/sdk-reference.md +371 -0
- package/docs/whatsapp-ctwa.md +209 -0
- package/extracted-skill/tracking-events-generator/INDEX.md +94 -0
- package/extracted-skill/tracking-events-generator/INSTALACAO-CDPEDGE.md +58 -0
- package/extracted-skill/tracking-events-generator/INTEGRACAO-COMPLETA.md +594 -0
- package/extracted-skill/tracking-events-generator/MELHORIAS-IMPLEMENTADAS.md +412 -0
- package/extracted-skill/tracking-events-generator/Premium-Tracking-Intelligence-Resumo.md +333 -0
- package/extracted-skill/tracking-events-generator/SKILL.md +257 -0
- package/extracted-skill/tracking-events-generator/advanced-matching.js +364 -0
- package/extracted-skill/tracking-events-generator/agents/ab-testing-agent.md +54 -0
- package/extracted-skill/tracking-events-generator/agents/attribution-agent.md +1304 -0
- package/extracted-skill/tracking-events-generator/agents/bing-agent.md +76 -0
- package/extracted-skill/tracking-events-generator/agents/browser-tracking.md +264 -0
- package/extracted-skill/tracking-events-generator/agents/code-guardian-agent.md +149 -0
- package/extracted-skill/tracking-events-generator/agents/compliance-agent.md +2077 -0
- package/extracted-skill/tracking-events-generator/agents/crm-integration-agent.md +1419 -0
- package/extracted-skill/tracking-events-generator/agents/dashboard-agent.md +456 -0
- package/extracted-skill/tracking-events-generator/agents/database-agent.md +667 -0
- package/extracted-skill/tracking-events-generator/agents/debug-agent.md +1455 -0
- package/extracted-skill/tracking-events-generator/agents/domain-setup-agent.md +224 -0
- package/extracted-skill/tracking-events-generator/agents/email-agent.md +61 -0
- package/extracted-skill/tracking-events-generator/agents/fingerprint-agent.md +52 -0
- package/extracted-skill/tracking-events-generator/agents/google-agent.md +109 -0
- package/extracted-skill/tracking-events-generator/agents/intelligence-agent.md +365 -0
- package/extracted-skill/tracking-events-generator/agents/intelligence-scheduling.md +643 -0
- package/extracted-skill/tracking-events-generator/agents/linkedin-agent.md +62 -0
- package/extracted-skill/tracking-events-generator/agents/localization-agent.md +55 -0
- package/extracted-skill/tracking-events-generator/agents/ltv-predictor-agent.md +59 -0
- package/extracted-skill/tracking-events-generator/agents/master-feedback-loop.md +900 -0
- package/extracted-skill/tracking-events-generator/agents/master-orchestrator.md +1922 -0
- package/extracted-skill/tracking-events-generator/agents/memory-agent.json +109 -0
- package/extracted-skill/tracking-events-generator/agents/memory-agent.md +703 -0
- package/extracted-skill/tracking-events-generator/agents/meta-agent.md +110 -0
- package/extracted-skill/tracking-events-generator/agents/page-analyzer.md +255 -0
- package/extracted-skill/tracking-events-generator/agents/performance-agent.md +1157 -0
- package/extracted-skill/tracking-events-generator/agents/performance-optimization-agent.md +1432 -0
- package/extracted-skill/tracking-events-generator/agents/pinterest-agent.md +310 -0
- package/extracted-skill/tracking-events-generator/agents/premium-tracking-intelligence-agent.md +849 -0
- package/extracted-skill/tracking-events-generator/agents/r2-setup-agent.md +250 -0
- package/extracted-skill/tracking-events-generator/agents/reddit-agent.md +313 -0
- package/extracted-skill/tracking-events-generator/agents/security-enterprise-agent.md +1752 -0
- package/extracted-skill/tracking-events-generator/agents/server-tracking.md +1188 -0
- package/extracted-skill/tracking-events-generator/agents/spotify-agent.md +383 -0
- package/extracted-skill/tracking-events-generator/agents/tiktok-agent.md +111 -0
- package/extracted-skill/tracking-events-generator/agents/tracking-plan-agent.md +364 -0
- package/extracted-skill/tracking-events-generator/agents/validator-agent.md +267 -0
- package/extracted-skill/tracking-events-generator/agents/webhook-agent.md +69 -0
- package/extracted-skill/tracking-events-generator/agents/whatsapp-agent.md +76 -0
- package/extracted-skill/tracking-events-generator/agents/whatsapp-ctwa-setup-agent.md +699 -0
- package/extracted-skill/tracking-events-generator/agents/youtube-agent.md +422 -0
- package/extracted-skill/tracking-events-generator/anti-blocking.js +285 -0
- package/extracted-skill/tracking-events-generator/cdpTrack.js +641 -0
- package/extracted-skill/tracking-events-generator/contracts/api-versions.json +368 -0
- package/extracted-skill/tracking-events-generator/docs/guia-cloudflare-iniciante.md +107 -0
- package/extracted-skill/tracking-events-generator/engagement-scoring.js +226 -0
- package/extracted-skill/tracking-events-generator/evals/evals.json +235 -0
- package/extracted-skill/tracking-events-generator/integration-test.js +497 -0
- package/extracted-skill/tracking-events-generator/knowledge-base.md +2894 -0
- package/extracted-skill/tracking-events-generator/micro-events.js +992 -0
- package/extracted-skill/tracking-events-generator/models/captura-de-lead.md +78 -0
- package/extracted-skill/tracking-events-generator/models/captura-lead-evento-externo.md +99 -0
- package/extracted-skill/tracking-events-generator/models/checkout-proprio.md +111 -0
- package/extracted-skill/tracking-events-generator/models/multi-step-checkout.md +672 -0
- package/extracted-skill/tracking-events-generator/models/pagina-obrigado.md +55 -0
- package/extracted-skill/tracking-events-generator/models/pinterest/conversions-api-template.js +144 -0
- package/extracted-skill/tracking-events-generator/models/pinterest/event-mappings.json +48 -0
- package/extracted-skill/tracking-events-generator/models/pinterest/tag-template.js +28 -0
- package/extracted-skill/tracking-events-generator/models/quiz-funnel.md +68 -0
- package/extracted-skill/tracking-events-generator/models/reddit/conversions-api-template.js +205 -0
- package/extracted-skill/tracking-events-generator/models/reddit/event-mappings.json +56 -0
- package/extracted-skill/tracking-events-generator/models/reddit/pixel-template.js +19 -0
- package/extracted-skill/tracking-events-generator/models/scenarios/behavior-engine.js +425 -0
- package/extracted-skill/tracking-events-generator/models/scenarios/real-estate-logic.md +50 -0
- package/extracted-skill/tracking-events-generator/models/scenarios/sales-page-logic.md +50 -0
- package/extracted-skill/tracking-events-generator/models/trafego-direto.md +582 -0
- package/extracted-skill/tracking-events-generator/models/webinar-registration.md +63 -0
- package/extracted-skill/tracking-events-generator/tracking.config.js +46 -0
- package/extracted-skill/tracking-events-generator/walkthrough.md +26 -0
- package/package.json +75 -0
- package/server-edge-tracker/INSTALAR.md +328 -0
- package/server-edge-tracker/migrate-new-db.sql +137 -0
- package/server-edge-tracker/migrate-v2.sql +16 -0
- package/server-edge-tracker/migrate-v3.sql +6 -0
- package/server-edge-tracker/migrate-v4.sql +18 -0
- package/server-edge-tracker/migrate-v5.sql +17 -0
- package/server-edge-tracker/migrate-v6.sql +24 -0
- package/server-edge-tracker/migrate.sql +111 -0
- package/server-edge-tracker/schema.sql +265 -0
- package/server-edge-tracker/worker.js +2574 -0
- package/server-edge-tracker/wrangler.toml +85 -0
- package/templates/afiliado-sem-landing.md +312 -0
- package/templates/captura-de-lead.md +78 -0
- package/templates/captura-lead-evento-externo.md +99 -0
- package/templates/checkout-proprio.md +111 -0
- package/templates/install/.claude/commands/cdp.md +1 -0
- package/templates/install/CLAUDE.md +65 -0
- package/templates/linkedin/tag-template.js +46 -0
- package/templates/multi-step-checkout.md +673 -0
- package/templates/pagina-obrigado.md +55 -0
- package/templates/pinterest/conversions-api-template.js +144 -0
- package/templates/pinterest/event-mappings.json +48 -0
- package/templates/pinterest/tag-template.js +28 -0
- package/templates/quiz-funnel.md +68 -0
- package/templates/reddit/conversions-api-template.js +205 -0
- package/templates/reddit/event-mappings.json +56 -0
- package/templates/reddit/pixel-template.js +46 -0
- package/templates/scenarios/behavior-engine.js +402 -0
- package/templates/scenarios/real-estate-logic.md +50 -0
- package/templates/scenarios/sales-page-logic.md +50 -0
- package/templates/spotify/pixel-template.js +46 -0
- package/templates/trafego-direto.md +582 -0
- package/templates/vsl-page.md +292 -0
- package/templates/webinar-registration.md +63 -0
|
@@ -0,0 +1,641 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CDP EDGE TRACKING SDK (Quantum Tier)
|
|
3
|
+
* @version 1.0.0
|
|
4
|
+
*
|
|
5
|
+
* SDK principal de tracking do CDP Edge.
|
|
6
|
+
* Comunica diretamente com o Cloudflare Worker.
|
|
7
|
+
* Suporta micro-events, engagement scoring e tracking premium.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
// ── Imports ────────────────────────────────────────────────
|
|
11
|
+
import CONFIG from './tracking.config.js';
|
|
12
|
+
import { initMicroEvents, trackScroll, trackTimeOnPage, trackVideo, trackCTAHover } from './micro-events.js';
|
|
13
|
+
import {
|
|
14
|
+
calculateEngagementScore,
|
|
15
|
+
updateEngagementState,
|
|
16
|
+
engagementState
|
|
17
|
+
} from './engagement-scoring.js';
|
|
18
|
+
import {
|
|
19
|
+
extractFormPII,
|
|
20
|
+
capturePII,
|
|
21
|
+
isValidPII
|
|
22
|
+
} from './advanced-matching.js';
|
|
23
|
+
import {
|
|
24
|
+
sendWithRetry,
|
|
25
|
+
initAntiBlocking,
|
|
26
|
+
ANTI_BLOCKING_CONFIG
|
|
27
|
+
} from './anti-blocking.js';
|
|
28
|
+
|
|
29
|
+
// ── Guards — segurança em SSR e SDK não carregado ──
|
|
30
|
+
const isBrowser = typeof window !== 'undefined';
|
|
31
|
+
const has = (fn) => isBrowser && typeof window[fn] === 'function';
|
|
32
|
+
|
|
33
|
+
// ── Google Consent Mode v2 ────────────────────────────────────────────────────
|
|
34
|
+
// Inicializa com todos os sinais negados por padrão (LGPD/GDPR compliance).
|
|
35
|
+
// Sites devem chamar cdpTrack.updateConsent({ analytics: true, ads: true })
|
|
36
|
+
// após o usuário aceitar o banner de cookies.
|
|
37
|
+
const CONSENT_KEY = '_cdp_consent';
|
|
38
|
+
|
|
39
|
+
const _defaultConsent = {
|
|
40
|
+
ad_storage: 'denied',
|
|
41
|
+
analytics_storage: 'denied',
|
|
42
|
+
ad_user_data: 'denied',
|
|
43
|
+
ad_personalization: 'denied',
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
// Lê consentimento salvo (boolean → objeto de sinais)
|
|
47
|
+
function _readStoredConsent() {
|
|
48
|
+
if (!isBrowser) return null;
|
|
49
|
+
try {
|
|
50
|
+
const raw = localStorage.getItem(CONSENT_KEY);
|
|
51
|
+
return raw ? JSON.parse(raw) : null;
|
|
52
|
+
} catch { return null; }
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Inicializa o Google Consent Mode v2 com defaults negados.
|
|
57
|
+
* Chamado automaticamente em init() ANTES de qualquer gtag disparo.
|
|
58
|
+
* Também aplica consentimento salvo previamente (sessões recorrentes).
|
|
59
|
+
*/
|
|
60
|
+
function initConsentMode() {
|
|
61
|
+
if (!isBrowser) return;
|
|
62
|
+
|
|
63
|
+
// Se consent mode desabilitado na config, não inicializa
|
|
64
|
+
if (CONFIG.consent?.defaultDenied === false) return;
|
|
65
|
+
|
|
66
|
+
// gtag helper — funciona mesmo que gtag não tenha sido carregado ainda
|
|
67
|
+
window.dataLayer = window.dataLayer || [];
|
|
68
|
+
function gtag() { window.dataLayer.push(arguments); }
|
|
69
|
+
|
|
70
|
+
const waitForUpdate = CONFIG.consent?.waitForUpdate ?? 500;
|
|
71
|
+
|
|
72
|
+
// Consent padrão: tudo negado + url_passthrough para preservar atribuição
|
|
73
|
+
gtag('consent', 'default', {
|
|
74
|
+
..._defaultConsent,
|
|
75
|
+
wait_for_update: waitForUpdate,
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
// url_passthrough: preserva gclid/fbclid na URL mesmo sem consent
|
|
79
|
+
if (CONFIG.consent?.urlPassthrough !== false) {
|
|
80
|
+
gtag('set', 'url_passthrough', true);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Aplicar consentimento salvo (usuário que já aceitou anteriormente)
|
|
84
|
+
const stored = _readStoredConsent();
|
|
85
|
+
if (stored) {
|
|
86
|
+
gtag('consent', 'update', stored);
|
|
87
|
+
if (CONFIG.debug) console.log('✅ Consent Mode: consentimento anterior restaurado');
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Atualiza o Consent Mode quando o usuário aceita/rejeita o banner.
|
|
93
|
+
*
|
|
94
|
+
* @param {object} params - { analytics?: boolean, ads?: boolean }
|
|
95
|
+
* @example
|
|
96
|
+
* // Usuário aceitou tudo
|
|
97
|
+
* cdpTrack.updateConsent({ analytics: true, ads: true });
|
|
98
|
+
*
|
|
99
|
+
* // Usuário aceitou só analytics, rejeitou anúncios
|
|
100
|
+
* cdpTrack.updateConsent({ analytics: true, ads: false });
|
|
101
|
+
*/
|
|
102
|
+
export function updateConsent(params = {}) {
|
|
103
|
+
if (!isBrowser) return;
|
|
104
|
+
|
|
105
|
+
window.dataLayer = window.dataLayer || [];
|
|
106
|
+
function gtag() { window.dataLayer.push(arguments); }
|
|
107
|
+
|
|
108
|
+
const analyticsGranted = params.analytics === true ? 'granted' : 'denied';
|
|
109
|
+
const adsGranted = params.ads === true ? 'granted' : 'denied';
|
|
110
|
+
|
|
111
|
+
const consentUpdate = {
|
|
112
|
+
analytics_storage: analyticsGranted,
|
|
113
|
+
ad_storage: adsGranted,
|
|
114
|
+
ad_user_data: adsGranted,
|
|
115
|
+
ad_personalization: adsGranted,
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
gtag('consent', 'update', consentUpdate);
|
|
119
|
+
|
|
120
|
+
// Persiste para sessões futuras
|
|
121
|
+
try {
|
|
122
|
+
localStorage.setItem(CONSENT_KEY, JSON.stringify(consentUpdate));
|
|
123
|
+
} catch { /* storage bloqueado (incognito, etc.) */ }
|
|
124
|
+
|
|
125
|
+
console.log('✅ Consent Mode atualizado:', consentUpdate);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// ── Captura de parâmetros de URL no carregamento da página ──
|
|
129
|
+
// Cada plataforma injeta seu próprio Click ID na URL quando o usuário clica em um anúncio.
|
|
130
|
+
// Guardamos todos em memória para enviar ao servidor e garantir atribuição correta.
|
|
131
|
+
const _urlParams = isBrowser ? new URLSearchParams(window.location.search) : new URLSearchParams();
|
|
132
|
+
|
|
133
|
+
// Meta
|
|
134
|
+
const _fbclid = _urlParams.get('fbclid') || ''; // Meta Ads click ID → gera cookie _fbc
|
|
135
|
+
|
|
136
|
+
// Google Ads (três variantes por tipo de match/privacy)
|
|
137
|
+
const _gclid = _urlParams.get('gclid') || ''; // Google Ads standard click ID → gera cookie _gcl_aw
|
|
138
|
+
const _wbraid = _urlParams.get('wbraid') || ''; // Google Ads (iOS, web-to-app, privacy preserving)
|
|
139
|
+
const _gbraid = _urlParams.get('gbraid') || ''; // Google Ads (app campaigns, privacy preserving)
|
|
140
|
+
|
|
141
|
+
// TikTok
|
|
142
|
+
const _ttclid = _urlParams.get('ttclid') || ''; // TikTok Ads click ID → complementa cookie _ttp
|
|
143
|
+
|
|
144
|
+
// UTMs — rastreamento interno de origem de tráfego (independente da atribuição das plataformas)
|
|
145
|
+
const _utms = {
|
|
146
|
+
utm_source: _urlParams.get('utm_source') || '',
|
|
147
|
+
utm_medium: _urlParams.get('utm_medium') || '',
|
|
148
|
+
utm_campaign: _urlParams.get('utm_campaign') || '',
|
|
149
|
+
utm_content: _urlParams.get('utm_content') || '',
|
|
150
|
+
utm_term: _urlParams.get('utm_term') || '',
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
// ── User ID persistente (first-party cookie) ──────────────────
|
|
154
|
+
// Identifica o mesmo usuário entre sessões diferentes, mesmo sem login.
|
|
155
|
+
// Resolve o problema de cookies de terceiros bloqueados por ad-blockers.
|
|
156
|
+
const _getUserId = () => {
|
|
157
|
+
if (!isBrowser) return '';
|
|
158
|
+
const KEY = '_cdp_uid';
|
|
159
|
+
let uid = document.cookie.match(new RegExp(`${KEY}=([^;]+)`))?.[1];
|
|
160
|
+
if (!uid) {
|
|
161
|
+
uid = `${Date.now()}_${Math.random().toString(36).slice(2, 11)}`;
|
|
162
|
+
// Cookie first-party de 365 dias — não bloqueado por navegadores
|
|
163
|
+
document.cookie = `${KEY}=${uid}; max-age=${60*60*24*365}; path=/; SameSite=Lax`;
|
|
164
|
+
}
|
|
165
|
+
return uid;
|
|
166
|
+
};
|
|
167
|
+
const _userId = isBrowser ? _getUserId() : '';
|
|
168
|
+
|
|
169
|
+
// ── Session ID (cross-tab) ─────────────────────────────────────
|
|
170
|
+
const _getSessionId = () => {
|
|
171
|
+
if (!isBrowser) return '';
|
|
172
|
+
const sessionId = 'cdp_session';
|
|
173
|
+
let sid = sessionStorage.getItem(sessionId);
|
|
174
|
+
if (!sid) {
|
|
175
|
+
sid = `${Date.now()}_${Math.random().toString(36).slice(2, 11)}`;
|
|
176
|
+
sessionStorage.setItem(sessionId, sid);
|
|
177
|
+
}
|
|
178
|
+
return sid;
|
|
179
|
+
};
|
|
180
|
+
const _sessionId = isBrowser ? _getSessionId() : '';
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* getUTMs() — retorna os UTMs capturados na chegada do usuário
|
|
184
|
+
* Útil para: salvar no CRM, incluir no payload do servidor, rastreamento interno.
|
|
185
|
+
* Atribuição própria independente da janela das plataformas (último clique registrado).
|
|
186
|
+
*/
|
|
187
|
+
export const getUTMs = () => ({ ..._utms });
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* getUserId() — retorna o User ID persistente gerado como cookie first-party.
|
|
191
|
+
* Identificador de usuário persistente nativo.
|
|
192
|
+
*/
|
|
193
|
+
export const getUserId = () => _userId;
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* getSessionId() — retorna o Session ID (cross-tab).
|
|
197
|
+
*/
|
|
198
|
+
export const getSessionId = () => _sessionId;
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* generateId() — gera um ID único de evento
|
|
202
|
+
* Baseado em timestamp + random
|
|
203
|
+
*/
|
|
204
|
+
export const generateId = () => {
|
|
205
|
+
return `${Date.now()}_${Math.random().toString(36).slice(2, 11)}`;
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
// ── Captura de Click IDs das plataformas ──────────────────────
|
|
209
|
+
const _getClickIDs = () => {
|
|
210
|
+
return {
|
|
211
|
+
fbp: document.cookie.match(/_fbp=([^;]+)/)?.[1],
|
|
212
|
+
fbc: document.cookie.match(/_fbc=([^;]+)/)?.[1],
|
|
213
|
+
gclid: _gclid,
|
|
214
|
+
gbraid: _gbraid,
|
|
215
|
+
wbraid: _wbraid,
|
|
216
|
+
ttclid: _ttclid,
|
|
217
|
+
rclid: _urlParams.get('rclid')
|
|
218
|
+
};
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* passCheckoutParams(options?) — adiciona UTMs + User ID nos links de checkout externo.
|
|
223
|
+
*
|
|
224
|
+
* Problema: quando o usuário clica em um link de Hotmart, Kiwify, Eduzz etc., os UTMs e
|
|
225
|
+
* o user ID são perdidos — a plataforma não sabe de onde veio o comprador.
|
|
226
|
+
*
|
|
227
|
+
* Solução: interceptar os links de checkout e adicionar os parâmetros de rastreamento
|
|
228
|
+
* na URL antes do clique, para que a plataforma receba e registre a origem.
|
|
229
|
+
*
|
|
230
|
+
* Plataformas suportadas e seus parâmetros:
|
|
231
|
+
* hotmart → xcod (user ID) + sck (UTMs pipe-separados: src|med|camp|con|term)
|
|
232
|
+
* kiwify → src (utm_source)
|
|
233
|
+
* eduzz → src (utm_source)
|
|
234
|
+
* monetizze → src (utm_source)
|
|
235
|
+
* cartpanda → utm_* passthrough direto
|
|
236
|
+
* custom → qualquer domínio via opção `domains`
|
|
237
|
+
*
|
|
238
|
+
* Uso:
|
|
239
|
+
* passCheckoutParams() // detecta automaticamente
|
|
240
|
+
* passCheckoutParams({ platforms: ['hotmart'] }) // só Hotmart
|
|
241
|
+
* passCheckoutParams({ domains: ['meusite.com/checkout'] }) // domínio customizado
|
|
242
|
+
*/
|
|
243
|
+
export const passCheckoutParams = (options = {}) => {
|
|
244
|
+
if (!isBrowser) return;
|
|
245
|
+
|
|
246
|
+
const {
|
|
247
|
+
platforms = ['hotmart', 'kiwify', 'eduzz', 'monetizze', 'cartpanda', 'ticto'],
|
|
248
|
+
domains = []
|
|
249
|
+
} = options;
|
|
250
|
+
|
|
251
|
+
const utms = getUTMs();
|
|
252
|
+
const userId = getUserId();
|
|
253
|
+
|
|
254
|
+
// Mapa de domínios por plataforma
|
|
255
|
+
const PLATFORM_DOMAINS = {
|
|
256
|
+
hotmart: ['hotmart.com', 'pay.hotmart.com', 'payment.hotmart.com'],
|
|
257
|
+
kiwify: ['kiwify.com.br', 'checkout.kiwify.com.br'],
|
|
258
|
+
eduzz: ['eduzz.com', 'sun.eduzz.com'],
|
|
259
|
+
monetizze: ['monetizze.com.br'],
|
|
260
|
+
cartpanda: ['cartpanda.com', 'pay.cartpanda.com'],
|
|
261
|
+
ticto: ['ticto.app', 'pay.ticto.app', 'checkout.ticto.app']
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
// Constrói o `sck` do Hotmart: utm_source|utm_medium|utm_campaign|utm_content|utm_term
|
|
265
|
+
const buildSck = () =>
|
|
266
|
+
[utms.utm_source, utms.utm_medium, utms.utm_campaign, utms.utm_content, utms.utm_term]
|
|
267
|
+
.map(v => v || 'direto')
|
|
268
|
+
.join('|');
|
|
269
|
+
|
|
270
|
+
// Parâmetros por plataforma
|
|
271
|
+
const getParamsForUrl = (href) => {
|
|
272
|
+
const params = { ...domains };
|
|
273
|
+
const url = href.toLowerCase();
|
|
274
|
+
|
|
275
|
+
const isMatch = (list) => list.some(d => url.includes(d));
|
|
276
|
+
|
|
277
|
+
if (platforms.includes('hotmart') && isMatch(PLATFORM_DOMAINS.hotmart)) {
|
|
278
|
+
if (userId) params.xcod = userId;
|
|
279
|
+
if (utms.utm_source) params.sck = buildSck();
|
|
280
|
+
} else if (platforms.includes('kiwify') && isMatch(PLATFORM_DOMAINS.kiwify)) {
|
|
281
|
+
if (utms.utm_source) params.src = utms.utm_source;
|
|
282
|
+
if (utms.utm_medium) params.utm_medium = utms.utm_medium;
|
|
283
|
+
if (utms.utm_campaign) params.utm_campaign = utms.utm_campaign;
|
|
284
|
+
} else if (platforms.includes('eduzz') && isMatch(PLATFORM_DOMAINS.eduzz)) {
|
|
285
|
+
if (utms.utm_source) params.src = utms.utm_source;
|
|
286
|
+
} else if (platforms.includes('monetizze') && isMatch(PLATFORM_DOMAINS.monetizze)) {
|
|
287
|
+
if (utms.utm_source) params.src = utms.utm_source;
|
|
288
|
+
} else if (platforms.includes('cartpanda') && isMatch(PLATFORM_DOMAINS.cartpanda)) {
|
|
289
|
+
Object.entries(utms).forEach(([k, v]) => { if (v) params[k] = v; });
|
|
290
|
+
} else if (platforms.includes('ticto') && isMatch(PLATFORM_DOMAINS.ticto)) {
|
|
291
|
+
// Ticto captura UTMs em wh.tracking e parâmetros extras em wh.url_params
|
|
292
|
+
Object.entries(utms).forEach(([k, v]) => { if (v) params[k] = v; });
|
|
293
|
+
if (userId) params.user_id = userId; // recuperado no webhook via D1 cross-check
|
|
294
|
+
} else {
|
|
295
|
+
// Domínios customizados: repassa utm_* e userId
|
|
296
|
+
const allDomains = Object.values(PLATFORM_DOMAINS).flat().concat(domains);
|
|
297
|
+
if (!isMatch(allDomains)) return null;
|
|
298
|
+
Object.entries(utms).forEach(([k, v]) => { if (v) params[k] = v; });
|
|
299
|
+
if (userId) params.user_id = userId;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
return Object.keys(params).length ? params : null;
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
const applyParams = (link) => {
|
|
306
|
+
if (!link.href || link.href.startsWith('javascript')) return;
|
|
307
|
+
const p = getParamsForUrl(link);
|
|
308
|
+
if (!p) return;
|
|
309
|
+
|
|
310
|
+
try {
|
|
311
|
+
const url = new URL(link.href);
|
|
312
|
+
Object.entries(p).forEach(([k, v]) => url.searchParams.set(k, v));
|
|
313
|
+
link.href = url.toString();
|
|
314
|
+
} catch { /* URL inválida — ignora */ }
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
// Aplica em todos os links existentes
|
|
318
|
+
document.querySelectorAll('a[href]').forEach(applyParams);
|
|
319
|
+
|
|
320
|
+
// Aplica em links adicionados dinamicamente (ex: botões de checkout lazy loaded)
|
|
321
|
+
// BUG CORRIGIDO: .observe() estava faltando — links dinâmicos nunca eram rastreados
|
|
322
|
+
new MutationObserver((mutations) => {
|
|
323
|
+
mutations.forEach(m => m.addedNodes.forEach(node => {
|
|
324
|
+
if (node.nodeType !== 1) return;
|
|
325
|
+
if (node.tagName === 'A') applyParams(node);
|
|
326
|
+
// Também verifica <a> aninhados dentro do nó adicionado
|
|
327
|
+
node.querySelectorAll?.('a[href]').forEach(applyParams);
|
|
328
|
+
}));
|
|
329
|
+
}).observe(document.body, { childList: true, subtree: true });
|
|
330
|
+
|
|
331
|
+
console.log('✅ PassCheckoutParams inicializado');
|
|
332
|
+
};
|
|
333
|
+
|
|
334
|
+
// ── Fingerprint Fallback para Links de Afiliado ───────────────────────────────
|
|
335
|
+
// Problema: quando o cookie _cdp_uid ou os UTMs são perdidos antes do clique
|
|
336
|
+
// (ex: usuário abre em nova aba, troca de navegador), o checkout chega sem cdp_uid.
|
|
337
|
+
//
|
|
338
|
+
// Solução: salvar uid + UTMs no localStorage ao carregar. passCheckoutParams()
|
|
339
|
+
// usa este backup quando os parâmetros de URL estiverem vazios.
|
|
340
|
+
|
|
341
|
+
const _AFFILIATE_STORAGE_KEY = '_cdp_aff';
|
|
342
|
+
|
|
343
|
+
function _saveAffiliateContext() {
|
|
344
|
+
if (!isBrowser) return;
|
|
345
|
+
try {
|
|
346
|
+
const hasUtms = Object.values(_utms).some(v => !!v);
|
|
347
|
+
const existing = _loadAffiliateContext();
|
|
348
|
+
// Só sobrescreve se esta visita tem UTMs — não apaga atribuição melhor anterior
|
|
349
|
+
if (hasUtms || !existing) {
|
|
350
|
+
localStorage.setItem(_AFFILIATE_STORAGE_KEY, JSON.stringify({
|
|
351
|
+
uid: _userId,
|
|
352
|
+
utms: _utms,
|
|
353
|
+
ts: Date.now(),
|
|
354
|
+
}));
|
|
355
|
+
}
|
|
356
|
+
} catch { /* localStorage bloqueado */ }
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
function _loadAffiliateContext() {
|
|
360
|
+
if (!isBrowser) return null;
|
|
361
|
+
try {
|
|
362
|
+
const raw = localStorage.getItem(_AFFILIATE_STORAGE_KEY);
|
|
363
|
+
if (!raw) return null;
|
|
364
|
+
const ctx = JSON.parse(raw);
|
|
365
|
+
if (Date.now() - ctx.ts > 30 * 24 * 60 * 60 * 1000) {
|
|
366
|
+
localStorage.removeItem(_AFFILIATE_STORAGE_KEY);
|
|
367
|
+
return null;
|
|
368
|
+
}
|
|
369
|
+
return ctx;
|
|
370
|
+
} catch { return null; }
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Retorna UTMs ativos: da URL (prioridade) ou do localStorage (fallback de 30 dias).
|
|
375
|
+
* Uso principal: links de afiliado onde o usuário pode chegar sem parâmetros na URL.
|
|
376
|
+
*/
|
|
377
|
+
export function getUTMsWithFallback() {
|
|
378
|
+
const live = getUTMs();
|
|
379
|
+
if (Object.values(live).some(v => !!v)) return live;
|
|
380
|
+
const stored = _loadAffiliateContext();
|
|
381
|
+
if (stored?.utms && Object.values(stored.utms).some(v => !!v)) {
|
|
382
|
+
if (CONFIG.debug) console.log('ℹ️ UTMs restaurados do localStorage (affiliate fallback)');
|
|
383
|
+
return stored.utms;
|
|
384
|
+
}
|
|
385
|
+
return live;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* Retorna o userId com fallback para o armazenado no localStorage.
|
|
390
|
+
*/
|
|
391
|
+
export function getUserIdWithFallback() {
|
|
392
|
+
return _userId || _loadAffiliateContext()?.uid || '';
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// ── Tracking Events (Principal) ─────────────────────────────────
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* track() — Função principal de tracking
|
|
399
|
+
*
|
|
400
|
+
* @param {string} eventName - Nome do evento
|
|
401
|
+
* @param {object} data - Dados do evento
|
|
402
|
+
* @param {object} options - Opções adicionais
|
|
403
|
+
* @returns {Promise} - Promise com resultado
|
|
404
|
+
*
|
|
405
|
+
* Dispara evento para:
|
|
406
|
+
* - Worker (server-side)
|
|
407
|
+
* - Meta CAPI v22.0 (se configurado)
|
|
408
|
+
* - Google GA4 Measurement Protocol (se configurado)
|
|
409
|
+
* - TikTok Events API v1.3 (se configurado)
|
|
410
|
+
*
|
|
411
|
+
* Processo:
|
|
412
|
+
* 1. Gera event_id único
|
|
413
|
+
* 2. Coleta UTMs e Click IDs
|
|
414
|
+
* 3. Envia para Worker via fetch (same-domain)
|
|
415
|
+
* 4. Worker processa e despacha para plataformas
|
|
416
|
+
*/
|
|
417
|
+
export async function track(eventName, data = {}, options = {}) {
|
|
418
|
+
if (!isBrowser) {
|
|
419
|
+
console.warn('⚠️ cdpTrack.track() deve ser chamado no browser');
|
|
420
|
+
return { success: false, error: 'Not in browser' };
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
const event_id = generateId();
|
|
424
|
+
const utms = getUTMs();
|
|
425
|
+
const clickIds = _getClickIDs();
|
|
426
|
+
const userId = getUserId();
|
|
427
|
+
const sessionId = getSessionId();
|
|
428
|
+
const timestamp = Date.now();
|
|
429
|
+
|
|
430
|
+
// Calcular engajamento preliminar (browser-side)
|
|
431
|
+
const engagementScore = calculateEngagementScore();
|
|
432
|
+
const { totalScore, timeLevel, scrollScore, clickScore, videoScore, hoverScore, intentionLevel } = engagementScore;
|
|
433
|
+
|
|
434
|
+
// Payload completo
|
|
435
|
+
const payload = {
|
|
436
|
+
event_id,
|
|
437
|
+
event_name: eventName,
|
|
438
|
+
user_id: userId,
|
|
439
|
+
session_id: sessionId,
|
|
440
|
+
utms,
|
|
441
|
+
click_ids: clickIds,
|
|
442
|
+
timestamp,
|
|
443
|
+
page_url: window.location.href,
|
|
444
|
+
referrer: document.referrer,
|
|
445
|
+
user_agent: navigator.userAgent,
|
|
446
|
+
behavioral_data: {
|
|
447
|
+
...data,
|
|
448
|
+
engagement_score: totalScore,
|
|
449
|
+
time_level: timeLevel,
|
|
450
|
+
scroll_score: scrollScore,
|
|
451
|
+
click_score: clickScore,
|
|
452
|
+
video_score: videoScore,
|
|
453
|
+
hover_score: hoverScore,
|
|
454
|
+
intention_level: intentionLevel
|
|
455
|
+
},
|
|
456
|
+
...options
|
|
457
|
+
};
|
|
458
|
+
|
|
459
|
+
try {
|
|
460
|
+
// Enviar para Worker com retry automático (anti-blocking)
|
|
461
|
+
const result = await sendWithRetry(payload, '/api/tracking');
|
|
462
|
+
|
|
463
|
+
if (result.success) {
|
|
464
|
+
console.log(`✅ Evento ${eventName} enviado com sucesso:`, event_id, result);
|
|
465
|
+
|
|
466
|
+
// Atualizar estado de engajamento
|
|
467
|
+
updateEngagementState('timeOnPage', Math.round((Date.now() - timestamp) / 1000));
|
|
468
|
+
updateEngagementState('totalScore', totalScore);
|
|
469
|
+
|
|
470
|
+
return { success: true, event_id, result };
|
|
471
|
+
} else {
|
|
472
|
+
throw new Error(result.error || 'Envio falhou após todas as tentativas');
|
|
473
|
+
}
|
|
474
|
+
} catch (error) {
|
|
475
|
+
console.error(`❌ Erro ao enviar evento ${eventName}:`, error);
|
|
476
|
+
return { success: false, error: error.message, event_id, attempts: ANTI_BLOCKING_CONFIG.maxRetries };
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// ── Conveniência: Track Direct ──────────────────────────────────────
|
|
481
|
+
|
|
482
|
+
/**
|
|
483
|
+
* trackLead() — Captura de lead com dados PII crus
|
|
484
|
+
*
|
|
485
|
+
* @param {object} userData - Dados do usuário (email, phone, name, etc.)
|
|
486
|
+
* @param {HTMLFormElement} form - Formulário HTML (opcional, para captura automática)
|
|
487
|
+
* @returns {Promise} - Promise com resultado
|
|
488
|
+
*/
|
|
489
|
+
export async function trackLead(userData, form = null) {
|
|
490
|
+
// Se formulário fornecido, extrair dados PII automaticamente
|
|
491
|
+
let piiData = { ...userData };
|
|
492
|
+
|
|
493
|
+
if (form) {
|
|
494
|
+
const formPII = extractFormPII(form);
|
|
495
|
+
piiData = { ...piiData, ...formPII };
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
// Passa dados crus para Worker fazer hashing
|
|
499
|
+
// Worker fará SHA256 para máximo performance e segurança
|
|
500
|
+
return track('Lead', piiData);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
/**
|
|
504
|
+
* trackPurchase() — Captura de compra
|
|
505
|
+
*
|
|
506
|
+
* @param {object} orderData - Dados da compra (order_id, value, currency, etc.)
|
|
507
|
+
* @param {HTMLFormElement} form - Formulário HTML (opcional, para captura automática)
|
|
508
|
+
* @returns {Promise} - Promise com resultado
|
|
509
|
+
*/
|
|
510
|
+
export async function trackPurchase(orderData, form = null) {
|
|
511
|
+
let purchaseData = { ...orderData };
|
|
512
|
+
|
|
513
|
+
if (form) {
|
|
514
|
+
const formPII = extractFormPII(form);
|
|
515
|
+
purchaseData = { ...purchaseData, ...formPII };
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
return track('Purchase', purchaseData);
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
/**
|
|
522
|
+
* Auto-captura formulários de lead na página
|
|
523
|
+
* Intercepta submit de formulários e captura PII automaticamente
|
|
524
|
+
*/
|
|
525
|
+
export function setupAutoFormCapture() {
|
|
526
|
+
if (!isBrowser) return;
|
|
527
|
+
|
|
528
|
+
// Interceptar todos os formulários
|
|
529
|
+
document.addEventListener('submit', async (e) => {
|
|
530
|
+
const form = e.target;
|
|
531
|
+
|
|
532
|
+
// Verificar se é um formulário de lead (baseado em campos)
|
|
533
|
+
const hasEmailField = form.querySelector('[name*="email"]');
|
|
534
|
+
const hasPhoneField = form.querySelector('[name*="phone"], [name*="telefone"], [name*="celular"]');
|
|
535
|
+
|
|
536
|
+
if (hasEmailField || hasPhoneField) {
|
|
537
|
+
e.preventDefault();
|
|
538
|
+
|
|
539
|
+
const piiData = extractFormPII(form);
|
|
540
|
+
|
|
541
|
+
if (isValidPII(piiData)) {
|
|
542
|
+
await trackLead(piiData, form);
|
|
543
|
+
console.log('✅ Lead capturado automaticamente com Advanced Matching:', piiData);
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
// Continuar com submit do formulário
|
|
547
|
+
setTimeout(() => form.submit(), 100);
|
|
548
|
+
}
|
|
549
|
+
}, true); // Capture para formulários dinâmicos
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
/**
|
|
553
|
+
* trackPurchase() — Captura de compra
|
|
554
|
+
*
|
|
555
|
+
* @param {object} orderData - Dados da compra (order_id, value, currency, etc.)
|
|
556
|
+
* @returns {Promise} - Promise com resultado
|
|
557
|
+
*/
|
|
558
|
+
export async function trackPurchase(orderData) {
|
|
559
|
+
return track('Purchase', orderData);
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
/**
|
|
563
|
+
* trackViewContent() — Captura de visualização de conteúdo
|
|
564
|
+
*
|
|
565
|
+
* @param {object} contentData - Dados do conteúdo (content_id, content_name, value, currency)
|
|
566
|
+
* @returns {Promise} - Promise com resultado
|
|
567
|
+
*/
|
|
568
|
+
export async function trackViewContent(contentData) {
|
|
569
|
+
return track('ViewContent', contentData);
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
/**
|
|
573
|
+
* trackAddToCart() — Captura de adição ao carrinho
|
|
574
|
+
*
|
|
575
|
+
* @param {object} cartData - Dados do carrinho (content_id, value, currency)
|
|
576
|
+
* @returns {Promise} - Promise com resultado
|
|
577
|
+
*/
|
|
578
|
+
export async function trackAddToCart(cartData) {
|
|
579
|
+
return track('AddToCart', cartData);
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
// ── Inicialização ──────────────────────────────────────────────────────
|
|
583
|
+
|
|
584
|
+
/**
|
|
585
|
+
* Inicializa o cdpTrack SDK
|
|
586
|
+
* Deve ser chamado após carregar tracking.config.js
|
|
587
|
+
*/
|
|
588
|
+
export async function init() {
|
|
589
|
+
if (!isBrowser) return;
|
|
590
|
+
|
|
591
|
+
if (CONFIG.debug) console.log('🚀 Inicializando cdpTrack SDK...');
|
|
592
|
+
|
|
593
|
+
// 0. Google Consent Mode v2 — DEVE ser o primeiro passo, antes de qualquer gtag
|
|
594
|
+
initConsentMode();
|
|
595
|
+
|
|
596
|
+
// 1. Inicializar anti-blocking
|
|
597
|
+
initAntiBlocking();
|
|
598
|
+
|
|
599
|
+
// 1-B. Salvar contexto de afiliado no localStorage (fallback para UTM resurrection)
|
|
600
|
+
_saveAffiliateContext();
|
|
601
|
+
|
|
602
|
+
// 2. Inicializar micro-events
|
|
603
|
+
initMicroEvents();
|
|
604
|
+
|
|
605
|
+
// 3. Inicializar auto-captura de formulários (respeitando CONFIG)
|
|
606
|
+
if (CONFIG.autoCaptureForms !== false) {
|
|
607
|
+
setupAutoFormCapture();
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
// 4. Expõe cdpTrack globalmente ANTES do BehaviorEngine (ele referencia window.cdpTrack)
|
|
611
|
+
window.cdpTrack = { track, trackLead, trackPurchase, trackViewContent, trackAddToCart, getUserId, getUserIdWithFallback, getSessionId, getUTMs, getUTMsWithFallback, updateConsent };
|
|
612
|
+
|
|
613
|
+
// 5. Carregar BehaviorEngine (respeitando CONFIG)
|
|
614
|
+
if (CONFIG.initBehaviorEngine !== false) {
|
|
615
|
+
try {
|
|
616
|
+
const behaviorModule = await import('./models/scenarios/behavior-engine.js');
|
|
617
|
+
const BehaviorEngine = behaviorModule.default;
|
|
618
|
+
|
|
619
|
+
if (BehaviorEngine && typeof BehaviorEngine.init === 'function') {
|
|
620
|
+
BehaviorEngine.init();
|
|
621
|
+
if (CONFIG.debug) console.log('✅ Behavior Engine inicializado');
|
|
622
|
+
}
|
|
623
|
+
} catch (error) {
|
|
624
|
+
// Não bloqueia o SDK — continua sem funcionalidades avançadas
|
|
625
|
+
if (CONFIG.debug) console.info('ℹ️ Behavior Engine não disponível:', error.message);
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
// 6. passCheckoutParams — controlado por CONFIG.passCheckoutParams + CONFIG.platforms
|
|
630
|
+
if (CONFIG.passCheckoutParams !== false && CONFIG.platforms?.length > 0) {
|
|
631
|
+
passCheckoutParams({ platforms: CONFIG.platforms });
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
if (CONFIG.debug) console.log('✅ cdpTrack SDK inicializado (Quantum Tier)');
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
// Auto-inicialização
|
|
638
|
+
if (isBrowser) {
|
|
639
|
+
// Inicializa após carregamento da página
|
|
640
|
+
window.addEventListener('DOMContentLoaded', init);
|
|
641
|
+
}
|