# Post-Mortems — XAMLÉ Platform Ce document recense les bugs significatifs rencontrés en production, leurs causes racines, et les règles à appliquer pour ne pas les reproduire. À lire avant tout changement touchant l'infrastructure, les APIs tierces ou l'authentification. --- ## Architecture de référence ``` Netlify (Admin Frontend — React/Vite) │ │ VITE_API_URL = https://safetrack-edtech.hf.space ▼ HuggingFace Space (safetrack/edtech) └── API Fastify [port 7860] ← répond à toutes les requêtes REST du frontend │ │ BullMQ (Redis partagé) ▼ Railway "whatsapp-worker" ├── API Fastify [port 7860] ← PM2, même image Docker └── Worker Bridge [port 8082] ← consommateur BullMQ, envoie les messages WhatsApp seul à pouvoir appeler graph.facebook.com (HuggingFace est bloqué réseau vers Meta) ``` > **Règle fondamentale :** HuggingFace ne peut **pas** appeler `graph.facebook.com` directement. Tout appel Meta doit passer par le Worker Railway via un job BullMQ. --- ## Post-Mortem #1 — Daily Limit affiche « — » (mai 2026) ### Symptôme La carte "Daily Limit" (Limite Quotidienne WhatsApp) sur `/clients` affichait `—` au lieu du tier (`TIER_1K`, `TIER_10K`…). ### Cause racine Le service `fetchMetaStatus` appelait l'API Meta avec la version **v19.0** : ```typescript `https://graph.facebook.com/v19.0/${phoneNumberId}?fields=messaging_limit_tier,quality_rating` ``` `v19.0` a été **dépréciée en février 2026** (2 ans après sa sortie en février 2024). Meta renvoie une erreur de dépréciation, mais le `catch { /* non-fatal */ }` du code la masquait silencieusement. `messagingLimitTier` restait `undefined` → affichage `—`. Le même problème touchait **7 fichiers** avec des versions hardcodées incohérentes (`v18.0` et `v19.0` mélangées). ### Correction appliquée - Toutes les URLs Meta remplacées par `process.env.META_GRAPH_API_VERSION || 'v22.0'` - Le `catch` silencieux remplacé par un `logger.error` visible - Log de démarrage ajouté dans l'API et le Worker : `✅ [META] Graph API version: v22.0` ### Règles à retenir > ⚠️ **Ne jamais hardcoder une version d'API Meta dans le code.** > Toujours utiliser la variable d'environnement `META_GRAPH_API_VERSION`. > ⚠️ **Meta déprécie ses versions tous les ~2 ans.** > Vérifier la version active sur [developers.facebook.com/docs/graph-api/changelog](https://developers.facebook.com/docs/graph-api/changelog) lors de chaque mise à jour majeure du projet. > ⚠️ **Un `catch { /* non-fatal */ }` masque les bugs en production.** > Tout catch doit au minimum faire un `logger.error(err)`. ### Procédure de mise à jour future 1. Changer `META_GRAPH_API_VERSION=vXX.0` dans Railway (variables d'env) 2. Redémarrer le service → vérifier le log `✅ [META] Graph API version: vXX.0` 3. Vider le cache Redis : `redis-cli --scan --pattern "meta:status:*" | xargs redis-cli del` 4. **Aucun changement de code nécessaire** --- ## Post-Mortem #2 — SSE Stream : 401 Unauthorized (mai 2026) ### Symptôme ``` safetrack-edtech.hf.space/v1/organizations/.../stream → 401 Unauthorized ``` La connexion SSE (Server-Sent Events) pour les notifications en temps réel échouait systématiquement. Le badge de messages non lus ne se mettait jamais à jour. ### Cause racine : deux problèmes distincts **2A — `VITE_API_URL` pointait vers le mauvais service** À un moment, la variable pointait vers `https://whatsapp-worker-production-0bc0.up.railway.app` (le Worker Railway) au lieu de `https://safetrack-edtech.hf.space` (l'API HuggingFace). Le Worker n'expose que 2 routes (`/v1/internal/whatsapp/inbound` et `/health`). Tous les autres appels retournaient 401/404. **2B — L'API `EventSource` du navigateur ne peut pas envoyer de headers** ```typescript // ❌ AVANT — le JWT n'arrive jamais au serveur const es = new EventSource(`${apiBase}/v1/organizations/${orgId}/stream`); // ✅ APRÈS — le JWT passe en query param const es = new EventSource(`${apiBase}/v1/organizations/${orgId}/stream?token=${encodeURIComponent(token)}`); ``` L'API web `EventSource` est une limitation navigateur : elle ne supporte pas les headers personnalisés (`Authorization: Bearer ...`). Le serveur ne recevait donc jamais le JWT → 401. ### Correction appliquée - **Frontend** (`MainLayout.tsx`, `CrmConversationalDashboard.tsx`) : ajout de `?token=` dans l'URL EventSource - **Backend** (`verifyJwt.ts`) : injection du query param dans le header `Authorization` avant `jwtVerify()` ```typescript // verifyJwt.ts — fallback query param pour SSE const queryToken = (request.query as Record)?.token; if (queryToken && !request.headers.authorization) { request.headers.authorization = `Bearer ${queryToken}`; } await request.jwtVerify(); ``` ### Règles à retenir > ⚠️ **`EventSource` ne supporte pas les headers custom.** > Toute connexion SSE nécessite le token en query param (`?token=...`). C'est le standard pour SSE — ne pas essayer de passer le JWT autrement. > ⚠️ **`VITE_API_URL` doit toujours pointer vers l'API HuggingFace**, jamais vers le Worker Railway. > Valeur correcte sur Netlify : `https://safetrack-edtech.hf.space` > ℹ️ Le service Railway s'appelle "whatsapp-worker" mais fait tourner les deux processus (API + Worker) via PM2 sur des ports distincts (7860 et 8082). Le nom du service Railway est trompeur. --- ## Post-Mortem #3 — Import Excel : "File chooser dialog can only be shown with a user activation" (mai 2026) ### Symptôme Cliquer sur "Parcourir mes fichiers" dans le CRM ouvrait un sélecteur de fichier, mais après sélection du fichier, une erreur console bloquait le traitement : ``` File chooser dialog can only be shown with a user activation. onFileUpload @ index-BcnTfhzI.js:598 ``` ### Cause racine Deux `` en cascade — architecture incorrecte entre `CrmAIAssistant` et `FileImporter` : ``` Utilisateur clique "Parcourir mes fichiers" └── CrmAIAssistant :