cdp-edge 2.2.5 → 2.3.1

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 (24) hide show
  1. package/README.md +57 -2
  2. package/contracts/types.ts +81 -0
  3. package/docs/whatsapp-ctwa.md +3 -2
  4. package/package.json +7 -4
  5. package/server-edge-tracker/.client.env.example +14 -0
  6. package/server-edge-tracker/deploy-client.js +76 -0
  7. package/server-edge-tracker/{index.js → index.ts} +93 -84
  8. package/server-edge-tracker/modules/{db.js → db.ts} +117 -77
  9. package/server-edge-tracker/modules/dispatch/{ga4.js → ga4.ts} +12 -10
  10. package/server-edge-tracker/modules/dispatch/{meta.js → meta.ts} +35 -28
  11. package/server-edge-tracker/modules/dispatch/{platforms.js → platforms.ts} +58 -56
  12. package/server-edge-tracker/modules/dispatch/{tiktok.js → tiktok.ts} +22 -20
  13. package/server-edge-tracker/modules/dispatch/{whatsapp.js → whatsapp.ts} +74 -28
  14. package/server-edge-tracker/modules/{intelligence.js → intelligence.ts} +175 -60
  15. package/server-edge-tracker/modules/ml/{bidding.js → bidding.ts} +37 -35
  16. package/server-edge-tracker/modules/ml/{fraud.js → fraud.ts} +48 -40
  17. package/server-edge-tracker/modules/ml/{logistic.js → logistic.ts} +44 -19
  18. package/server-edge-tracker/modules/ml/{ltv.js → ltv.ts} +135 -90
  19. package/server-edge-tracker/modules/ml/{matchquality.js → matchquality.ts} +70 -26
  20. package/server-edge-tracker/modules/ml/{segmentation.js → segmentation.ts} +109 -48
  21. package/server-edge-tracker/modules/{utils.js → utils.ts} +41 -22
  22. package/server-edge-tracker/types.ts +255 -0
  23. package/server-edge-tracker/wrangler.toml +2 -2
  24. package/docs/PixelBuilder-Documentacao-Completa (2).docx +0 -0
@@ -1,17 +1,19 @@
1
1
  /**
2
- * CDP Edge — index.js (ES Module Entry Point)
2
+ * CDP Edge — index.ts (ES Module Entry Point)
3
3
  *
4
4
  * Este arquivo é o novo entry point modular do Worker.
5
5
  * Para usá-lo, altere em wrangler.toml:
6
- * main = "worker.js" → main = "index.js"
6
+ * main = "worker.js" → main = "index.ts"
7
7
  *
8
8
  * O worker.js original permanece intacto como fallback.
9
9
  * Todos os módulos ficam em ./modules/
10
10
  */
11
11
 
12
+ import { ExecutionContext } from '@cloudflare/workers-types';
13
+ import { Env, TrackPayload, BehavioralData, HotmartWebhook, KiwifyWebhook, TictoWebhook } from './types';
14
+
12
15
  // ── Utilitários base ──────────────────────────────────────────────────────────
13
16
  import {
14
- isAllowedOrigin,
15
17
  corsHeaders,
16
18
  sha256,
17
19
  META_TO_GA4,
@@ -21,7 +23,7 @@ import {
21
23
  distanceBucketWeight,
22
24
  computeMetaSignalWeights,
23
25
  metaSignalBucket,
24
- } from './modules/utils.js';
26
+ } from './modules/utils';
25
27
 
26
28
  // ── Banco de dados (D1) ───────────────────────────────────────────────────────
27
29
  import {
@@ -37,23 +39,23 @@ import {
37
39
  resurrectUTM,
38
40
  upsertLtvProfile,
39
41
  recordLtvFeedback,
40
- } from './modules/db.js';
42
+ } from './modules/db';
41
43
 
42
44
  // ── Dispatch — plataformas de ads ─────────────────────────────────────────────
43
- import { sendMetaCapi } from './modules/dispatch/meta.js';
44
- import { sendGA4Mp } from './modules/dispatch/ga4.js';
45
- import { sendTikTokApi } from './modules/dispatch/tiktok.js';
45
+ import { sendMetaCapi } from './modules/dispatch/meta';
46
+ import { sendGA4Mp } from './modules/dispatch/ga4';
47
+ import { sendTikTokApi } from './modules/dispatch/tiktok';
46
48
  import {
47
49
  sendPinterestCapi,
48
50
  sendRedditCapi,
49
51
  sendLinkedInCapi,
50
52
  sendSpotifyCapi,
51
- } from './modules/dispatch/platforms.js';
53
+ } from './modules/dispatch/platforms';
52
54
  import {
53
55
  sendWhatsApp,
54
56
  processWhatsAppWebhook,
55
57
  verifyHmac,
56
- } from './modules/dispatch/whatsapp.js';
58
+ } from './modules/dispatch/whatsapp';
57
59
 
58
60
  // ── ML — LTV + A/B Testing ────────────────────────────────────────────────────
59
61
  import {
@@ -64,7 +66,7 @@ import {
64
66
  handleLtvAbTestList,
65
67
  handleLtvAbTestResults,
66
68
  handleLtvAbTestWinner,
67
- } from './modules/ml/ltv.js';
69
+ } from './modules/ml/ltv';
68
70
 
69
71
  // ── ML — Segmentação ──────────────────────────────────────────────────────────
70
72
  import {
@@ -72,14 +74,14 @@ import {
72
74
  handleSegmentationList,
73
75
  handleSegmentationOutliers,
74
76
  handleSegmentationUpdate,
75
- } from './modules/ml/segmentation.js';
77
+ } from './modules/ml/segmentation';
76
78
 
77
79
  // ── ML — Bidding ──────────────────────────────────────────────────────────────
78
80
  import {
79
81
  handleBiddingRecommend,
80
82
  handleBiddingHistory,
81
83
  handleBiddingStatus,
82
- } from './modules/ml/bidding.js';
84
+ } from './modules/ml/bidding';
83
85
 
84
86
  // ── ML — Fraud Detection ──────────────────────────────────────────────────────
85
87
  import {
@@ -90,21 +92,25 @@ import {
90
92
  handleFraudBlocklistAdd,
91
93
  handleFraudBlocklistRemove,
92
94
  handleFraudStats,
93
- } from './modules/ml/fraud.js';
95
+ } from './modules/ml/fraud';
94
96
 
95
97
  // ── Intelligence Agent (Cron) ─────────────────────────────────────────────────
96
98
  import {
97
99
  runIntelligenceAgent,
98
100
  buildGoogleCustomerMatchExport,
99
- } from './modules/intelligence.js';
101
+ } from './modules/intelligence';
100
102
 
101
103
  // ── Haversine distance (km) — sem dependência externa ────────────────────────
102
- function haversineKm(lat1, lon1, lat2, lon2) {
104
+ function haversineKm(lat1: number | string | null | undefined, lon1: number | string | null | undefined, lat2: number | string | null | undefined, lon2: number | string | null | undefined): number {
103
105
  const R = 6371;
104
- const dLat = (lat2 - lat1) * Math.PI / 180;
105
- const dLon = (lon2 - lon1) * Math.PI / 180;
106
+ const lat1Num = parseFloat(String(lat1 ?? '0'));
107
+ const lon1Num = parseFloat(String(lon1 ?? '0'));
108
+ const lat2Num = parseFloat(String(lat2 ?? '0'));
109
+ const lon2Num = parseFloat(String(lon2 ?? '0'));
110
+ const dLat = (lat2Num - lat1Num) * Math.PI / 180;
111
+ const dLon = (lon2Num - lon1Num) * Math.PI / 180;
106
112
  const a = Math.sin(dLat / 2) ** 2 +
107
- Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180) * Math.sin(dLon / 2) ** 2;
113
+ Math.cos(lat1Num * Math.PI / 180) * Math.cos(lat2Num * Math.PI / 180) * Math.sin(dLon / 2) ** 2;
108
114
  return R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
109
115
  }
110
116
 
@@ -113,12 +119,13 @@ function haversineKm(lat1, lon1, lat2, lon2) {
113
119
  // ─────────────────────────────────────────────────────────────────────────────
114
120
  export default {
115
121
 
116
- async fetch(request, env, ctx) {
122
+ async fetch(request: Request, env: Env, ctx: ExecutionContext) {
117
123
  const origin = request.headers.get('Origin') || '';
118
- const headers = {
124
+ const headersObj = {
119
125
  'Content-Type': 'application/json',
120
- ...corsHeaders(origin, env.SITE_DOMAIN),
126
+ ...corsHeaders(origin, env.SITE_DOMAIN || null),
121
127
  };
128
+ const headers = new Headers(headersObj);
122
129
 
123
130
  // Preflight CORS
124
131
  if (request.method === 'OPTIONS') {
@@ -142,10 +149,10 @@ export default {
142
149
  // Roda ANTES de qualquer processamento de evento
143
150
  // Silent drop (200) — bots não sabem que foram detectados
144
151
  if (url.pathname === '/track' && request.method === 'POST') {
145
- let trackBodyForFraud;
152
+ let trackBodyForFraud: TrackPayload = {};
146
153
  try {
147
154
  const cloned = request.clone();
148
- trackBodyForFraud = await cloned.json().catch(() => ({}));
155
+ trackBodyForFraud = await cloned.json().catch(() => ({})) as TrackPayload;
149
156
  } catch { trackBodyForFraud = {}; }
150
157
 
151
158
  const fraudResult = await checkFraudGate(env, request, trackBodyForFraud);
@@ -174,30 +181,30 @@ export default {
174
181
 
175
182
  // ── GET /health ───────────────────────────────────────────────────────────
176
183
  if (request.method === 'GET' && url.pathname === '/health') {
177
- const results = {};
184
+ const results: Record<string, string> = {};
178
185
 
179
186
  try {
180
- await env.DB.prepare('SELECT 1').run();
187
+ await env.DB?.prepare('SELECT 1').run();
181
188
  results.d1 = 'ok';
182
- } catch (err) {
183
- results.d1 = `FAILED: ${err.message}`;
189
+ } catch (err: any) {
190
+ results.d1 = `FAILED: ${err?.message || String(err)}`;
184
191
  }
185
192
 
186
193
  try {
187
- await env.GEO_CACHE.get('__health_check__');
194
+ await env.GEO_CACHE?.get('__health_check__');
188
195
  results.kv = 'ok';
189
- } catch (err) {
190
- results.kv = `FAILED: ${err.message}`;
196
+ } catch (err: any) {
197
+ results.kv = `FAILED: ${err?.message || String(err)}`;
191
198
  }
192
199
 
193
200
  try {
194
- await env.AI.run('@cf/ibm-granite/granite-4.0-h-micro', {
201
+ await env.AI?.run('@cf/ibm-granite/granite-4.0-h-micro', {
195
202
  messages: [{ role: 'user', content: 'ping' }],
196
203
  max_tokens: 1,
197
204
  });
198
205
  results.ai = 'ok';
199
- } catch (err) {
200
- results.ai = `FAILED: ${err.message}`;
206
+ } catch (err: any) {
207
+ results.ai = `FAILED: ${err?.message || String(err)}`;
201
208
  }
202
209
 
203
210
  const vars = {
@@ -211,8 +218,8 @@ export default {
211
218
  META_ACCESS_TOKEN: env.META_ACCESS_TOKEN ? 'set' : 'MISSING',
212
219
  GA4_API_SECRET: env.GA4_API_SECRET ? 'set' : 'MISSING',
213
220
  WA_WEBHOOK_VERIFY_TOKEN: env.WA_WEBHOOK_VERIFY_TOKEN ? 'set' : 'MISSING',
214
- WHATSAPP_ACCESS_TOKEN: env.WHATSAPP_ACCESS_TOKEN ? 'set' : 'not set (optional - only for auto-reply)',
215
- WHATSAPP_PHONE_NUMBER_ID: env.WHATSAPP_PHONE_NUMBER_ID ? 'set' : 'not set (optional - only for auto-reply)',
221
+ WHATSAPP_ACCESS_TOKEN: (env.WHATSAPP_ACCESS_TOKEN || env.WA_ACCESS_TOKEN) ? 'set' : 'not set (optional - only for auto-reply)',
222
+ WHATSAPP_PHONE_NUMBER_ID: (env.WHATSAPP_PHONE_NUMBER_ID || env.WA_PHONE_ID) ? 'set' : 'not set (optional - only for auto-reply)',
216
223
  WA_NOTIFY_NUMBER: env.WA_NOTIFY_NUMBER ? 'set' : 'not set (optional - only for auto-reply)',
217
224
  TIKTOK_ACCESS_TOKEN: env.TIKTOK_ACCESS_TOKEN ? 'set' : 'not set (optional)',
218
225
  CALLMEBOT_PHONE: env.CALLMEBOT_PHONE ? 'set' : 'not set (optional)',
@@ -255,7 +262,8 @@ export default {
255
262
  'utmSource','utmMedium','utmCampaign','utmContent','utmTerm',
256
263
  'fbclid','ttclid','gclid','transactionId','productName','currency'];
257
264
 
258
- const { eventName, behavioral_data, ...payload } = body;
265
+ const { eventName, behavioral_data, ...payload } = body as { eventName?: string; behavioral_data?: BehavioralData; [key: string]: any };
266
+ const trackPayload: TrackPayload = payload;
259
267
 
260
268
  if (!eventName) {
261
269
  return new Response(JSON.stringify({ error: 'eventName é obrigatório' }), { status: 400, headers });
@@ -266,19 +274,19 @@ export default {
266
274
  }
267
275
 
268
276
  for (const field of STR_FIELDS) {
269
- if (payload[field] !== undefined && payload[field] !== null) {
270
- if (typeof payload[field] !== 'string' || payload[field].length > 512) {
277
+ if (trackPayload[field as keyof TrackPayload] !== undefined && trackPayload[field as keyof TrackPayload] !== null) {
278
+ if (typeof trackPayload[field as keyof TrackPayload] !== 'string' || String(trackPayload[field as keyof TrackPayload]).length > 512) {
271
279
  return new Response(JSON.stringify({ error: `Campo inválido: ${field}` }), { status: 400, headers });
272
280
  }
273
281
  }
274
282
  }
275
283
 
276
- if (payload.value !== undefined && payload.value !== null) {
277
- const v = Number(payload.value);
284
+ if (trackPayload.value !== undefined && trackPayload.value !== null) {
285
+ const v = Number(trackPayload.value);
278
286
  if (isNaN(v) || v < 0 || v > 9_999_999) {
279
287
  return new Response(JSON.stringify({ error: 'value fora do intervalo permitido' }), { status: 400, headers });
280
288
  }
281
- payload.value = v;
289
+ trackPayload.value = v;
282
290
  }
283
291
 
284
292
  // ── Extrair dados comportamentais do browser ──────────────────────────
@@ -332,7 +340,7 @@ export default {
332
340
  const fingerprint = await generateEdgeFingerprint(request);
333
341
  payload.utmRestored = false;
334
342
 
335
- if (fingerprint) {
343
+ if (fingerprint && env.DB) {
336
344
  if (payload.utmSource) {
337
345
  ctx.waitUntil(saveEdgeFingerprint(env.DB, fingerprint, payload.userId, payload));
338
346
  } else {
@@ -349,13 +357,13 @@ export default {
349
357
  }
350
358
 
351
359
  // ── Bot Mitigation ────────────────────────────────────────────────────
352
- const botScoreStr = request.cf?.botManagement?.score;
353
- const cfBotScore = botScoreStr !== undefined ? parseInt(botScoreStr) : 100;
360
+ const botScoreStr = (request as any).cf?.botManagement?.score;
361
+ const cfBotScore = botScoreStr !== undefined ? parseInt(String(botScoreStr)) : 100;
354
362
  const ua = (request.headers.get('User-Agent') || '').toLowerCase();
355
363
  const isBotPattern = /bot|crawler|spider|lighthouse|postman|curl|inspect|headless/i.test(ua);
356
364
 
357
365
  const isBot = cfBotScore < 30 || isBotPattern;
358
- payload.botScore = isBot ? 2 : (cfBotScore < 60 ? 1 : 0);
366
+ trackPayload.botScore = isBot ? 2 : (cfBotScore < 60 ? 1 : 0);
359
367
 
360
368
  if (isBot && !['Contact', 'Lead', 'Purchase'].includes(eventName)) {
361
369
  return new Response(JSON.stringify({ ok: true, skipped_due_to_bot: true }), { status: 200, headers });
@@ -387,17 +395,17 @@ export default {
387
395
  // ── Real Estate Distance Enrichment ──────────────────────────────────
388
396
  // Calcula distância entre o IP do usuário (via Cloudflare) e o imóvel.
389
397
  // Ativa quando o evento carrega property_lat + property_lng (ex: Schedule de rota).
390
- const propLat = parseFloat(payload.property_lat ?? payload.propertyLat);
391
- const propLng = parseFloat(payload.property_lng ?? payload.propertyLng);
392
- const userLat = parseFloat(request.cf?.latitude);
393
- const userLng = parseFloat(request.cf?.longitude);
398
+ const propLat = parseFloat(String(trackPayload.property_lat ?? trackPayload.propertyLat));
399
+ const propLng = parseFloat(String(trackPayload.property_lng ?? trackPayload.propertyLng));
400
+ const userLat = parseFloat(String(request.cf?.latitude ?? '0'));
401
+ const userLng = parseFloat(String(request.cf?.longitude ?? '0'));
394
402
  if (!isNaN(propLat) && !isNaN(propLng) && !isNaN(userLat) && !isNaN(userLng)) {
395
403
  const distKm = haversineKm(userLat, userLng, propLat, propLng);
396
- payload.distanceKm = Math.round(distKm * 10) / 10;
397
- payload.distanceBucket = distKm < 5 ? 'very_close' :
398
- distKm < 15 ? 'close' :
399
- distKm < 30 ? 'nearby' :
400
- distKm < 60 ? 'moderate' : 'far';
404
+ trackPayload.distanceKm = Math.round(distKm * 10) / 10;
405
+ trackPayload.distanceBucket = distKm < 5 ? 'very_close' :
406
+ distKm < 15 ? 'close' :
407
+ distKm < 30 ? 'nearby' :
408
+ distKm < 60 ? 'moderate' : 'far';
401
409
  }
402
410
 
403
411
  // ── LTV Prediction (+ A/B Testing de Prompts) ─────────────────────────
@@ -418,11 +426,11 @@ export default {
418
426
  recordAbAssignment(
419
427
  env,
420
428
  payload.userId,
421
- abVariation.variation_id,
429
+ abVariation.id,
422
430
  abVariation.test_id,
423
431
  ltv.value,
424
432
  ltv.class,
425
- emailHash,
433
+ emailHash ?? null,
426
434
  )
427
435
  );
428
436
  }
@@ -481,26 +489,27 @@ export default {
481
489
  // Automação de mensagens
482
490
  const AUTOMATION_EVENTS = ['Lead', 'Purchase', 'InitiateCheckout'];
483
491
  if (AUTOMATION_EVENTS.includes(eventName) && env.DB) {
492
+ const db = env.DB; // Captura em variável local
484
493
  ctx.waitUntil(
485
494
  (async () => {
486
495
  try {
487
- const lastLead = await env.DB
496
+ const lastLead = await db
488
497
  .prepare(`SELECT id FROM leads WHERE event_id = ?1 LIMIT 1`)
489
- .bind(payload.eventId || payload.event_id || '')
490
- .first();
491
- const leadId = lastLead?.id ?? null;
492
- if (leadId) await fireAutomation(env, eventName, leadId, payload);
493
- } catch (e) { console.error('[Automation] lead lookup error:', e.message); }
498
+ .bind(trackPayload.eventId || trackPayload.event_id || '')
499
+ .first() as any;
500
+ const leadId = lastLead?.id ? Number(lastLead.id) : null;
501
+ if (leadId) await fireAutomation(env, eventName, leadId, trackPayload);
502
+ } catch (e: any) { console.error('[Automation] lead lookup error:', e?.message || String(e)); }
494
503
  })()
495
504
  );
496
505
  }
497
506
 
498
507
  // Edge Personalization
499
508
  let currentScore = 0;
500
- if (env.DB && payload.userId) {
509
+ if (env.DB && trackPayload.userId) {
501
510
  try {
502
- const profileRow = await env.DB.prepare('SELECT score FROM user_profiles WHERE user_id = ?').bind(payload.userId).first();
503
- if (profileRow) currentScore = profileRow.score;
511
+ const profileRow = await env.DB.prepare('SELECT score FROM user_profiles WHERE user_id = ?').bind(trackPayload.userId).first();
512
+ if (profileRow) currentScore = Number(profileRow.score) || 0;
504
513
  } catch {}
505
514
  }
506
515
 
@@ -510,9 +519,9 @@ export default {
510
519
  return new Response(JSON.stringify({
511
520
  ok: true,
512
521
  userProfile: { score: currentScore, user_id: finalUserId },
513
- meta: metaRes.value ?? { error: metaRes.reason?.message },
514
- ga4: ga4Res.value ?? { error: ga4Res.reason?.message },
515
- tiktok: ttRes.value ?? { error: ttRes.reason?.message },
522
+ meta: metaRes.status === 'fulfilled' ? metaRes.value : { error: metaRes.reason?.message },
523
+ ga4: ga4Res.status === 'fulfilled' ? ga4Res.value : { error: ga4Res.reason?.message },
524
+ tiktok: ttRes.status === 'fulfilled' ? ttRes.value : { error: ttRes.reason?.message },
516
525
  }), { status: 200, headers: resHeaders });
517
526
  }
518
527
 
@@ -525,8 +534,8 @@ export default {
525
534
  }
526
535
  }
527
536
 
528
- let wh;
529
- try { wh = await request.json(); } catch {
537
+ let wh: HotmartWebhook;
538
+ try { wh = await request.json() as HotmartWebhook; } catch {
530
539
  return new Response('JSON inválido', { status: 400 });
531
540
  }
532
541
 
@@ -603,8 +612,8 @@ export default {
603
612
  }
604
613
  }
605
614
 
606
- let wh;
607
- try { wh = await request.json(); } catch {
615
+ let wh: KiwifyWebhook;
616
+ try { wh = await request.json() as KiwifyWebhook; } catch {
608
617
  return new Response('JSON inválido', { status: 400 });
609
618
  }
610
619
 
@@ -625,8 +634,8 @@ export default {
625
634
  }
626
635
 
627
636
  const customer = wh.Customer || {};
628
- const product = wh.Product || {};
629
- const profile = await getProfileByEmail(env, customer.email);
637
+ const product = wh.Product || {};
638
+ const profile = await getProfileByEmail(env, customer.email || '');
630
639
 
631
640
  const payload = {
632
641
  email: customer.email,
@@ -687,8 +696,8 @@ export default {
687
696
  }
688
697
  }
689
698
 
690
- let wh;
691
- try { wh = JSON.parse(rawBody); } catch {
699
+ let wh: TictoWebhook;
700
+ try { wh = JSON.parse(rawBody) as TictoWebhook; } catch {
692
701
  return new Response('JSON inválido', { status: 400 });
693
702
  }
694
703
 
@@ -703,7 +712,7 @@ export default {
703
712
  const tracking = wh.tracking || wh.url_params || {};
704
713
 
705
714
  const valueRaw = order.paid_amount ?? order.total ?? order.amount;
706
- const value = valueRaw ? parseFloat(valueRaw) / 100 : undefined;
715
+ const value = valueRaw ? parseFloat(String(valueRaw)) / 100 : undefined;
707
716
  const transactionId = order.hash || order.transaction_hash || order.id;
708
717
  const tcTxId = String(order.hash || order.transaction_hash || order.id || '');
709
718
 
@@ -719,7 +728,7 @@ export default {
719
728
  }
720
729
 
721
730
  const urlUserId = tracking.user_id || wh.url_params?.user_id;
722
- let profile = await getProfileByEmail(env, customer.email);
731
+ let profile = await getProfileByEmail(env, customer.email || '');
723
732
  if (!profile && urlUserId && env.DB) {
724
733
  try {
725
734
  profile = await env.DB.prepare(
@@ -865,7 +874,7 @@ export default {
865
874
  },
866
875
 
867
876
  // ── Cron Handler — Intelligence Agent ────────────────────────────────────────
868
- async scheduled(event, env, ctx) {
877
+ async scheduled(event: any, env: Env, ctx: ExecutionContext) {
869
878
  const cron = event.cron;
870
879
  const isMonthly = cron === '0 3 1 * *';
871
880
 
@@ -873,9 +882,9 @@ export default {
873
882
  },
874
883
 
875
884
  // ── Queue Consumer — Retry de eventos com falha ───────────────────────────────
876
- async queue(batch, env) {
885
+ async queue(batch: any, env: Env) {
877
886
  for (const message of batch.messages) {
878
- const { eventType, payload, platform, attempt = 1 } = message.body;
887
+ const { eventType, payload, platform } = message.body as { eventType: string; payload: TrackPayload; platform: string; attempt?: number };
879
888
 
880
889
 
881
890
  try {
@@ -884,8 +893,8 @@ export default {
884
893
  if (platform === 'tiktok') await sendTikTokApi(env, eventType, payload, null, null);
885
894
 
886
895
  message.ack();
887
- } catch (err) {
888
- console.error(`[Queue] Falha ao reprocessar ${platform}/${eventType}:`, err.message);
896
+ } catch (err: any) {
897
+ console.error(`[Queue] Falha ao reprocessar ${platform}/${eventType}:`, err?.message || String(err));
889
898
  message.retry();
890
899
  }
891
900
  }