cdp-edge 1.17.0 → 2.0.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 +308 -308
- package/bin/cdp-edge.js +61 -61
- package/dist/commands/analyze.js +52 -52
- package/dist/commands/infra.js +54 -54
- package/dist/commands/install.js +187 -0
- package/dist/commands/server.js +174 -174
- package/dist/commands/setup.js +19 -1
- package/dist/commands/validate.js +84 -84
- package/dist/index.js +12 -12
- package/extracted-skill/tracking-events-generator/advanced-matching.js +364 -364
- package/extracted-skill/tracking-events-generator/agents/bing-agent.md +8 -0
- package/extracted-skill/tracking-events-generator/agents/crm-integration-agent.md +8 -0
- package/extracted-skill/tracking-events-generator/agents/database-agent.md +8 -0
- package/extracted-skill/tracking-events-generator/agents/devops-agent.md +1 -0
- package/extracted-skill/tracking-events-generator/agents/domain-setup-agent.md +8 -0
- package/extracted-skill/tracking-events-generator/agents/google-agent.md +8 -0
- package/extracted-skill/tracking-events-generator/agents/linkedin-agent.md +8 -0
- package/extracted-skill/tracking-events-generator/agents/memory-agent.md +29 -2
- package/extracted-skill/tracking-events-generator/agents/meta-agent.md +8 -0
- package/extracted-skill/tracking-events-generator/agents/pinterest-agent.md +8 -0
- package/extracted-skill/tracking-events-generator/agents/r2-setup-agent.md +8 -0
- package/extracted-skill/tracking-events-generator/agents/reddit-agent.md +8 -0
- package/extracted-skill/tracking-events-generator/agents/server-tracking.md +1 -0
- package/extracted-skill/tracking-events-generator/agents/spotify-agent.md +8 -0
- package/extracted-skill/tracking-events-generator/agents/tiktok-agent.md +8 -0
- package/extracted-skill/tracking-events-generator/agents/validator-agent.md +4 -0
- package/extracted-skill/tracking-events-generator/agents/webhook-agent.md +8 -0
- package/extracted-skill/tracking-events-generator/agents/whatsapp-ctwa-setup-agent.md +8 -0
- package/extracted-skill/tracking-events-generator/anti-blocking.js +285 -285
- package/extracted-skill/tracking-events-generator/cdpTrack.js +641 -641
- package/extracted-skill/tracking-events-generator/engagement-scoring.js +226 -226
- package/extracted-skill/tracking-events-generator/evals/evals.json +235 -235
- package/extracted-skill/tracking-events-generator/integration-test.js +497 -497
- package/extracted-skill/tracking-events-generator/micro-events.js +992 -992
- package/extracted-skill/tracking-events-generator/models/pinterest/conversions-api-template.js +144 -144
- package/extracted-skill/tracking-events-generator/models/pinterest/event-mappings.json +48 -48
- package/extracted-skill/tracking-events-generator/models/pinterest/tag-template.js +28 -28
- package/extracted-skill/tracking-events-generator/models/reddit/conversions-api-template.js +205 -205
- package/extracted-skill/tracking-events-generator/models/reddit/event-mappings.json +56 -56
- package/extracted-skill/tracking-events-generator/models/reddit/pixel-template.js +19 -19
- package/extracted-skill/tracking-events-generator/models/scenarios/behavior-engine.js +425 -425
- package/package.json +76 -76
- package/server-edge-tracker/schema.sql +265 -265
- package/server-edge-tracker/worker.js +4160 -4160
- package/server-edge-tracker/wrangler.toml +103 -103
- package/templates/pinterest/conversions-api-template.js +144 -144
- package/templates/pinterest/event-mappings.json +48 -48
- package/templates/pinterest/tag-template.js +28 -28
- package/templates/reddit/conversions-api-template.js +205 -205
- package/templates/reddit/event-mappings.json +56 -56
- package/templates/reddit/pixel-template.js +19 -19
- package/templates/scenarios/behavior-engine.js +425 -425
|
@@ -1,364 +1,364 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ADVANCED MATCHING MAXIMUM - CDP Edge (Quantum Tier)
|
|
3
|
-
*
|
|
4
|
-
* Sistema de normalização e hashing de PII (Personally Identifiable Information)
|
|
5
|
-
* para máxima precisão de atribuição em Meta CAPI v22.0.
|
|
6
|
-
*
|
|
7
|
-
* @version 1.0.0
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
// ── Guards ────────────────────────────────────────────────
|
|
11
|
-
const isBrowser = typeof window !== 'undefined';
|
|
12
|
-
|
|
13
|
-
// ── Configurações de Normalização ─────────────────────
|
|
14
|
-
|
|
15
|
-
const NORMALIZATION_CONFIG = {
|
|
16
|
-
email: {
|
|
17
|
-
trim: true,
|
|
18
|
-
lowercase: true,
|
|
19
|
-
removeDots: false, // Gmail: user.name@gmail.com vs username@gmail.com (diferentes)
|
|
20
|
-
removePlus: true // Gmail: user+tag@gmail.com = user@gmail.com (mesmo)
|
|
21
|
-
},
|
|
22
|
-
phone: {
|
|
23
|
-
keepOnlyDigits: true,
|
|
24
|
-
addCountryCode: true, // Adiciona DDI se não tiver (55 para Brasil)
|
|
25
|
-
defaultCountryCode: '55'
|
|
26
|
-
},
|
|
27
|
-
name: {
|
|
28
|
-
trim: true,
|
|
29
|
-
lowercase: true,
|
|
30
|
-
removeAccents: true,
|
|
31
|
-
removeExtraSpaces: true,
|
|
32
|
-
maxLength: 100
|
|
33
|
-
},
|
|
34
|
-
city: {
|
|
35
|
-
trim: true,
|
|
36
|
-
lowercase: true,
|
|
37
|
-
removeAccents: true,
|
|
38
|
-
maxLength: 50
|
|
39
|
-
},
|
|
40
|
-
state: {
|
|
41
|
-
trim: true,
|
|
42
|
-
lowercase: true,
|
|
43
|
-
removeAccents: true,
|
|
44
|
-
maxLength: 50
|
|
45
|
-
},
|
|
46
|
-
zip: {
|
|
47
|
-
trim: true,
|
|
48
|
-
keepOnlyDigits: true,
|
|
49
|
-
maxLength: 10
|
|
50
|
-
}
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
// ── Funções de Normalização ──────────────────────────────
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Normaliza e-mail para máximo match
|
|
57
|
-
*
|
|
58
|
-
* Gmail Plus: user+tag@gmail.com = user@gmail.com (mesmo usuário)
|
|
59
|
-
* Lowercase: USER@GMAIL.COM = user@gmail.com (case-insensitive)
|
|
60
|
-
* Trim: remove espaços
|
|
61
|
-
*
|
|
62
|
-
* @param {string} email - E-mail bruto
|
|
63
|
-
* @returns {string} E-mail normalizado
|
|
64
|
-
*/
|
|
65
|
-
function normalizeEmail(email) {
|
|
66
|
-
if (!email || typeof email !== 'string') return '';
|
|
67
|
-
|
|
68
|
-
let normalized = email.trim();
|
|
69
|
-
|
|
70
|
-
// Gmail: remover tudo após + (plus addressing)
|
|
71
|
-
if (normalized.includes('@gmail.com')) {
|
|
72
|
-
normalized = normalized.split('+')[0] + '@gmail.com';
|
|
73
|
-
}
|
|
74
|
-
// GMR (Google Mail): remover plus
|
|
75
|
-
if (normalized.includes('@googlemail.com')) {
|
|
76
|
-
normalized = normalized.split('+')[0] + '@googlemail.com';
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
return normalized.toLowerCase();
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* Normaliza telefone para máximo match
|
|
84
|
-
*
|
|
85
|
-
* Remove caracteres não numéricos
|
|
86
|
-
* Adiciona código do país (55 para Brasil) se não tiver
|
|
87
|
-
*
|
|
88
|
-
* @param {string} phone - Telefone bruto
|
|
89
|
-
* @returns {string} Telefone normalizado (apenas números)
|
|
90
|
-
*/
|
|
91
|
-
function normalizePhone(phone) {
|
|
92
|
-
if (!phone || typeof phone !== 'string') return '';
|
|
93
|
-
|
|
94
|
-
// Remove tudo que não é número
|
|
95
|
-
let normalized = phone.replace(/\D/g, '');
|
|
96
|
-
|
|
97
|
-
// Se não tem DDI, adiciona 55 (Brasil)
|
|
98
|
-
if (normalized.length === 11 || normalized.length === 10) {
|
|
99
|
-
normalized = '55' + normalized;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// Limita a 15 dígitos (máximo internacional)
|
|
103
|
-
return normalized.substring(0, 15);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* Normaliza nome para máximo match
|
|
108
|
-
*
|
|
109
|
-
* Remove acentos, converte para minúsculas, remove espaços extras
|
|
110
|
-
*
|
|
111
|
-
* @param {string} name - Nome bruto
|
|
112
|
-
* @returns {string} Nome normalizado
|
|
113
|
-
*/
|
|
114
|
-
function normalizeName(name) {
|
|
115
|
-
if (!name || typeof name !== 'string') return '';
|
|
116
|
-
|
|
117
|
-
let normalized = name.trim();
|
|
118
|
-
|
|
119
|
-
// Remove acentos
|
|
120
|
-
normalized = normalized.normalize('NFD').replace(/[\u0300-\u036f]/g, '');
|
|
121
|
-
|
|
122
|
-
// Converte para minúsculas
|
|
123
|
-
normalized = normalized.toLowerCase();
|
|
124
|
-
|
|
125
|
-
// Remove espaços extras (múltiplos espaços vira um)
|
|
126
|
-
normalized = normalized.replace(/\s+/g, ' ');
|
|
127
|
-
|
|
128
|
-
// Limita tamanho
|
|
129
|
-
return normalized.substring(0, NORMALIZATION_CONFIG.name.maxLength);
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* Normaliza cidade para Meta AM (Advanced Matching)
|
|
134
|
-
*
|
|
135
|
-
* @param {string} city - Cidade bruta
|
|
136
|
-
* @returns {string} Cidade normalizada
|
|
137
|
-
*/
|
|
138
|
-
function normalizeCity(city) {
|
|
139
|
-
if (!city || typeof city !== 'string') return '';
|
|
140
|
-
|
|
141
|
-
let normalized = city.trim();
|
|
142
|
-
|
|
143
|
-
// Remove acentos
|
|
144
|
-
normalized = normalized.normalize('NFD').replace(/[\u0300-\u036f]/g, '');
|
|
145
|
-
|
|
146
|
-
// Converte para minúsculas
|
|
147
|
-
normalized = normalized.toLowerCase();
|
|
148
|
-
|
|
149
|
-
// Remove espaços extras
|
|
150
|
-
normalized = normalized.replace(/\s+/g, ' ');
|
|
151
|
-
|
|
152
|
-
return normalized.substring(0, NORMALIZATION_CONFIG.city.maxLength);
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
/**
|
|
156
|
-
* Normaliza estado para Meta AM
|
|
157
|
-
*
|
|
158
|
-
* @param {string} state - Estado bruto
|
|
159
|
-
* @returns {string} Estado normalizado
|
|
160
|
-
*/
|
|
161
|
-
function normalizeState(state) {
|
|
162
|
-
if (!state || typeof state !== 'string') return '';
|
|
163
|
-
|
|
164
|
-
let normalized = state.trim();
|
|
165
|
-
|
|
166
|
-
// Remove acentos
|
|
167
|
-
normalized = normalized.normalize('NFD').replace(/[\u0300-\u036f]/g, '');
|
|
168
|
-
|
|
169
|
-
// Converte para minúsculas
|
|
170
|
-
normalized = normalized.toLowerCase();
|
|
171
|
-
|
|
172
|
-
return normalized.substring(0, NORMALIZATION_CONFIG.state.maxLength);
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
/**
|
|
176
|
-
* Normaliza CEP para Meta AM
|
|
177
|
-
*
|
|
178
|
-
* @param {string} zip - CEP bruto
|
|
179
|
-
* @returns {string} CEP normalizado (apenas números)
|
|
180
|
-
*/
|
|
181
|
-
function normalizeZip(zip) {
|
|
182
|
-
if (!zip || typeof zip !== 'string') return '';
|
|
183
|
-
|
|
184
|
-
// Remove tudo que não é número
|
|
185
|
-
const normalized = zip.replace(/\D/g, '');
|
|
186
|
-
|
|
187
|
-
return normalized.substring(0, NORMALIZATION_CONFIG.zip.maxLength);
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
/**
|
|
191
|
-
* Normaliza data de nascimento para Meta AM
|
|
192
|
-
*
|
|
193
|
-
* Formato esperado: YYYYMMDD
|
|
194
|
-
*
|
|
195
|
-
* @param {string} dob - Data de nascimento bruta
|
|
196
|
-
* @returns {string} Data normalizada (YYYYMMDD)
|
|
197
|
-
*/
|
|
198
|
-
function normalizeDOB(dob) {
|
|
199
|
-
if (!dob || typeof dob !== 'string') return '';
|
|
200
|
-
|
|
201
|
-
// Tenta diferentes formatos
|
|
202
|
-
const formats = [
|
|
203
|
-
/(\d{4})-(\d{2})-(\d{2})/, // YYYY-MM-DD
|
|
204
|
-
/(\d{2})\/(\d{2})\/(\d{4})/, // DD/MM/YYYY
|
|
205
|
-
/(\d{2})-(\d{2})-(\d{4})/ // DD-MM-YYYY
|
|
206
|
-
];
|
|
207
|
-
|
|
208
|
-
for (const format of formats) {
|
|
209
|
-
const match = dob.match(format);
|
|
210
|
-
if (match) {
|
|
211
|
-
let year, month, day;
|
|
212
|
-
if (match[1].length === 4) {
|
|
213
|
-
// YYYY-MM-DD
|
|
214
|
-
[year, month, day] = [match[1], match[2], match[3]];
|
|
215
|
-
} else {
|
|
216
|
-
// DD/MM/YYYY
|
|
217
|
-
[day, month, year] = [match[1], match[2], match[3]];
|
|
218
|
-
}
|
|
219
|
-
return `${year}${month}${day}`;
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
return '';
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
// ── Browser-Side: Dados Crus para Worker ───────────
|
|
227
|
-
|
|
228
|
-
/**
|
|
229
|
-
* Extrai dados PII de um formulário HTML para Advanced Matching
|
|
230
|
-
*
|
|
231
|
-
* @param {HTMLFormElement} form - Elemento do formulário
|
|
232
|
-
* @returns {object} Dados normalizados para envio ao Worker
|
|
233
|
-
*/
|
|
234
|
-
export function extractFormPII(form) {
|
|
235
|
-
if (!form || typeof form.elements !== 'object') return {};
|
|
236
|
-
|
|
237
|
-
const formData = new FormData(form);
|
|
238
|
-
const piiData = {};
|
|
239
|
-
|
|
240
|
-
// Campo de e-mail
|
|
241
|
-
const email = formData.get('email') || formData.get('user_email') || formData.get('useremail');
|
|
242
|
-
if (email) piiData.email = normalizeEmail(email);
|
|
243
|
-
|
|
244
|
-
// Campo de telefone
|
|
245
|
-
const phone = formData.get('phone') || formData.get('telephone') || formData.get('telefone') || formData.get('celular');
|
|
246
|
-
if (phone) piiData.phone = normalizePhone(phone);
|
|
247
|
-
|
|
248
|
-
// Campos de nome
|
|
249
|
-
const firstName = formData.get('first_name') || formData.get('nome') || formData.get('firstname');
|
|
250
|
-
const lastName = formData.get('last_name') || formData.get('sobrenome') || formData.get('lastname');
|
|
251
|
-
const fullName = formData.get('name') || formData.get('fullname') || formData.get('nome_completo');
|
|
252
|
-
|
|
253
|
-
if (fullName) {
|
|
254
|
-
const names = fullName.split(' ');
|
|
255
|
-
piiData.first_name = normalizeName(names[0]);
|
|
256
|
-
piiData.last_name = normalizeName(names.slice(1).join(' '));
|
|
257
|
-
} else {
|
|
258
|
-
if (firstName) piiData.first_name = normalizeName(firstName);
|
|
259
|
-
if (lastName) piiData.last_name = normalizeName(lastName);
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
// Campos de endereço (Meta AM)
|
|
263
|
-
const city = formData.get('city') || formData.get('cidade');
|
|
264
|
-
if (city) piiData.city = normalizeCity(city);
|
|
265
|
-
|
|
266
|
-
const state = formData.get('state') || formData.get('estado') || formData.get('uf');
|
|
267
|
-
if (state) piiData.state = normalizeState(state);
|
|
268
|
-
|
|
269
|
-
const zip = formData.get('zip') || formData.get('cep') || formData.get('postalcode');
|
|
270
|
-
if (zip) piiData.zip = normalizeZip(zip);
|
|
271
|
-
|
|
272
|
-
const dob = formData.get('dob') || formData.get('date_of_birth') || formData.get('nascimento');
|
|
273
|
-
if (dob) piiData.dob = normalizeDOB(dob);
|
|
274
|
-
|
|
275
|
-
return piiData;
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
/**
|
|
279
|
-
* Captura dados PII de qualquer elemento (input, button click, etc.)
|
|
280
|
-
*
|
|
281
|
-
* @param {HTMLElement} element - Elemento DOM
|
|
282
|
-
* @returns {object} Dados PII normalizados
|
|
283
|
-
*/
|
|
284
|
-
export function capturePII(element) {
|
|
285
|
-
if (!element) return {};
|
|
286
|
-
|
|
287
|
-
const piiData = {};
|
|
288
|
-
|
|
289
|
-
// Se for input, extrair valor
|
|
290
|
-
if (element.tagName === 'INPUT' || element.tagName === 'TEXTAREA') {
|
|
291
|
-
const name = element.name.toLowerCase();
|
|
292
|
-
const value = element.value;
|
|
293
|
-
|
|
294
|
-
if (name.includes('email') && value) {
|
|
295
|
-
piiData.email = normalizeEmail(value);
|
|
296
|
-
} else if (name.includes('phone') || name.includes('telefone') || name.includes('celular')) {
|
|
297
|
-
if (value) piiData.phone = normalizePhone(value);
|
|
298
|
-
} else if (name.includes('name') || name.includes('nome')) {
|
|
299
|
-
if (value) piiData.full_name = normalizeName(value);
|
|
300
|
-
} else if (name.includes('city') || name.includes('cidade')) {
|
|
301
|
-
if (value) piiData.city = normalizeCity(value);
|
|
302
|
-
} else if (name.includes('state') || name.includes('estado')) {
|
|
303
|
-
if (value) piiData.state = normalizeState(value);
|
|
304
|
-
} else if (name.includes('zip') || name.includes('cep')) {
|
|
305
|
-
if (value) piiData.zip = normalizeZip(value);
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
return piiData;
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
/**
|
|
313
|
-
* Valida se os dados PII são válidos para envio
|
|
314
|
-
*
|
|
315
|
-
* @param {object} piiData - Dados PII normalizados
|
|
316
|
-
* @returns {boolean} True se tiver dados válidos
|
|
317
|
-
*/
|
|
318
|
-
export function isValidPII(piiData) {
|
|
319
|
-
if (!piiData || typeof piiData !== 'object') return false;
|
|
320
|
-
|
|
321
|
-
const hasEmail = piiData.email && piiData.email.includes('@');
|
|
322
|
-
const hasPhone = piiData.phone && piiData.phone.length >= 10;
|
|
323
|
-
|
|
324
|
-
return hasEmail || hasPhone;
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
// ── Browser-Side: Hashing (Opcional - para debug) ────────
|
|
328
|
-
|
|
329
|
-
/**
|
|
330
|
-
* Calcula SHA256 de um valor (browser-side)
|
|
331
|
-
*
|
|
332
|
-
* AVISO: Para máxima segurança, o hashing deve ser feito no Worker.
|
|
333
|
-
* Esta função é apenas para debug/visualização.
|
|
334
|
-
*
|
|
335
|
-
* @param {string} value - Valor para hash
|
|
336
|
-
* @returns {Promise<string>} Hash SHA256 em hexadecimal
|
|
337
|
-
*/
|
|
338
|
-
export async function hashPII(value) {
|
|
339
|
-
if (!value || !isBrowser) return '';
|
|
340
|
-
|
|
341
|
-
try {
|
|
342
|
-
const encoder = new TextEncoder();
|
|
343
|
-
const data = encoder.encode(value);
|
|
344
|
-
const hashBuffer = await crypto.subtle.digest('SHA-256', data);
|
|
345
|
-
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
346
|
-
return hashArray.map(byte => byte.toString(16).padStart(2, '0')).join('');
|
|
347
|
-
} catch (error) {
|
|
348
|
-
console.error('Erro ao calcular SHA256 no browser:', error);
|
|
349
|
-
return '';
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
// ── Exportações ────────────────────────────────────────────────
|
|
354
|
-
|
|
355
|
-
export {
|
|
356
|
-
normalizeEmail,
|
|
357
|
-
normalizePhone,
|
|
358
|
-
normalizeName,
|
|
359
|
-
normalizeCity,
|
|
360
|
-
normalizeState,
|
|
361
|
-
normalizeZip,
|
|
362
|
-
normalizeDOB,
|
|
363
|
-
NORMALIZATION_CONFIG
|
|
364
|
-
};
|
|
1
|
+
/**
|
|
2
|
+
* ADVANCED MATCHING MAXIMUM - CDP Edge (Quantum Tier)
|
|
3
|
+
*
|
|
4
|
+
* Sistema de normalização e hashing de PII (Personally Identifiable Information)
|
|
5
|
+
* para máxima precisão de atribuição em Meta CAPI v22.0.
|
|
6
|
+
*
|
|
7
|
+
* @version 1.0.0
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
// ── Guards ────────────────────────────────────────────────
|
|
11
|
+
const isBrowser = typeof window !== 'undefined';
|
|
12
|
+
|
|
13
|
+
// ── Configurações de Normalização ─────────────────────
|
|
14
|
+
|
|
15
|
+
const NORMALIZATION_CONFIG = {
|
|
16
|
+
email: {
|
|
17
|
+
trim: true,
|
|
18
|
+
lowercase: true,
|
|
19
|
+
removeDots: false, // Gmail: user.name@gmail.com vs username@gmail.com (diferentes)
|
|
20
|
+
removePlus: true // Gmail: user+tag@gmail.com = user@gmail.com (mesmo)
|
|
21
|
+
},
|
|
22
|
+
phone: {
|
|
23
|
+
keepOnlyDigits: true,
|
|
24
|
+
addCountryCode: true, // Adiciona DDI se não tiver (55 para Brasil)
|
|
25
|
+
defaultCountryCode: '55'
|
|
26
|
+
},
|
|
27
|
+
name: {
|
|
28
|
+
trim: true,
|
|
29
|
+
lowercase: true,
|
|
30
|
+
removeAccents: true,
|
|
31
|
+
removeExtraSpaces: true,
|
|
32
|
+
maxLength: 100
|
|
33
|
+
},
|
|
34
|
+
city: {
|
|
35
|
+
trim: true,
|
|
36
|
+
lowercase: true,
|
|
37
|
+
removeAccents: true,
|
|
38
|
+
maxLength: 50
|
|
39
|
+
},
|
|
40
|
+
state: {
|
|
41
|
+
trim: true,
|
|
42
|
+
lowercase: true,
|
|
43
|
+
removeAccents: true,
|
|
44
|
+
maxLength: 50
|
|
45
|
+
},
|
|
46
|
+
zip: {
|
|
47
|
+
trim: true,
|
|
48
|
+
keepOnlyDigits: true,
|
|
49
|
+
maxLength: 10
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
// ── Funções de Normalização ──────────────────────────────
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Normaliza e-mail para máximo match
|
|
57
|
+
*
|
|
58
|
+
* Gmail Plus: user+tag@gmail.com = user@gmail.com (mesmo usuário)
|
|
59
|
+
* Lowercase: USER@GMAIL.COM = user@gmail.com (case-insensitive)
|
|
60
|
+
* Trim: remove espaços
|
|
61
|
+
*
|
|
62
|
+
* @param {string} email - E-mail bruto
|
|
63
|
+
* @returns {string} E-mail normalizado
|
|
64
|
+
*/
|
|
65
|
+
function normalizeEmail(email) {
|
|
66
|
+
if (!email || typeof email !== 'string') return '';
|
|
67
|
+
|
|
68
|
+
let normalized = email.trim();
|
|
69
|
+
|
|
70
|
+
// Gmail: remover tudo após + (plus addressing)
|
|
71
|
+
if (normalized.includes('@gmail.com')) {
|
|
72
|
+
normalized = normalized.split('+')[0] + '@gmail.com';
|
|
73
|
+
}
|
|
74
|
+
// GMR (Google Mail): remover plus
|
|
75
|
+
if (normalized.includes('@googlemail.com')) {
|
|
76
|
+
normalized = normalized.split('+')[0] + '@googlemail.com';
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return normalized.toLowerCase();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Normaliza telefone para máximo match
|
|
84
|
+
*
|
|
85
|
+
* Remove caracteres não numéricos
|
|
86
|
+
* Adiciona código do país (55 para Brasil) se não tiver
|
|
87
|
+
*
|
|
88
|
+
* @param {string} phone - Telefone bruto
|
|
89
|
+
* @returns {string} Telefone normalizado (apenas números)
|
|
90
|
+
*/
|
|
91
|
+
function normalizePhone(phone) {
|
|
92
|
+
if (!phone || typeof phone !== 'string') return '';
|
|
93
|
+
|
|
94
|
+
// Remove tudo que não é número
|
|
95
|
+
let normalized = phone.replace(/\D/g, '');
|
|
96
|
+
|
|
97
|
+
// Se não tem DDI, adiciona 55 (Brasil)
|
|
98
|
+
if (normalized.length === 11 || normalized.length === 10) {
|
|
99
|
+
normalized = '55' + normalized;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Limita a 15 dígitos (máximo internacional)
|
|
103
|
+
return normalized.substring(0, 15);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Normaliza nome para máximo match
|
|
108
|
+
*
|
|
109
|
+
* Remove acentos, converte para minúsculas, remove espaços extras
|
|
110
|
+
*
|
|
111
|
+
* @param {string} name - Nome bruto
|
|
112
|
+
* @returns {string} Nome normalizado
|
|
113
|
+
*/
|
|
114
|
+
function normalizeName(name) {
|
|
115
|
+
if (!name || typeof name !== 'string') return '';
|
|
116
|
+
|
|
117
|
+
let normalized = name.trim();
|
|
118
|
+
|
|
119
|
+
// Remove acentos
|
|
120
|
+
normalized = normalized.normalize('NFD').replace(/[\u0300-\u036f]/g, '');
|
|
121
|
+
|
|
122
|
+
// Converte para minúsculas
|
|
123
|
+
normalized = normalized.toLowerCase();
|
|
124
|
+
|
|
125
|
+
// Remove espaços extras (múltiplos espaços vira um)
|
|
126
|
+
normalized = normalized.replace(/\s+/g, ' ');
|
|
127
|
+
|
|
128
|
+
// Limita tamanho
|
|
129
|
+
return normalized.substring(0, NORMALIZATION_CONFIG.name.maxLength);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Normaliza cidade para Meta AM (Advanced Matching)
|
|
134
|
+
*
|
|
135
|
+
* @param {string} city - Cidade bruta
|
|
136
|
+
* @returns {string} Cidade normalizada
|
|
137
|
+
*/
|
|
138
|
+
function normalizeCity(city) {
|
|
139
|
+
if (!city || typeof city !== 'string') return '';
|
|
140
|
+
|
|
141
|
+
let normalized = city.trim();
|
|
142
|
+
|
|
143
|
+
// Remove acentos
|
|
144
|
+
normalized = normalized.normalize('NFD').replace(/[\u0300-\u036f]/g, '');
|
|
145
|
+
|
|
146
|
+
// Converte para minúsculas
|
|
147
|
+
normalized = normalized.toLowerCase();
|
|
148
|
+
|
|
149
|
+
// Remove espaços extras
|
|
150
|
+
normalized = normalized.replace(/\s+/g, ' ');
|
|
151
|
+
|
|
152
|
+
return normalized.substring(0, NORMALIZATION_CONFIG.city.maxLength);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Normaliza estado para Meta AM
|
|
157
|
+
*
|
|
158
|
+
* @param {string} state - Estado bruto
|
|
159
|
+
* @returns {string} Estado normalizado
|
|
160
|
+
*/
|
|
161
|
+
function normalizeState(state) {
|
|
162
|
+
if (!state || typeof state !== 'string') return '';
|
|
163
|
+
|
|
164
|
+
let normalized = state.trim();
|
|
165
|
+
|
|
166
|
+
// Remove acentos
|
|
167
|
+
normalized = normalized.normalize('NFD').replace(/[\u0300-\u036f]/g, '');
|
|
168
|
+
|
|
169
|
+
// Converte para minúsculas
|
|
170
|
+
normalized = normalized.toLowerCase();
|
|
171
|
+
|
|
172
|
+
return normalized.substring(0, NORMALIZATION_CONFIG.state.maxLength);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Normaliza CEP para Meta AM
|
|
177
|
+
*
|
|
178
|
+
* @param {string} zip - CEP bruto
|
|
179
|
+
* @returns {string} CEP normalizado (apenas números)
|
|
180
|
+
*/
|
|
181
|
+
function normalizeZip(zip) {
|
|
182
|
+
if (!zip || typeof zip !== 'string') return '';
|
|
183
|
+
|
|
184
|
+
// Remove tudo que não é número
|
|
185
|
+
const normalized = zip.replace(/\D/g, '');
|
|
186
|
+
|
|
187
|
+
return normalized.substring(0, NORMALIZATION_CONFIG.zip.maxLength);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Normaliza data de nascimento para Meta AM
|
|
192
|
+
*
|
|
193
|
+
* Formato esperado: YYYYMMDD
|
|
194
|
+
*
|
|
195
|
+
* @param {string} dob - Data de nascimento bruta
|
|
196
|
+
* @returns {string} Data normalizada (YYYYMMDD)
|
|
197
|
+
*/
|
|
198
|
+
function normalizeDOB(dob) {
|
|
199
|
+
if (!dob || typeof dob !== 'string') return '';
|
|
200
|
+
|
|
201
|
+
// Tenta diferentes formatos
|
|
202
|
+
const formats = [
|
|
203
|
+
/(\d{4})-(\d{2})-(\d{2})/, // YYYY-MM-DD
|
|
204
|
+
/(\d{2})\/(\d{2})\/(\d{4})/, // DD/MM/YYYY
|
|
205
|
+
/(\d{2})-(\d{2})-(\d{4})/ // DD-MM-YYYY
|
|
206
|
+
];
|
|
207
|
+
|
|
208
|
+
for (const format of formats) {
|
|
209
|
+
const match = dob.match(format);
|
|
210
|
+
if (match) {
|
|
211
|
+
let year, month, day;
|
|
212
|
+
if (match[1].length === 4) {
|
|
213
|
+
// YYYY-MM-DD
|
|
214
|
+
[year, month, day] = [match[1], match[2], match[3]];
|
|
215
|
+
} else {
|
|
216
|
+
// DD/MM/YYYY
|
|
217
|
+
[day, month, year] = [match[1], match[2], match[3]];
|
|
218
|
+
}
|
|
219
|
+
return `${year}${month}${day}`;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return '';
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// ── Browser-Side: Dados Crus para Worker ───────────
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Extrai dados PII de um formulário HTML para Advanced Matching
|
|
230
|
+
*
|
|
231
|
+
* @param {HTMLFormElement} form - Elemento do formulário
|
|
232
|
+
* @returns {object} Dados normalizados para envio ao Worker
|
|
233
|
+
*/
|
|
234
|
+
export function extractFormPII(form) {
|
|
235
|
+
if (!form || typeof form.elements !== 'object') return {};
|
|
236
|
+
|
|
237
|
+
const formData = new FormData(form);
|
|
238
|
+
const piiData = {};
|
|
239
|
+
|
|
240
|
+
// Campo de e-mail
|
|
241
|
+
const email = formData.get('email') || formData.get('user_email') || formData.get('useremail');
|
|
242
|
+
if (email) piiData.email = normalizeEmail(email);
|
|
243
|
+
|
|
244
|
+
// Campo de telefone
|
|
245
|
+
const phone = formData.get('phone') || formData.get('telephone') || formData.get('telefone') || formData.get('celular');
|
|
246
|
+
if (phone) piiData.phone = normalizePhone(phone);
|
|
247
|
+
|
|
248
|
+
// Campos de nome
|
|
249
|
+
const firstName = formData.get('first_name') || formData.get('nome') || formData.get('firstname');
|
|
250
|
+
const lastName = formData.get('last_name') || formData.get('sobrenome') || formData.get('lastname');
|
|
251
|
+
const fullName = formData.get('name') || formData.get('fullname') || formData.get('nome_completo');
|
|
252
|
+
|
|
253
|
+
if (fullName) {
|
|
254
|
+
const names = fullName.split(' ');
|
|
255
|
+
piiData.first_name = normalizeName(names[0]);
|
|
256
|
+
piiData.last_name = normalizeName(names.slice(1).join(' '));
|
|
257
|
+
} else {
|
|
258
|
+
if (firstName) piiData.first_name = normalizeName(firstName);
|
|
259
|
+
if (lastName) piiData.last_name = normalizeName(lastName);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Campos de endereço (Meta AM)
|
|
263
|
+
const city = formData.get('city') || formData.get('cidade');
|
|
264
|
+
if (city) piiData.city = normalizeCity(city);
|
|
265
|
+
|
|
266
|
+
const state = formData.get('state') || formData.get('estado') || formData.get('uf');
|
|
267
|
+
if (state) piiData.state = normalizeState(state);
|
|
268
|
+
|
|
269
|
+
const zip = formData.get('zip') || formData.get('cep') || formData.get('postalcode');
|
|
270
|
+
if (zip) piiData.zip = normalizeZip(zip);
|
|
271
|
+
|
|
272
|
+
const dob = formData.get('dob') || formData.get('date_of_birth') || formData.get('nascimento');
|
|
273
|
+
if (dob) piiData.dob = normalizeDOB(dob);
|
|
274
|
+
|
|
275
|
+
return piiData;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Captura dados PII de qualquer elemento (input, button click, etc.)
|
|
280
|
+
*
|
|
281
|
+
* @param {HTMLElement} element - Elemento DOM
|
|
282
|
+
* @returns {object} Dados PII normalizados
|
|
283
|
+
*/
|
|
284
|
+
export function capturePII(element) {
|
|
285
|
+
if (!element) return {};
|
|
286
|
+
|
|
287
|
+
const piiData = {};
|
|
288
|
+
|
|
289
|
+
// Se for input, extrair valor
|
|
290
|
+
if (element.tagName === 'INPUT' || element.tagName === 'TEXTAREA') {
|
|
291
|
+
const name = element.name.toLowerCase();
|
|
292
|
+
const value = element.value;
|
|
293
|
+
|
|
294
|
+
if (name.includes('email') && value) {
|
|
295
|
+
piiData.email = normalizeEmail(value);
|
|
296
|
+
} else if (name.includes('phone') || name.includes('telefone') || name.includes('celular')) {
|
|
297
|
+
if (value) piiData.phone = normalizePhone(value);
|
|
298
|
+
} else if (name.includes('name') || name.includes('nome')) {
|
|
299
|
+
if (value) piiData.full_name = normalizeName(value);
|
|
300
|
+
} else if (name.includes('city') || name.includes('cidade')) {
|
|
301
|
+
if (value) piiData.city = normalizeCity(value);
|
|
302
|
+
} else if (name.includes('state') || name.includes('estado')) {
|
|
303
|
+
if (value) piiData.state = normalizeState(value);
|
|
304
|
+
} else if (name.includes('zip') || name.includes('cep')) {
|
|
305
|
+
if (value) piiData.zip = normalizeZip(value);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
return piiData;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Valida se os dados PII são válidos para envio
|
|
314
|
+
*
|
|
315
|
+
* @param {object} piiData - Dados PII normalizados
|
|
316
|
+
* @returns {boolean} True se tiver dados válidos
|
|
317
|
+
*/
|
|
318
|
+
export function isValidPII(piiData) {
|
|
319
|
+
if (!piiData || typeof piiData !== 'object') return false;
|
|
320
|
+
|
|
321
|
+
const hasEmail = piiData.email && piiData.email.includes('@');
|
|
322
|
+
const hasPhone = piiData.phone && piiData.phone.length >= 10;
|
|
323
|
+
|
|
324
|
+
return hasEmail || hasPhone;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// ── Browser-Side: Hashing (Opcional - para debug) ────────
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Calcula SHA256 de um valor (browser-side)
|
|
331
|
+
*
|
|
332
|
+
* AVISO: Para máxima segurança, o hashing deve ser feito no Worker.
|
|
333
|
+
* Esta função é apenas para debug/visualização.
|
|
334
|
+
*
|
|
335
|
+
* @param {string} value - Valor para hash
|
|
336
|
+
* @returns {Promise<string>} Hash SHA256 em hexadecimal
|
|
337
|
+
*/
|
|
338
|
+
export async function hashPII(value) {
|
|
339
|
+
if (!value || !isBrowser) return '';
|
|
340
|
+
|
|
341
|
+
try {
|
|
342
|
+
const encoder = new TextEncoder();
|
|
343
|
+
const data = encoder.encode(value);
|
|
344
|
+
const hashBuffer = await crypto.subtle.digest('SHA-256', data);
|
|
345
|
+
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
346
|
+
return hashArray.map(byte => byte.toString(16).padStart(2, '0')).join('');
|
|
347
|
+
} catch (error) {
|
|
348
|
+
console.error('Erro ao calcular SHA256 no browser:', error);
|
|
349
|
+
return '';
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// ── Exportações ────────────────────────────────────────────────
|
|
354
|
+
|
|
355
|
+
export {
|
|
356
|
+
normalizeEmail,
|
|
357
|
+
normalizePhone,
|
|
358
|
+
normalizeName,
|
|
359
|
+
normalizeCity,
|
|
360
|
+
normalizeState,
|
|
361
|
+
normalizeZip,
|
|
362
|
+
normalizeDOB,
|
|
363
|
+
NORMALIZATION_CONFIG
|
|
364
|
+
};
|