cdp-edge 2.2.5 → 2.3.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.
@@ -5,9 +5,49 @@
5
5
  */
6
6
 
7
7
  import { sha256, normalizePhone, normalizeCity } from './utils.js';
8
+ import { Env, TrackPayload } from '../types.js';
9
+ import { D1Database } from '@cloudflare/workers-types';
10
+
11
+ // ── Tipos ───────────────────────────────────────────────────────────────────────
12
+ export interface GeoData {
13
+ country: string | null;
14
+ continent: string | null;
15
+ asn: string | null;
16
+ asOrg: string | null;
17
+ colo: string | null;
18
+ city: string | null;
19
+ region: string | null;
20
+ regionCode: string | null;
21
+ postalCode: string | null;
22
+ latitude: number | null;
23
+ longitude: number | null;
24
+ timezone: string | null;
25
+ metroCode: string | null;
26
+ }
27
+
28
+ export interface LtvResult {
29
+ value: number;
30
+ class: string;
31
+ score?: number;
32
+ }
33
+
34
+ export interface HealthMetrics {
35
+ platform: string;
36
+ hours: number;
37
+ events_sent: number;
38
+ events_failed: number;
39
+ success_rate: number;
40
+ errors_detected: Array<{ code: string; count: number }>;
41
+ issues: string[];
42
+ }
43
+
44
+ export interface DailyReport {
45
+ platform: string;
46
+ status: string;
47
+ }
8
48
 
9
49
  // ── saveLead — inserir evento de conversão ────────────────────────────────────
10
- export async function saveLead(env, eventName, payload, request, platform = 'website') {
50
+ export async function saveLead(env: Env, eventName: string, payload: TrackPayload, request: Request, platform: string = 'website'): Promise<void> {
11
51
  if (!env.DB) return;
12
52
  try {
13
53
  const {
@@ -36,7 +76,7 @@ export async function saveLead(env, eventName, payload, request, platform = 'web
36
76
  lastName || null,
37
77
  city || null,
38
78
  state || null,
39
- (country || request.cf?.country || null),
79
+ (country || (request as any).cf?.country || null),
40
80
  fbp || null,
41
81
  fbc || null,
42
82
  userId || null,
@@ -46,22 +86,22 @@ export async function saveLead(env, eventName, payload, request, platform = 'web
46
86
  utmContent || null,
47
87
  utmTerm || null,
48
88
  pageUrl || null,
49
- value !== undefined ? parseFloat(value) : null,
89
+ value !== undefined ? parseFloat(String(value)) : null,
50
90
  currency || 'BRL',
51
91
  request.headers.get('CF-Connecting-IP') || null,
52
92
  platform,
53
93
  botScore || 0,
54
- engagementScore !== undefined ? parseFloat(engagementScore) : null,
94
+ engagementScore !== undefined ? parseFloat(String(engagementScore)) : null,
55
95
  intentionLevel || null,
56
96
  utmRestored ? 1 : 0,
57
97
  ).run();
58
- } catch (err) {
59
- console.error('D1 saveLead error:', err.message);
98
+ } catch (err: any) {
99
+ console.error('D1 saveLead error:', err?.message || String(err));
60
100
  }
61
101
  }
62
102
 
63
103
  // ── calculateCohortLabel ──────────────────────────────────────────────────────
64
- export function calculateCohortLabel(score, eventName) {
104
+ export function calculateCohortLabel(score: number, eventName: string): string {
65
105
  if (eventName === 'Purchase') return 'buyer_lookalike';
66
106
  if (score >= 80) return 'high_intent';
67
107
  if (score >= 30) return 'nurture';
@@ -69,7 +109,7 @@ export function calculateCohortLabel(score, eventName) {
69
109
  }
70
110
 
71
111
  // ── upsertProfile — acumula cookies/scores entre visitas ─────────────────────
72
- export async function upsertProfile(env, eventName, payload, request) {
112
+ export async function upsertProfile(env: Env, eventName: string, payload: TrackPayload, request: Request): Promise<void> {
73
113
  if (!env.DB || !payload.userId) return;
74
114
  try {
75
115
  const {
@@ -79,7 +119,7 @@ export async function upsertProfile(env, eventName, payload, request) {
79
119
  engagementScore, userScore,
80
120
  } = payload;
81
121
 
82
- const scoreMap = { PageView: 5, ViewContent: 10, ScrollDepth: 3, TimeOnPage: 5, Lead: 30, InitiateCheckout: 50, Purchase: 100 };
122
+ const scoreMap: Record<string, number> = { PageView: 5, ViewContent: 10, ScrollDepth: 3, TimeOnPage: 5, Lead: 30, InitiateCheckout: 50, Purchase: 100 };
83
123
  const eventScore = scoreMap[eventName] || 2;
84
124
 
85
125
  const behaviorBonus = userScore
@@ -120,23 +160,23 @@ export async function upsertProfile(env, eventName, payload, request) {
120
160
  gaClientId || null,
121
161
  city || null,
122
162
  state || null,
123
- (country || request.cf?.country || null),
163
+ (country || (request as any).cf?.country || null),
124
164
  totalDelta,
125
165
  calculateCohortLabel(totalDelta, eventName),
126
166
  ).run();
127
- } catch (err) {
128
- console.error('D1 upsertProfile error:', err.message);
167
+ } catch (err: any) {
168
+ console.error('D1 upsertProfile error:', err?.message || String(err));
129
169
  }
130
170
  }
131
171
 
132
172
  // ── resolveDeviceGraph — Cross-Device Identity ────────────────────────────────
133
- export async function resolveDeviceGraph(DB, currentUserId, email, phone) {
173
+ export async function resolveDeviceGraph(DB: D1Database, currentUserId: string, email?: string | null, phone?: string | null): Promise<void> {
134
174
  if (!DB || !currentUserId) return;
135
175
  if (!email && !phone) return;
136
176
 
137
177
  try {
138
- const conditions = [];
139
- const bindings = [];
178
+ const conditions: string[] = [];
179
+ const bindings: (string | number)[] = [];
140
180
 
141
181
  if (email) {
142
182
  conditions.push('email = ?');
@@ -166,7 +206,7 @@ export async function resolveDeviceGraph(DB, currentUserId, email, phone) {
166
206
 
167
207
  for (const match of rows.results) {
168
208
  const emailMatch = email && match.email &&
169
- email.toLowerCase().trim() === match.email.toLowerCase().trim();
209
+ email.toLowerCase().trim() === (match.email as string).toLowerCase().trim();
170
210
  const phoneMatch = phone && match.phone && (() => {
171
211
  const a = String(phone).replace(/\D/g, '');
172
212
  const b = String(match.phone).replace(/\D/g, '');
@@ -177,7 +217,7 @@ export async function resolveDeviceGraph(DB, currentUserId, email, phone) {
177
217
 
178
218
  const matchType = emailMatch && phoneMatch ? 'email+phone' : (emailMatch ? 'email' : 'phone');
179
219
  const matchConfidence = emailMatch && phoneMatch ? 0.99 : (emailMatch ? 0.95 : 0.85);
180
- const primary = match.user_id;
220
+ const primary = match.user_id as string;
181
221
  const secondary = currentUserId;
182
222
 
183
223
  await DB.prepare(`
@@ -188,13 +228,13 @@ export async function resolveDeviceGraph(DB, currentUserId, email, phone) {
188
228
 
189
229
  console.log(`[DeviceGraph] Linked ${secondary} → ${primary} via ${matchType} (confidence: ${matchConfidence})`);
190
230
  }
191
- } catch (err) {
192
- console.error('resolveDeviceGraph error:', err.message);
231
+ } catch (err: any) {
232
+ console.error('resolveDeviceGraph error:', err?.message || String(err));
193
233
  }
194
234
  }
195
235
 
196
236
  // ── fireAutomation — dispara regras de automação (WA/Email) ──────────────────
197
- export async function fireAutomation(env, eventName, leadId, payload) {
237
+ export async function fireAutomation(env: Env, eventName: string, leadId: number | null, payload: TrackPayload): Promise<void> {
198
238
  if (!env.DB) return;
199
239
 
200
240
  try {
@@ -209,20 +249,20 @@ export async function fireAutomation(env, eventName, leadId, payload) {
209
249
 
210
250
  if (!rules || rules.length === 0) return;
211
251
 
212
- const vars = {
213
- name: String(payload.firstName || payload.name || ''),
252
+ const vars: Record<string, string> = {
253
+ name: String(payload.firstName || (payload as any).name || ''),
214
254
  email: String(payload.email || ''),
215
255
  phone: String(payload.phone || ''),
216
- campaign: String(payload.utm_campaign || payload.utmCampaign || ''),
256
+ campaign: String(payload.utmCampaign || payload.utm_campaign || ''),
217
257
  intention: String(payload.intentionLevel || payload.intention_level || ''),
218
258
  };
219
259
 
220
- const interpolate = (tpl) =>
260
+ const interpolate = (tpl: string) =>
221
261
  tpl.replace(/\{\{(\w+)\}\}/g, (_, k) => vars[k] ?? '');
222
262
 
223
263
  for (const rule of rules) {
224
- const message = interpolate(rule.message_template);
225
- const subject = rule.subject_template ? interpolate(rule.subject_template) : null;
264
+ const message = interpolate(rule.message_template as string);
265
+ const subject = rule.subject_template ? interpolate(rule.subject_template as string) : null;
226
266
 
227
267
  try {
228
268
  if (rule.channel === 'whatsapp' && payload.phone && env.WHATSAPP_ACCESS_TOKEN && env.WHATSAPP_PHONE_NUMBER_ID) {
@@ -238,7 +278,7 @@ export async function fireAutomation(env, eventName, leadId, payload) {
238
278
  );
239
279
  const waData = await waRes.json();
240
280
  const status = waRes.ok ? 'sent' : 'failed';
241
- const meta = waRes.ok ? (waData.messages?.[0]?.id ?? null) : JSON.stringify(waData);
281
+ const meta = waRes.ok ? ((waData as any).messages?.[0]?.id ?? null) : JSON.stringify(waData);
242
282
  await env.DB.prepare(
243
283
  `INSERT INTO messaging_history (lead_id, channel, recipient, subject, content, status, meta) VALUES (?1,?2,?3,?4,?5,?6,?7)`
244
284
  ).bind(leadId, 'whatsapp', e164, null, message, status, meta).run();
@@ -256,22 +296,22 @@ export async function fireAutomation(env, eventName, leadId, payload) {
256
296
  });
257
297
  const resendData = await resendRes.json();
258
298
  const status = resendRes.ok ? 'sent' : 'failed';
259
- const meta = resendRes.ok ? (resendData.id ?? null) : JSON.stringify(resendData);
299
+ const meta = resendRes.ok ? ((resendData as any).id ?? null) : JSON.stringify(resendData);
260
300
  await env.DB.prepare(
261
301
  `INSERT INTO messaging_history (lead_id, channel, recipient, subject, content, status, meta) VALUES (?1,?2,?3,?4,?5,?6,?7)`
262
302
  ).bind(leadId, 'email', payload.email, subject, message, status, meta).run();
263
303
  }
264
- } catch (err) {
265
- console.error(`[Automation] rule ${rule.id} error:`, err.message);
304
+ } catch (err: any) {
305
+ console.error(`[Automation] rule ${(rule as any).id} error:`, err?.message || String(err));
266
306
  }
267
307
  }
268
- } catch (err) {
269
- console.error('[Automation] fireAutomation error:', err.message);
308
+ } catch (err: any) {
309
+ console.error('[Automation] fireAutomation error:', err?.message || String(err));
270
310
  }
271
311
  }
272
312
 
273
313
  // ── getProfileByEmail ─────────────────────────────────────────────────────────
274
- export async function getProfileByEmail(env, email) {
314
+ export async function getProfileByEmail(env: Env, email: string): Promise<any | null> {
275
315
  if (!env.DB || !email) return null;
276
316
  try {
277
317
  return await env.DB.prepare(
@@ -283,14 +323,14 @@ export async function getProfileByEmail(env, email) {
283
323
  }
284
324
 
285
325
  // ── enrichGeoFromEdge — enriquece payload com dados de geolocalização ─────────
286
- export async function enrichGeoFromEdge(request, env, payload) {
287
- const cf = request.cf || {};
326
+ export async function enrichGeoFromEdge(request: Request, env: Env, payload: TrackPayload): Promise<GeoData> {
327
+ const cf = (request as any).cf || {};
288
328
  const ip = request.headers.get('CF-Connecting-IP') || '';
289
329
 
290
- let geoData = null;
330
+ let geoData: GeoData | null = null;
291
331
  if (env.GEO_CACHE && ip) {
292
332
  try {
293
- const cached = await env.GEO_CACHE.get(`geo:${ip}`, 'json');
333
+ const cached = await env.GEO_CACHE.get(`geo:${ip}`, 'json') as GeoData | null;
294
334
  if (cached) geoData = cached;
295
335
  } catch {}
296
336
  }
@@ -323,13 +363,13 @@ export async function enrichGeoFromEdge(request, env, payload) {
323
363
  payload.city = payload.city || geoData.city;
324
364
  payload.state = payload.state || geoData.regionCode;
325
365
  payload.zip = payload.zip || geoData.postalCode;
326
- payload.geo = geoData;
366
+ (payload as any).geo = geoData;
327
367
 
328
368
  return geoData;
329
369
  }
330
370
 
331
371
  // ── writeAuditLog — grava evento no R2 ───────────────────────────────────────
332
- export async function writeAuditLog(env, eventName, payload, geoData) {
372
+ export async function writeAuditLog(env: Env, eventName: string, payload: TrackPayload, geoData: GeoData | null): Promise<void> {
333
373
  if (!env.AUDIT_LOGS) return;
334
374
  try {
335
375
  const now = new Date();
@@ -360,14 +400,14 @@ export async function writeAuditLog(env, eventName, payload, geoData) {
360
400
  await env.AUDIT_LOGS.put(key, JSON.stringify(log), {
361
401
  httpMetadata: { contentType: 'application/json' },
362
402
  });
363
- } catch (err) {
364
- console.error('[R2 Audit] Error:', err.message);
403
+ } catch (err: any) {
404
+ console.error('[R2 Audit] Error:', err?.message || String(err));
365
405
  }
366
406
  }
367
407
 
368
408
  // ── generateEdgeFingerprint ───────────────────────────────────────────────────
369
- export async function generateEdgeFingerprint(request) {
370
- const asn = String(request.cf?.asn || '0');
409
+ export async function generateEdgeFingerprint(request: Request): Promise<string | undefined> {
410
+ const asn = String((request as any).cf?.asn || '0');
371
411
  const lang = (request.headers.get('Accept-Language') || 'unknown').split(',')[0].trim();
372
412
  const ua = request.headers.get('User-Agent') || '';
373
413
 
@@ -386,7 +426,7 @@ export async function generateEdgeFingerprint(request) {
386
426
  }
387
427
 
388
428
  // ── saveEdgeFingerprint ───────────────────────────────────────────────────────
389
- export async function saveEdgeFingerprint(DB, fingerprint, userId, payload) {
429
+ export async function saveEdgeFingerprint(DB: D1Database, fingerprint: string | undefined, userId: string | undefined, payload: TrackPayload): Promise<void> {
390
430
  if (!DB || !fingerprint) return;
391
431
  const { utmSource, utmMedium, utmCampaign, utmContent, utmTerm } = payload;
392
432
  if (!utmSource) return;
@@ -404,13 +444,13 @@ export async function saveEdgeFingerprint(DB, fingerprint, userId, payload) {
404
444
  utmContent || null,
405
445
  utmTerm || null,
406
446
  ).run();
407
- } catch (err) {
408
- console.error('saveEdgeFingerprint error:', err.message);
447
+ } catch (err: any) {
448
+ console.error('saveEdgeFingerprint error:', err?.message || String(err));
409
449
  }
410
450
  }
411
451
 
412
452
  // ── resurrectUTM ──────────────────────────────────────────────────────────────
413
- export async function resurrectUTM(DB, fingerprint) {
453
+ export async function resurrectUTM(DB: D1Database, fingerprint: string | undefined): Promise<any | null> {
414
454
  if (!DB || !fingerprint) return null;
415
455
  try {
416
456
  return await DB.prepare(`
@@ -428,7 +468,7 @@ export async function resurrectUTM(DB, fingerprint) {
428
468
  }
429
469
 
430
470
  // ── upsertLtvProfile — persiste LTV no perfil ────────────────────────────────
431
- export async function upsertLtvProfile(env, userId, ltv) {
471
+ export async function upsertLtvProfile(env: Env, userId: string, ltv: LtvResult): Promise<void> {
432
472
  if (!env.DB || !userId) return;
433
473
  try {
434
474
  await env.DB.prepare(`
@@ -438,15 +478,15 @@ export async function upsertLtvProfile(env, userId, ltv) {
438
478
  updated_at = datetime('now')
439
479
  WHERE user_id = ?
440
480
  `).bind(ltv.class, ltv.value, userId).run();
441
- } catch (err) {
442
- console.error('upsertLtvProfile error:', err.message);
481
+ } catch (err: any) {
482
+ console.error('upsertLtvProfile error:', err?.message || String(err));
443
483
  }
444
484
  }
445
485
 
446
486
  // ── recordLtvFeedback — fecha o ciclo preditivo com valor real de compra ─────
447
487
  // Chamado em background quando um Purchase chega com payload.value > 0.
448
488
  // Atualiza user_profiles + ltv_ab_assignments + ltv_ab_variations em cascata.
449
- export async function recordLtvFeedback(env, userId, realValue) {
489
+ export async function recordLtvFeedback(env: Env, userId: string, realValue: number): Promise<void> {
450
490
  if (!env.DB || !userId || !realValue || realValue <= 0) return;
451
491
 
452
492
  try {
@@ -458,7 +498,7 @@ export async function recordLtvFeedback(env, userId, realValue) {
458
498
  // accuracy = 1 - |pred-real|/real (0–1, mesmo padrão do A/B test accuracy_score)
459
499
  const predictedValue = profile?.predicted_ltv_value;
460
500
  const ltv_accuracy = (predictedValue !== null && predictedValue !== undefined)
461
- ? Math.max(0, Math.round((1 - Math.abs(predictedValue - realValue) / Math.max(realValue, 1)) * 100) / 100)
501
+ ? Math.max(0, Math.round((1 - Math.abs(Number(predictedValue) - realValue) / Math.max(realValue, 1)) * 100) / 100)
462
502
  : null;
463
503
 
464
504
  // 2. Grava valor real + accuracy no perfil
@@ -491,10 +531,10 @@ export async function recordLtvFeedback(env, userId, realValue) {
491
531
  real_revenue = ?,
492
532
  converted_at = datetime('now')
493
533
  WHERE id = ?
494
- `).bind(realValue, assignment.id).run();
534
+ `).bind(realValue, (assignment as any).id).run();
495
535
 
496
536
  // 3b. Atualiza métricas acumuladas da variação (running average — safe para concorrência D1)
497
- const predLtv = assignment.predicted_ltv || 0;
537
+ const predLtv = (assignment as any).predicted_ltv || 0;
498
538
  const indivAcc = Math.max(0, 1 - Math.abs(predLtv - realValue) / Math.max(realValue, 1));
499
539
 
500
540
  await env.DB.prepare(`
@@ -507,27 +547,27 @@ export async function recordLtvFeedback(env, userId, realValue) {
507
547
  4
508
548
  )
509
549
  WHERE id = ?
510
- `).bind(realValue, realValue, indivAcc, assignment.variation_id).run();
550
+ `).bind(realValue, realValue, indivAcc, (assignment as any).variation_id).run();
511
551
 
512
- } catch (err) {
513
- console.error('[LTV-Feedback] recordLtvFeedback error:', err.message);
552
+ } catch (err: any) {
553
+ console.error('[LTV-Feedback] recordLtvFeedback error:', err?.message || String(err));
514
554
  }
515
555
  }
516
556
 
517
557
  // ── Feedback Loop — Log de falhas e métricas de saúde ────────────────────────
518
558
 
519
- export async function logApiFailure(DB, platform, eventName, errorCode, errorMessage, eventId, rawPayload) {
559
+ export async function logApiFailure(DB: D1Database, platform: string, eventName: string, errorCode: string | number, errorMessage: string, eventId: string, rawPayload: string): Promise<void> {
520
560
  try {
521
561
  await DB.prepare(`
522
562
  INSERT INTO api_failures (platform, event_name, error_code, error_message, event_id, raw_payload, retry_count, final_status)
523
563
  VALUES (?, ?, ?, ?, ?, ?, 0, 'failed')
524
564
  `).bind(platform, eventName, String(errorCode), errorMessage, eventId, rawPayload).run();
525
- } catch (err) {
526
- console.error('Failed to log API failure:', err.message);
565
+ } catch (err: any) {
566
+ console.error('Failed to log API failure:', err?.message || String(err));
527
567
  }
528
568
  }
529
569
 
530
- export async function getHealthMetrics(DB, platform, hours = 24) {
570
+ export async function getHealthMetrics(DB: D1Database, platform: string, hours: number = 24): Promise<HealthMetrics> {
531
571
  try {
532
572
  const failures = await DB.prepare(`
533
573
  SELECT COUNT(*) as count, error_code
@@ -542,30 +582,30 @@ export async function getHealthMetrics(DB, platform, hours = 24) {
542
582
  WHERE platform = ? AND created_at > datetime('now', '-${hours} hours')
543
583
  `).bind(platform).first();
544
584
 
545
- const totalFailed = failures.results?.reduce((sum, f) => sum + f.count, 0) || 0;
546
- const successRate = totalSent?.count > 0
547
- ? ((totalSent.count - totalFailed) / totalSent.count) * 100
585
+ const totalFailed = failures.results?.reduce((sum: number, f: any) => sum + f.count, 0) || 0;
586
+ const successRate = (totalSent as any)?.count > 0
587
+ ? (((totalSent as any).count - totalFailed) / (totalSent as any).count) * 100
548
588
  : 100;
549
589
 
550
590
  return {
551
591
  platform,
552
592
  hours,
553
- events_sent: totalSent?.count || 0,
593
+ events_sent: (totalSent as any)?.count || 0,
554
594
  events_failed: totalFailed,
555
595
  success_rate: successRate,
556
- errors_detected: (failures.results || []).map(f => ({ code: f.error_code, count: f.count })),
557
- issues: totalFailed > (totalSent?.count || 0) * 0.1 ? ['high_error_rate'] : [],
596
+ errors_detected: (failures.results || []).map((f: any) => ({ code: f.error_code, count: f.count })),
597
+ issues: totalFailed > ((totalSent as any)?.count || 0) * 0.1 ? ['high_error_rate'] : [],
558
598
  };
559
- } catch (err) {
560
- console.error('Failed to get health metrics:', err.message);
599
+ } catch (err: any) {
600
+ console.error('Failed to get health metrics:', err?.message || String(err));
561
601
  return { platform, hours, events_sent: 0, events_failed: 0, success_rate: 0, errors_detected: [], issues: ['metrics_unavailable'] };
562
602
  }
563
603
  }
564
604
 
565
- export async function generateDailyReport(DB) {
605
+ export async function generateDailyReport(DB: D1Database): Promise<DailyReport[]> {
566
606
  const platforms = ['meta', 'ga4', 'tiktok', 'pinterest', 'reddit'];
567
607
  const today = new Date().toISOString().split('T')[0];
568
- const reports = [];
608
+ const reports: DailyReport[] = [];
569
609
 
570
610
  for (const platform of platforms) {
571
611
  const metrics = await getHealthMetrics(DB, platform, 24);
@@ -580,8 +620,8 @@ export async function generateDailyReport(DB) {
580
620
  JSON.stringify(metrics.errors_detected), JSON.stringify(metrics.issues)
581
621
  ).run();
582
622
  reports.push({ platform, status: 'ok' });
583
- } catch (err) {
584
- console.error(`Failed to generate report for ${platform}:`, err.message);
623
+ } catch (err: any) {
624
+ console.error(`Failed to generate report for ${platform}:`, err?.message || String(err));
585
625
  reports.push({ platform, status: 'failed' });
586
626
  }
587
627
  }
@@ -589,14 +629,14 @@ export async function generateDailyReport(DB) {
589
629
  return reports;
590
630
  }
591
631
 
592
- export async function logIntelligence(DB, runType, platform, checkType, status, currentValue, expectedValue, message, alertSent = false) {
632
+ export async function logIntelligence(DB: D1Database, runType: string, platform: string, checkType: string, status: string, currentValue: any, expectedValue: any, message: string, alertSent: boolean = false): Promise<void> {
593
633
  if (!DB) return;
594
634
  try {
595
635
  await DB.prepare(`
596
636
  INSERT INTO intelligence_logs (run_type, platform, check_type, status, current_value, expected_value, message, alert_sent)
597
637
  VALUES (?, ?, ?, ?, ?, ?, ?, ?)
598
638
  `).bind(runType, platform, checkType, status, String(currentValue ?? ''), String(expectedValue ?? ''), message, alertSent ? 1 : 0).run();
599
- } catch (err) {
600
- console.error('logIntelligence error:', err.message);
639
+ } catch (err: any) {
640
+ console.error('logIntelligence error:', err?.message || String(err));
601
641
  }
602
642
  }
@@ -5,12 +5,14 @@
5
5
 
6
6
  import { normalizePhone } from '../utils.js';
7
7
  import { logApiFailure } from '../db.js';
8
+ import { Env, TrackPayload } from '../../types.js';
9
+ import { ExecutionContext } from '@cloudflare/workers-types';
8
10
 
9
- export async function sendGA4Mp(env, ga4EventName, payload, ctx) {
11
+ export async function sendGA4Mp(env: Env, ga4EventName: string, payload: TrackPayload, ctx: ExecutionContext | null): Promise<{ ok?: boolean; status?: number; skipped?: string; error?: string }> {
10
12
  if (!env.GA4_API_SECRET) return { skipped: 'GA4_API_SECRET not set' };
11
13
 
12
14
  const {
13
- clientId, sessionId,
15
+ gaClientId: clientId, sessionId,
14
16
  value, currency, contentName,
15
17
  email, phone, firstName,
16
18
  orderId,
@@ -18,13 +20,13 @@ export async function sendGA4Mp(env, ga4EventName, payload, ctx) {
18
20
 
19
21
  if (!clientId) return { skipped: 'no clientId' };
20
22
 
21
- const eventParams = {
22
- ...(value !== undefined && { value: parseFloat(value) }),
23
+ const eventParams: Record<string, string | number> = {
24
+ ...(value !== undefined && { value: parseFloat(String(value)) }),
23
25
  ...(currency && { currency: String(currency).toUpperCase() }),
24
26
  ...(contentName && { content_name: contentName }),
25
27
  ...(orderId && { transaction_id: orderId }),
26
28
  ...(email && { user_data_email_address: email.toLowerCase().trim() }),
27
- ...(phone && { user_data_phone_number: normalizePhone(phone) }),
29
+ ...(phone && { user_data_phone_number: normalizePhone(phone) || '' }),
28
30
  ...(firstName && { user_data_first_name: firstName.toLowerCase().trim() }),
29
31
  ...(sessionId && { session_id: sessionId }),
30
32
  engagement_time_msec: 100,
@@ -48,18 +50,18 @@ export async function sendGA4Mp(env, ga4EventName, payload, ctx) {
48
50
 
49
51
  if (res.status !== 204) {
50
52
  if (env.DB && ctx) {
51
- ctx.waitUntil(logApiFailure(env.DB, 'ga4', ga4EventName, String(res.status), 'GA4 returned non-204 status', null, JSON.stringify(body)));
53
+ ctx.waitUntil(logApiFailure(env.DB, 'ga4', ga4EventName, String(res.status), 'GA4 returned non-204 status', '', JSON.stringify(body)));
52
54
  }
53
55
  }
54
56
 
55
57
  return res.status === 204 ? { ok: true } : { status: res.status };
56
- } catch (err) {
57
- console.error('GA4 MP fetch failed:', err.message);
58
+ } catch (err: any) {
59
+ console.error('GA4 MP fetch failed:', err?.message || String(err));
58
60
 
59
61
  if (env.DB && ctx) {
60
- ctx.waitUntil(logApiFailure(env.DB, 'ga4', ga4EventName, 'FETCH_ERROR', err.message, null, JSON.stringify(body)));
62
+ ctx.waitUntil(logApiFailure(env.DB, 'ga4', ga4EventName, 'FETCH_ERROR', err?.message || String(err), '', JSON.stringify(body)));
61
63
  }
62
64
 
63
- return { error: err.message };
65
+ return { error: err?.message || String(err) };
64
66
  }
65
67
  }
@@ -6,12 +6,19 @@
6
6
  import { sha256, normalizePhone, normalizeCity } from '../utils.js';
7
7
  import { logApiFailure } from '../db.js';
8
8
  import { logMatchQuality, autoEnrichPayload } from '../ml/matchquality.js';
9
+ import { Env, TrackPayload } from '../../types.js';
10
+ import { ExecutionContext } from '@cloudflare/workers-types';
9
11
 
10
- export async function sendMetaCapi(env, eventName, payload, request, ctx) {
12
+ interface EnrichedPayload {
13
+ payload: TrackPayload;
14
+ recovered: { email: boolean; utm: boolean };
15
+ }
16
+
17
+ export async function sendMetaCapi(env: Env, eventName: string, payload: TrackPayload, request: Request | null, ctx: ExecutionContext | null): Promise<any> {
11
18
  // Auto-enriquecer payload com dados do Identity Graph antes do envio
12
19
  let recovered = { email: false, utm: false };
13
20
  if (env.DB && payload) {
14
- const enriched = await autoEnrichPayload(env, payload);
21
+ const enriched = await autoEnrichPayload(env, payload) as EnrichedPayload;
15
22
  payload = enriched.payload;
16
23
  recovered = enriched.recovered;
17
24
  }
@@ -25,41 +32,41 @@ export async function sendMetaCapi(env, eventName, payload, request, ctx) {
25
32
  value, currency,
26
33
  contentIds, contentName, contentType, numItems,
27
34
  // Dual-layer context — funil avançado + imóveis
28
- funnel_stage, distance_bucket, intent_score, intent_bucket,
35
+ funnel_stage, distanceBucket: distance_bucket, intentScoreNum: intent_score, intent_bucket,
29
36
  ltvScore, ltvClass, metaSignal, metaSignalBucket: metaSignalBucketVal,
30
37
  } = payload;
31
38
 
32
39
  const phoneNorm = normalizePhone(phone);
33
- const countryCode = (country || request.cf?.country || 'br').toLowerCase();
40
+ const countryCode = (country || (request as any)?.cf?.country || 'br').toLowerCase();
34
41
  const stateCode = state ? String(state).toLowerCase() : undefined;
35
42
  const cityNorm = normalizeCity(city);
36
43
 
37
- const userData = {
38
- ...(email && { em: await sha256(email) }),
39
- ...(phoneNorm && { ph: await sha256(phoneNorm) }),
40
- ...(firstName && { fn: await sha256(firstName) }),
41
- ...(lastName && { ln: await sha256(lastName) }),
42
- ...(cityNorm && { ct: await sha256(cityNorm) }),
43
- ...(stateCode && { st: await sha256(stateCode) }),
44
- ...(countryCode && { country: await sha256(countryCode) }),
45
- ...(userId && { external_id: await sha256(String(userId)) }),
46
- ...(zip && { zp: await sha256(zip) }),
47
- ...(dob && { db: await sha256(dob) }),
44
+ const userData: Record<string, string> = {
45
+ ...(email && { em: await sha256(email) || '' }),
46
+ ...(phoneNorm && { ph: await sha256(phoneNorm) || '' }),
47
+ ...(firstName && { fn: await sha256(firstName) || '' }),
48
+ ...(lastName && { ln: await sha256(lastName) || '' }),
49
+ ...(cityNorm && { ct: await sha256(cityNorm) || '' }),
50
+ ...(stateCode && { st: await sha256(stateCode) || '' }),
51
+ ...(countryCode && { country: await sha256(countryCode) || '' }),
52
+ ...(userId && { external_id: await sha256(String(userId)) || '' }),
53
+ ...(zip && { zp: await sha256(zip) || '' }),
54
+ ...(dob && { db: await sha256(dob) || '' }),
48
55
  ...(fbp && { fbp }),
49
56
  ...(fbc && { fbc }),
50
- client_ip_address: request.headers.get('CF-Connecting-IP')
51
- || request.headers.get('X-Forwarded-For')
57
+ client_ip_address: request?.headers.get('CF-Connecting-IP')
58
+ || request?.headers.get('X-Forwarded-For')
52
59
  || '',
53
- client_user_agent: request.headers.get('User-Agent') || '',
60
+ client_user_agent: request?.headers.get('User-Agent') || '',
54
61
  };
55
62
 
56
- const customData = {
57
- ...(value !== undefined && { value: parseFloat(value) }),
63
+ const customData: Record<string, string | number | string[]> = {
64
+ ...(value !== undefined && { value: parseFloat(String(value)) }),
58
65
  ...(currency && { currency: String(currency).toUpperCase() }),
59
66
  ...(contentIds && contentIds.length > 0 && { content_ids: contentIds }),
60
67
  ...(contentName && { content_name: contentName }),
61
68
  ...(contentType && { content_type: contentType }),
62
- ...(numItems && { num_items: parseInt(numItems) }),
69
+ ...(numItems && { num_items: parseInt(String(numItems)) }),
63
70
  // Contexto de funil e proximidade — enriquece matching e otimização Meta
64
71
  ...(funnel_stage && { funnel_stage }),
65
72
  ...(distance_bucket && { distance_bucket }),
@@ -81,7 +88,7 @@ export async function sendMetaCapi(env, eventName, payload, request, ctx) {
81
88
  ...(Object.keys(customData).length > 0 && { custom_data: customData }),
82
89
  };
83
90
 
84
- const requestBody = {
91
+ const requestBody: Record<string, any> = {
85
92
  data: [eventPayload],
86
93
  access_token: env.META_ACCESS_TOKEN,
87
94
  };
@@ -109,8 +116,8 @@ export async function sendMetaCapi(env, eventName, payload, request, ctx) {
109
116
  const data = await res.json();
110
117
 
111
118
  if (!res.ok) {
112
- const errorCode = data.error?.code || String(res.status);
113
- const errorMessage = data.error?.message || data.error?.error_user_msg || 'Unknown error';
119
+ const errorCode = (data as any).error?.code || String(res.status);
120
+ const errorMessage = (data as any).error?.message || (data as any).error?.error_user_msg || 'Unknown error';
114
121
  console.error('Meta CAPI error:', res.status, errorMessage);
115
122
 
116
123
  if (env.DB && ctx) {
@@ -119,13 +126,13 @@ export async function sendMetaCapi(env, eventName, payload, request, ctx) {
119
126
  }
120
127
 
121
128
  return data;
122
- } catch (err) {
123
- console.error('Meta CAPI fetch failed:', err.message);
129
+ } catch (err: any) {
130
+ console.error('Meta CAPI fetch failed:', err?.message || String(err));
124
131
 
125
132
  if (env.DB && ctx) {
126
- ctx.waitUntil(logApiFailure(env.DB, 'meta', eventName, 'FETCH_ERROR', err.message, eventPayload.event_id, JSON.stringify(requestBody)));
133
+ ctx.waitUntil(logApiFailure(env.DB, 'meta', eventName, 'FETCH_ERROR', err?.message || String(err), eventPayload.event_id, JSON.stringify(requestBody)));
127
134
  }
128
135
 
129
- return { error: err.message };
136
+ return { error: err?.message || String(err) };
130
137
  }
131
138
  }