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 |
-
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
| 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
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
}
|