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.
Files changed (52) hide show
  1. package/README.md +308 -308
  2. package/bin/cdp-edge.js +61 -61
  3. package/dist/commands/analyze.js +52 -52
  4. package/dist/commands/infra.js +54 -54
  5. package/dist/commands/install.js +187 -0
  6. package/dist/commands/server.js +174 -174
  7. package/dist/commands/setup.js +19 -1
  8. package/dist/commands/validate.js +84 -84
  9. package/dist/index.js +12 -12
  10. package/extracted-skill/tracking-events-generator/advanced-matching.js +364 -364
  11. package/extracted-skill/tracking-events-generator/agents/bing-agent.md +8 -0
  12. package/extracted-skill/tracking-events-generator/agents/crm-integration-agent.md +8 -0
  13. package/extracted-skill/tracking-events-generator/agents/database-agent.md +8 -0
  14. package/extracted-skill/tracking-events-generator/agents/devops-agent.md +1 -0
  15. package/extracted-skill/tracking-events-generator/agents/domain-setup-agent.md +8 -0
  16. package/extracted-skill/tracking-events-generator/agents/google-agent.md +8 -0
  17. package/extracted-skill/tracking-events-generator/agents/linkedin-agent.md +8 -0
  18. package/extracted-skill/tracking-events-generator/agents/memory-agent.md +29 -2
  19. package/extracted-skill/tracking-events-generator/agents/meta-agent.md +8 -0
  20. package/extracted-skill/tracking-events-generator/agents/pinterest-agent.md +8 -0
  21. package/extracted-skill/tracking-events-generator/agents/r2-setup-agent.md +8 -0
  22. package/extracted-skill/tracking-events-generator/agents/reddit-agent.md +8 -0
  23. package/extracted-skill/tracking-events-generator/agents/server-tracking.md +1 -0
  24. package/extracted-skill/tracking-events-generator/agents/spotify-agent.md +8 -0
  25. package/extracted-skill/tracking-events-generator/agents/tiktok-agent.md +8 -0
  26. package/extracted-skill/tracking-events-generator/agents/validator-agent.md +4 -0
  27. package/extracted-skill/tracking-events-generator/agents/webhook-agent.md +8 -0
  28. package/extracted-skill/tracking-events-generator/agents/whatsapp-ctwa-setup-agent.md +8 -0
  29. package/extracted-skill/tracking-events-generator/anti-blocking.js +285 -285
  30. package/extracted-skill/tracking-events-generator/cdpTrack.js +641 -641
  31. package/extracted-skill/tracking-events-generator/engagement-scoring.js +226 -226
  32. package/extracted-skill/tracking-events-generator/evals/evals.json +235 -235
  33. package/extracted-skill/tracking-events-generator/integration-test.js +497 -497
  34. package/extracted-skill/tracking-events-generator/micro-events.js +992 -992
  35. package/extracted-skill/tracking-events-generator/models/pinterest/conversions-api-template.js +144 -144
  36. package/extracted-skill/tracking-events-generator/models/pinterest/event-mappings.json +48 -48
  37. package/extracted-skill/tracking-events-generator/models/pinterest/tag-template.js +28 -28
  38. package/extracted-skill/tracking-events-generator/models/reddit/conversions-api-template.js +205 -205
  39. package/extracted-skill/tracking-events-generator/models/reddit/event-mappings.json +56 -56
  40. package/extracted-skill/tracking-events-generator/models/reddit/pixel-template.js +19 -19
  41. package/extracted-skill/tracking-events-generator/models/scenarios/behavior-engine.js +425 -425
  42. package/package.json +76 -76
  43. package/server-edge-tracker/schema.sql +265 -265
  44. package/server-edge-tracker/worker.js +4160 -4160
  45. package/server-edge-tracker/wrangler.toml +103 -103
  46. package/templates/pinterest/conversions-api-template.js +144 -144
  47. package/templates/pinterest/event-mappings.json +48 -48
  48. package/templates/pinterest/tag-template.js +28 -28
  49. package/templates/reddit/conversions-api-template.js +205 -205
  50. package/templates/reddit/event-mappings.json +56 -56
  51. package/templates/reddit/pixel-template.js +19 -19
  52. 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
+ };