cdp-edge 2.2.4 → 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.
- package/README.md +44 -2
- package/bin/cdp-edge.js +10 -1
- package/contracts/types.ts +81 -0
- package/dist/commands/install.js +6 -1
- package/docs/whatsapp-ctwa.md +3 -2
- package/package.json +7 -4
- package/server-edge-tracker/{index.js → index.ts} +91 -82
- package/server-edge-tracker/modules/{db.js → db.ts} +116 -76
- package/server-edge-tracker/modules/dispatch/{ga4.js → ga4.ts} +12 -10
- package/server-edge-tracker/modules/dispatch/{meta.js → meta.ts} +35 -28
- package/server-edge-tracker/modules/dispatch/{platforms.js → platforms.ts} +58 -56
- package/server-edge-tracker/modules/dispatch/{tiktok.js → tiktok.ts} +22 -20
- package/server-edge-tracker/modules/dispatch/{whatsapp.js → whatsapp.ts} +59 -25
- package/server-edge-tracker/modules/{intelligence.js → intelligence.ts} +175 -60
- package/server-edge-tracker/modules/ml/{bidding.js → bidding.ts} +37 -35
- package/server-edge-tracker/modules/ml/{fraud.js → fraud.ts} +48 -40
- package/server-edge-tracker/modules/ml/{logistic.js → logistic.ts} +44 -19
- package/server-edge-tracker/modules/ml/{ltv.js → ltv.ts} +135 -90
- package/server-edge-tracker/modules/ml/{matchquality.js → matchquality.ts} +70 -26
- package/server-edge-tracker/modules/ml/{segmentation.js → segmentation.ts} +109 -48
- package/server-edge-tracker/modules/{utils.js → utils.ts} +41 -22
- package/server-edge-tracker/types.ts +251 -0
- package/server-edge-tracker/wrangler.toml +8 -8
- package/docs/PixelBuilder-Documentacao-Completa (2).docx +0 -0
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* CDP Edge — index.
|
|
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.
|
|
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
|
|
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
|
|
42
|
+
} from './modules/db';
|
|
41
43
|
|
|
42
44
|
// ── Dispatch — plataformas de ads ─────────────────────────────────────────────
|
|
43
|
-
import { sendMetaCapi } from './modules/dispatch/meta
|
|
44
|
-
import { sendGA4Mp } from './modules/dispatch/ga4
|
|
45
|
-
import { sendTikTokApi } from './modules/dispatch/tiktok
|
|
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
|
|
53
|
+
} from './modules/dispatch/platforms';
|
|
52
54
|
import {
|
|
53
55
|
sendWhatsApp,
|
|
54
56
|
processWhatsAppWebhook,
|
|
55
57
|
verifyHmac,
|
|
56
|
-
} from './modules/dispatch/whatsapp
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
105
|
-
const
|
|
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(
|
|
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
|
|
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
|
|
187
|
+
await env.DB?.prepare('SELECT 1').run();
|
|
181
188
|
results.d1 = 'ok';
|
|
182
|
-
} catch (err) {
|
|
183
|
-
results.d1 = `FAILED: ${err
|
|
189
|
+
} catch (err: any) {
|
|
190
|
+
results.d1 = `FAILED: ${err?.message || String(err)}`;
|
|
184
191
|
}
|
|
185
192
|
|
|
186
193
|
try {
|
|
187
|
-
await env.GEO_CACHE
|
|
194
|
+
await env.GEO_CACHE?.get('__health_check__');
|
|
188
195
|
results.kv = 'ok';
|
|
189
|
-
} catch (err) {
|
|
190
|
-
results.kv = `FAILED: ${err
|
|
196
|
+
} catch (err: any) {
|
|
197
|
+
results.kv = `FAILED: ${err?.message || String(err)}`;
|
|
191
198
|
}
|
|
192
199
|
|
|
193
200
|
try {
|
|
194
|
-
await env.AI
|
|
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
|
|
206
|
+
} catch (err: any) {
|
|
207
|
+
results.ai = `FAILED: ${err?.message || String(err)}`;
|
|
201
208
|
}
|
|
202
209
|
|
|
203
210
|
const vars = {
|
|
@@ -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 (
|
|
270
|
-
if (typeof
|
|
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 (
|
|
277
|
-
const v = Number(
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
391
|
-
const propLng = parseFloat(
|
|
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
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
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.
|
|
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
|
|
496
|
+
const lastLead = await db
|
|
488
497
|
.prepare(`SELECT id FROM leads WHERE event_id = ?1 LIMIT 1`)
|
|
489
|
-
.bind(
|
|
490
|
-
.first();
|
|
491
|
-
const leadId = lastLead?.id
|
|
492
|
-
if (leadId) await fireAutomation(env, eventName, leadId,
|
|
493
|
-
} catch (e) { console.error('[Automation] lead lookup error:', e
|
|
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 &&
|
|
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(
|
|
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
|
|
514
|
-
ga4: ga4Res.value
|
|
515
|
-
tiktok: ttRes.value
|
|
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
|
|
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
|
|
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
|
}
|