CognxSafeTrack commited on
Commit
5443165
·
1 Parent(s): 9d2d3b2

feat(hardening): implement anti-silence, gateway loop protection and deep trace logging

Browse files
apps/api/src/routes/internal.ts CHANGED
@@ -156,5 +156,12 @@ export async function internalRoutes(fastify: FastifyInstance) {
156
 
157
  return reply.send({ ok: true });
158
  });
 
 
 
 
 
 
 
159
  }
160
 
 
156
 
157
  return reply.send({ ok: true });
158
  });
159
+
160
+ // ── Simple Ping for Token/Connectivity Verification ──────────────────────
161
+ fastify.get('/v1/internal/ping', {
162
+ config: { requireAuth: true } as any
163
+ }, async () => {
164
+ return { ok: true, message: 'Pong! Railway Internal API is reachable and authorized.', timestamp: new Date().toISOString() };
165
+ });
166
  }
167
 
apps/api/src/routes/whatsapp.ts CHANGED
@@ -113,26 +113,37 @@ export async function whatsappRoutes(fastify: FastifyInstance) {
113
 
114
  // ── 2. Forward to Railway Internal Worker (if configured as Gateway) ──
115
  const railwayInternalUrl = process.env.RAILWAY_INTERNAL_URL;
116
-
117
- if (railwayInternalUrl) {
118
- request.log.info(`[WEBHOOK] Gateway Mode: Forwarding payload to Railway API (${railwayInternalUrl}).`);
119
- try {
120
- // Fire and forget (don't await) to ensure fast 200 response to Meta
121
- fetch(`${railwayInternalUrl}/v1/internal/whatsapp/inbound`, {
122
- method: 'POST',
123
- headers: {
124
- 'Content-Type': 'application/json',
125
- 'Authorization': `Bearer ${process.env.ADMIN_API_KEY || ''}`
126
- },
127
- body: request.body ? JSON.stringify(request.body) : ''
128
- }).catch(err => {
129
- request.log.error(`[WEBHOOK] Forward to Railway failed: ${err.message}`);
130
- });
131
-
132
- return reply.code(200).send('EVENT_RECEIVED');
133
- } catch (error: any) {
134
- request.log.error(`[WEBHOOK] Forward throwing error: ${error?.message}`);
135
- return reply.code(500).send({ error: 'Gateway forwarding failed' });
 
 
 
 
 
 
 
 
 
 
 
136
  }
137
  }
138
 
 
113
 
114
  // ── 2. Forward to Railway Internal Worker (if configured as Gateway) ──
115
  const railwayInternalUrl = process.env.RAILWAY_INTERNAL_URL;
116
+ const isGateway = process.env.IS_GATEWAY === 'true' || process.env.HF_SPACE_ID !== undefined;
117
+
118
+ if (railwayInternalUrl && isGateway) {
119
+ // Loop protection: don't forward to yourself
120
+ const myUrl = process.env.RAILWAY_PUBLIC_URL || '';
121
+ if (railwayInternalUrl.includes(myUrl) && myUrl !== '') {
122
+ request.log.warn('[WEBHOOK] Loop detected: RAILWAY_INTERNAL_URL matches self. Skipping forward.');
123
+ } else {
124
+ const targetUrl = `${railwayInternalUrl.replace(/\/$/, '')}/v1/internal/whatsapp/inbound`;
125
+ request.log.info(`[WEBHOOK] Gateway Mode: Forwarding payload to ${targetUrl}`);
126
+
127
+ try {
128
+ // Fire and forget (don't await) to ensure fast 200 response to Meta
129
+ fetch(targetUrl, {
130
+ method: 'POST',
131
+ headers: {
132
+ 'Content-Type': 'application/json',
133
+ 'Authorization': `Bearer ${process.env.ADMIN_API_KEY || ''}`
134
+ },
135
+ body: request.body ? JSON.stringify(request.body) : ''
136
+ }).then(res => {
137
+ request.log.info(`[WEBHOOK] Gateway forward result: ${res.status} ${res.statusText}`);
138
+ }).catch(err => {
139
+ request.log.error(`[WEBHOOK] Forward to Railway failed: ${err.message}`);
140
+ });
141
+
142
+ return reply.code(200).send('EVENT_RECEIVED');
143
+ } catch (error: any) {
144
+ request.log.error(`[WEBHOOK] Forward throwing error: ${error?.message}`);
145
+ return reply.code(500).send({ error: 'Gateway forwarding failed' });
146
+ }
147
  }
148
  }
149
 
apps/api/src/services/whatsapp.ts CHANGED
@@ -29,7 +29,10 @@ export class WhatsAppService {
29
  let user = await prisma.user.findUnique({ where: { phone } });
30
 
31
  if (!user) {
32
- if (normalizedText === 'INSCRIPTION') {
 
 
 
33
  user = await prisma.user.create({ data: { phone } });
34
  await scheduleInteractiveButtons(user.id,
35
  "Dalal jàmm! Xamle ngay tàmbali. ⏳ 30s.\n(FR) Ton cours se prépare (30s).",
@@ -40,7 +43,13 @@ export class WhatsAppService {
40
  );
41
  return;
42
  } else {
43
- console.log(`Unregistered user ${phone} sent a message. Need INSCRIPTION.`);
 
 
 
 
 
 
44
  return;
45
  }
46
  }
 
29
  let user = await prisma.user.findUnique({ where: { phone } });
30
 
31
  if (!user) {
32
+ const isInscription = normalizedText === 'INSCRIPTION' || normalizedText.includes('INSCRI');
33
+
34
+ if (isInscription) {
35
+ console.log(`${traceId} New user registration triggered for ${phone}`);
36
  user = await prisma.user.create({ data: { phone } });
37
  await scheduleInteractiveButtons(user.id,
38
  "Dalal jàmm! Xamle ngay tàmbali. ⏳ 30s.\n(FR) Ton cours se prépare (30s).",
 
43
  );
44
  return;
45
  } else {
46
+ console.log(`${traceId} Unregistered user ${phone} sent: "${normalizedText}". Sending instructions.`);
47
+ // Anti-silence: Nudge them to register
48
+ const { whatsappQueue } = await import('./queue');
49
+ await whatsappQueue.add('send-message-direct', {
50
+ phone,
51
+ text: "🎓 Bienvenue chez XAMLÉ !\nPour commencer ta formation gratuite, envoie le mot : *INSCRIPTION*\n\n(WO) Dalal jàmm ! Ngir tàmbali sa njàng mburu, bindal : *INSCRIPTION*"
52
+ });
53
  return;
54
  }
55
  }