cdp-edge 2.3.0 → 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.
- package/README.md +24 -1
- package/package.json +1 -1
- package/server-edge-tracker/.client.env.example +14 -0
- package/server-edge-tracker/deploy-client.js +76 -0
- package/server-edge-tracker/index.ts +2 -2
- package/server-edge-tracker/modules/db.ts +1 -1
- package/server-edge-tracker/modules/dispatch/whatsapp.ts +15 -3
- package/server-edge-tracker/types.ts +6 -2
- package/server-edge-tracker/wrangler.toml +6 -6
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
**Padrão Quantum Tracking: 100% Cloudflare Edge.** Sem GTM. Sem Stape. Sem cookies de terceiros.
|
|
4
4
|
|
|
5
|
-
> **v2.3.
|
|
5
|
+
> **v2.3.1** — Hardening Enterprise · PII removido dos logs · Script de deploy seguro (`deploy-client.js`) · WhatsApp secrets alinhados à Meta Cloud API v22.0 · `api-versions.json` v1.1.0
|
|
6
6
|
|
|
7
7
|
> ⚠️ **REGRA DE OURO (SQUAD):** Todas as atualizações, correções ou novas features devem OBRIGATORIAMENTE ser documentadas de forma sincronizada neste `README.md`, no arquivo de instruções `CLAUDE.md` e no dossiê de diretoria `CDP-EDGE-BUSINESS-BOOK.md`. Nenhuma alteração passa sem esse tripé.
|
|
8
8
|
|
|
@@ -27,6 +27,29 @@ Meu ecossistema opera como um Cérebro de Conversão Privado na borda. Quando um
|
|
|
27
27
|
|
|
28
28
|
---
|
|
29
29
|
|
|
30
|
+
## 📋 CHANGELOG v2.3.1 — Hardening Enterprise (12 de Abril de 2026)
|
|
31
|
+
|
|
32
|
+
### 🔒 Segurança & Conformidade
|
|
33
|
+
|
|
34
|
+
- **PII removido dos logs:** `DeviceGraph` parou de logar `user_id` nos Workers logs — dados sensíveis nunca aparecem no Cloudflare dashboard
|
|
35
|
+
- **Deploy seguro:** novo `deploy-client.js` — lê credenciais de `.client.env` (gitignored), gera `wrangler.deploy.toml` temporário, faz deploy e autodestrói o arquivo. Credenciais de cliente nunca entram no repo
|
|
36
|
+
- **`.gitignore` reforçado:** `.client.env` e `wrangler.deploy.toml` explicitamente ignorados
|
|
37
|
+
- **WhatsApp secrets alinhados à Meta Cloud API v22.0:** `resolvePhoneNumberId()` e `resolveAccessToken()` com fallback canônico → legado — backwards compat garantido
|
|
38
|
+
- **`api-versions.json` v1.1.0:** metadata `updated_at` corrigido para `2026-04-12`
|
|
39
|
+
- **`/health` WhatsApp:** reflete corretamente secrets com nomes canônicos ou legados
|
|
40
|
+
|
|
41
|
+
### 🔧 Deploy de Cliente — Novo Fluxo
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
cd server-edge-tracker
|
|
45
|
+
cp .client.env.example .client.env
|
|
46
|
+
# preencher DATABASE_ID, SITE_DOMAIN, pixels
|
|
47
|
+
node deploy-client.js --dry-run # valida sem subir
|
|
48
|
+
node deploy-client.js # deploy real
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
30
53
|
## 📋 CHANGELOG v2.3.0 — TypeScript Nativo (12 de Abril de 2026)
|
|
31
54
|
|
|
32
55
|
### 🔷 Worker 100% TypeScript — Migração Completa
|
package/package.json
CHANGED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# CDP Edge — Variáveis do cliente para deploy
|
|
2
|
+
# Copie este arquivo para .client.env e preencha com os dados reais.
|
|
3
|
+
# .client.env é gitignored — NUNCA commitado.
|
|
4
|
+
|
|
5
|
+
# Cloudflare D1 — obter com: wrangler d1 list
|
|
6
|
+
DATABASE_ID=SEU_DATABASE_ID
|
|
7
|
+
|
|
8
|
+
# Domínio do projeto (sem https://)
|
|
9
|
+
SITE_DOMAIN=seudominio.com.br
|
|
10
|
+
|
|
11
|
+
# Pixels de rastreamento (opcional — deixar vazio se não usar)
|
|
12
|
+
META_PIXEL_ID=
|
|
13
|
+
GA4_MEASUREMENT_ID=
|
|
14
|
+
TIKTOK_PIXEL_ID=
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* CDP Edge — deploy-client.js
|
|
4
|
+
*
|
|
5
|
+
* Deploy do Worker com variáveis reais do cliente, sem commitar credenciais no repo.
|
|
6
|
+
* Lê de .client.env (gitignored) e gera um wrangler.deploy.toml temporário.
|
|
7
|
+
*
|
|
8
|
+
* Uso:
|
|
9
|
+
* node deploy-client.js → deploy completo
|
|
10
|
+
* node deploy-client.js --dry-run → valida sem subir ao Cloudflare
|
|
11
|
+
*
|
|
12
|
+
* Setup:
|
|
13
|
+
* cp .client.env.example .client.env
|
|
14
|
+
* # edite .client.env com os valores do cliente
|
|
15
|
+
* node deploy-client.js
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
const fs = require('fs');
|
|
19
|
+
const path = require('path');
|
|
20
|
+
const { execSync } = require('child_process');
|
|
21
|
+
|
|
22
|
+
const ROOT = __dirname;
|
|
23
|
+
const TOML = path.join(ROOT, 'wrangler.toml');
|
|
24
|
+
const DEPLOY = path.join(ROOT, 'wrangler.deploy.toml');
|
|
25
|
+
const ENV = path.join(ROOT, '.client.env');
|
|
26
|
+
const DRY_RUN = process.argv.includes('--dry-run');
|
|
27
|
+
|
|
28
|
+
// ── Carregar .client.env ──────────────────────────────────────────────────────
|
|
29
|
+
if (!fs.existsSync(ENV)) {
|
|
30
|
+
console.error('\n❌ .client.env não encontrado.');
|
|
31
|
+
console.error(' cp .client.env.example .client.env e preencha os valores do cliente.\n');
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const env = {};
|
|
36
|
+
fs.readFileSync(ENV, 'utf8').split('\n').forEach(line => {
|
|
37
|
+
const trimmed = line.trim();
|
|
38
|
+
if (!trimmed || trimmed.startsWith('#')) return;
|
|
39
|
+
const [key, ...rest] = trimmed.split('=');
|
|
40
|
+
if (key) env[key.trim()] = rest.join('=').trim();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const required = ['DATABASE_ID', 'SITE_DOMAIN'];
|
|
44
|
+
const missing = required.filter(k => !env[k]);
|
|
45
|
+
if (missing.length > 0) {
|
|
46
|
+
console.error(`\n❌ Variáveis obrigatórias faltando no .client.env: ${missing.join(', ')}\n`);
|
|
47
|
+
process.exit(1);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// ── Substituir placeholders no wrangler.toml → wrangler.deploy.toml ───────────
|
|
51
|
+
let toml = fs.readFileSync(TOML, 'utf8');
|
|
52
|
+
|
|
53
|
+
toml = toml
|
|
54
|
+
.replace(/SEU_DATABASE_ID/g, env.DATABASE_ID)
|
|
55
|
+
.replace(/SEU_DOMINIO/g, env.SITE_DOMAIN)
|
|
56
|
+
.replace(/META_PIXEL_ID\s*=\s*""/, `META_PIXEL_ID = "${env.META_PIXEL_ID || ''}"`)
|
|
57
|
+
.replace(/GA4_MEASUREMENT_ID\s*=\s*""/, `GA4_MEASUREMENT_ID = "${env.GA4_MEASUREMENT_ID || ''}"`)
|
|
58
|
+
.replace(/TIKTOK_PIXEL_ID\s*=\s*""/, `TIKTOK_PIXEL_ID = "${env.TIKTOK_PIXEL_ID || ''}"`);
|
|
59
|
+
|
|
60
|
+
fs.writeFileSync(DEPLOY, toml);
|
|
61
|
+
|
|
62
|
+
// ── Executar wrangler deploy ──────────────────────────────────────────────────
|
|
63
|
+
const cmd = `wrangler deploy --config wrangler.deploy.toml${DRY_RUN ? ' --dry-run' : ''}`;
|
|
64
|
+
console.log(`\n🚀 ${DRY_RUN ? '[DRY-RUN] ' : ''}Deploying com config do cliente...\n`);
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
execSync(cmd, { stdio: 'inherit', cwd: ROOT });
|
|
68
|
+
console.log(`\n✅ Deploy ${DRY_RUN ? '(dry-run) ' : ''}concluído.\n`);
|
|
69
|
+
} catch (err) {
|
|
70
|
+
console.error('\n❌ Deploy falhou.\n');
|
|
71
|
+
process.exit(1);
|
|
72
|
+
} finally {
|
|
73
|
+
// sempre remove o arquivo temporário
|
|
74
|
+
if (fs.existsSync(DEPLOY)) fs.unlinkSync(DEPLOY);
|
|
75
|
+
console.log('🧹 wrangler.deploy.toml removido.\n');
|
|
76
|
+
}
|
|
@@ -218,8 +218,8 @@ export default {
|
|
|
218
218
|
META_ACCESS_TOKEN: env.META_ACCESS_TOKEN ? 'set' : 'MISSING',
|
|
219
219
|
GA4_API_SECRET: env.GA4_API_SECRET ? 'set' : 'MISSING',
|
|
220
220
|
WA_WEBHOOK_VERIFY_TOKEN: env.WA_WEBHOOK_VERIFY_TOKEN ? 'set' : 'MISSING',
|
|
221
|
-
WHATSAPP_ACCESS_TOKEN: env.WHATSAPP_ACCESS_TOKEN
|
|
222
|
-
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)',
|
|
223
223
|
WA_NOTIFY_NUMBER: env.WA_NOTIFY_NUMBER ? 'set' : 'not set (optional - only for auto-reply)',
|
|
224
224
|
TIKTOK_ACCESS_TOKEN: env.TIKTOK_ACCESS_TOKEN ? 'set' : 'not set (optional)',
|
|
225
225
|
CALLMEBOT_PHONE: env.CALLMEBOT_PHONE ? 'set' : 'not set (optional)',
|
|
@@ -226,7 +226,7 @@ export async function resolveDeviceGraph(DB: D1Database, currentUserId: string,
|
|
|
226
226
|
VALUES (?, ?, ?, ?)
|
|
227
227
|
`).bind(primary, secondary, matchType, matchConfidence).run();
|
|
228
228
|
|
|
229
|
-
|
|
229
|
+
// sem log de user IDs — dados sensíveis não entram em Workers log
|
|
230
230
|
}
|
|
231
231
|
} catch (err: any) {
|
|
232
232
|
console.error('resolveDeviceGraph error:', err?.message || String(err));
|
|
@@ -40,9 +40,19 @@ interface WhatsAppMessage {
|
|
|
40
40
|
};
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
// ── Resolvedores de secrets (canônico + legado) ────────────────────────────────
|
|
44
|
+
// Meta Cloud API v22.0 usa PHONE_NUMBER_ID e ACCESS_TOKEN como termos oficiais.
|
|
45
|
+
// Suportamos ambos os nomes para compatibilidade com secrets já configurados.
|
|
46
|
+
function resolvePhoneNumberId(env: Env): string | undefined {
|
|
47
|
+
return env.WHATSAPP_PHONE_NUMBER_ID || env.WA_PHONE_ID;
|
|
48
|
+
}
|
|
49
|
+
function resolveAccessToken(env: Env): string | undefined {
|
|
50
|
+
return env.WHATSAPP_ACCESS_TOKEN || env.WA_ACCESS_TOKEN;
|
|
51
|
+
}
|
|
52
|
+
|
|
43
53
|
// ── sendWhatsApp — envia mensagem via Meta Cloud API ──────────────────────────
|
|
44
54
|
export async function sendWhatsApp(env: Env, tipo: string, payload: TrackPayload, options: WhatsAppOptions = {}): Promise<any> {
|
|
45
|
-
if (!env
|
|
55
|
+
if (!resolvePhoneNumberId(env) || !resolveAccessToken(env) || !env.WA_NOTIFY_NUMBER) {
|
|
46
56
|
return { skipped: 'WhatsApp não configurado' };
|
|
47
57
|
}
|
|
48
58
|
|
|
@@ -116,9 +126,11 @@ export async function sendWhatsApp(env: Env, tipo: string, payload: TrackPayload
|
|
|
116
126
|
// ── _sendWARequest — executor interno ─────────────────────────────────────────
|
|
117
127
|
async function _sendWARequest(env: Env, body: Record<string, any>): Promise<any> {
|
|
118
128
|
try {
|
|
119
|
-
const
|
|
129
|
+
const phoneNumberId = resolvePhoneNumberId(env);
|
|
130
|
+
const accessToken = resolveAccessToken(env);
|
|
131
|
+
const res = await fetch(`https://graph.facebook.com/v22.0/${phoneNumberId}/messages`, {
|
|
120
132
|
method: 'POST',
|
|
121
|
-
headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${
|
|
133
|
+
headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${accessToken}` },
|
|
122
134
|
body: JSON.stringify(body),
|
|
123
135
|
});
|
|
124
136
|
const data = await res.json();
|
|
@@ -33,8 +33,12 @@ export interface Env {
|
|
|
33
33
|
GA4_API_SECRET?: string;
|
|
34
34
|
TIKTOK_ACCESS_TOKEN?: string;
|
|
35
35
|
WA_WEBHOOK_VERIFY_TOKEN?: string;
|
|
36
|
-
|
|
37
|
-
|
|
36
|
+
// WhatsApp Cloud API — nomes canônicos (Meta Cloud API v22.0)
|
|
37
|
+
WHATSAPP_ACCESS_TOKEN?: string; // canonical: Bearer token do System User
|
|
38
|
+
WHATSAPP_PHONE_NUMBER_ID?: string; // canonical: ID numérico do número no Meta Business
|
|
39
|
+
// WhatsApp Cloud API — nomes legados (backwards compat)
|
|
40
|
+
WA_ACCESS_TOKEN?: string;
|
|
41
|
+
WA_PHONE_ID?: string;
|
|
38
42
|
WA_NOTIFY_NUMBER?: string;
|
|
39
43
|
CALLMEBOT_PHONE?: string;
|
|
40
44
|
WEBHOOK_SECRET_TICTO?: string;
|
|
@@ -17,19 +17,19 @@ workers_dev = true
|
|
|
17
17
|
# zone_name = "SEU_DOMINIO"
|
|
18
18
|
|
|
19
19
|
[[routes]]
|
|
20
|
-
pattern = "
|
|
21
|
-
zone_name = "
|
|
20
|
+
pattern = "SEU_DOMINIO/track*"
|
|
21
|
+
zone_name = "SEU_DOMINIO"
|
|
22
22
|
|
|
23
23
|
[[routes]]
|
|
24
|
-
pattern = "*.
|
|
25
|
-
zone_name = "
|
|
24
|
+
pattern = "*.SEU_DOMINIO/track*"
|
|
25
|
+
zone_name = "SEU_DOMINIO"
|
|
26
26
|
|
|
27
27
|
# ── Variáveis públicas (não são segredos) ─────────────────────────────────────
|
|
28
28
|
[vars]
|
|
29
29
|
META_PIXEL_ID = ""
|
|
30
30
|
GA4_MEASUREMENT_ID = ""
|
|
31
31
|
TIKTOK_PIXEL_ID = ""
|
|
32
|
-
SITE_DOMAIN = "
|
|
32
|
+
SITE_DOMAIN = "SEU_DOMINIO"
|
|
33
33
|
|
|
34
34
|
# ── Banco D1 ──────────────────────────────────────────────────────────────────
|
|
35
35
|
# Após criar o banco com "wrangler d1 create cdp-edge-db",
|
|
@@ -37,7 +37,7 @@ SITE_DOMAIN = "lancamentosabc.com.br"
|
|
|
37
37
|
[[d1_databases]]
|
|
38
38
|
binding = "DB"
|
|
39
39
|
database_name = "cdp-edge-db"
|
|
40
|
-
database_id = "
|
|
40
|
+
database_id = "SEU_DATABASE_ID"
|
|
41
41
|
|
|
42
42
|
# ── Queues — Retry + Dead Letter Queue ───────────────────────────────────────
|
|
43
43
|
# Produtor: worker envia eventos com falha para cdp-edge-retry
|