CognxSafeTrack commited on
Commit
b8d93e2
·
1 Parent(s): 87dcd87

docs: add post-mortems for Meta API deprecation, SSE 401 and file import bugs

Browse files
Files changed (1) hide show
  1. docs/post-mortems.md +191 -0
docs/post-mortems.md ADDED
@@ -0,0 +1,191 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Post-Mortems — XAMLÉ Platform
2
+
3
+ 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.
4
+
5
+ ---
6
+
7
+ ## Architecture de référence
8
+
9
+ ```
10
+ Netlify (Admin Frontend — React/Vite)
11
+
12
+ │ VITE_API_URL = https://safetrack-edtech.hf.space
13
+
14
+ HuggingFace Space (safetrack/edtech)
15
+ └── API Fastify [port 7860] ← répond à toutes les requêtes REST du frontend
16
+
17
+ │ BullMQ (Redis partagé)
18
+
19
+ Railway "whatsapp-worker"
20
+ ├── API Fastify [port 7860] ← PM2, même image Docker
21
+ └── Worker Bridge [port 8082] ← consommateur BullMQ, envoie les messages WhatsApp
22
+ seul à pouvoir appeler graph.facebook.com
23
+ (HuggingFace est bloqué réseau vers Meta)
24
+ ```
25
+
26
+ > **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.
27
+
28
+ ---
29
+
30
+ ## Post-Mortem #1 — Daily Limit affiche « — » (mai 2026)
31
+
32
+ ### Symptôme
33
+ La carte "Daily Limit" (Limite Quotidienne WhatsApp) sur `/clients` affichait `—` au lieu du tier (`TIER_1K`, `TIER_10K`…).
34
+
35
+ ### Cause racine
36
+ Le service `fetchMetaStatus` appelait l'API Meta avec la version **v19.0** :
37
+ ```typescript
38
+ `https://graph.facebook.com/v19.0/${phoneNumberId}?fields=messaging_limit_tier,quality_rating`
39
+ ```
40
+ `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 `—`.
41
+
42
+ Le même problème touchait **7 fichiers** avec des versions hardcodées incohérentes (`v18.0` et `v19.0` mélangées).
43
+
44
+ ### Correction appliquée
45
+ - Toutes les URLs Meta remplacées par `process.env.META_GRAPH_API_VERSION || 'v22.0'`
46
+ - Le `catch` silencieux remplacé par un `logger.error` visible
47
+ - Log de démarrage ajouté dans l'API et le Worker : `✅ [META] Graph API version: v22.0`
48
+
49
+ ### Règles à retenir
50
+
51
+ > ⚠️ **Ne jamais hardcoder une version d'API Meta dans le code.**
52
+ > Toujours utiliser la variable d'environnement `META_GRAPH_API_VERSION`.
53
+
54
+ > ⚠️ **Meta déprécie ses versions tous les ~2 ans.**
55
+ > 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.
56
+
57
+ > ⚠️ **Un `catch { /* non-fatal */ }` masque les bugs en production.**
58
+ > Tout catch doit au minimum faire un `logger.error(err)`.
59
+
60
+ ### Procédure de mise à jour future
61
+ 1. Changer `META_GRAPH_API_VERSION=vXX.0` dans Railway (variables d'env)
62
+ 2. Redémarrer le service → vérifier le log `✅ [META] Graph API version: vXX.0`
63
+ 3. Vider le cache Redis : `redis-cli --scan --pattern "meta:status:*" | xargs redis-cli del`
64
+ 4. **Aucun changement de code nécessaire**
65
+
66
+ ---
67
+
68
+ ## Post-Mortem #2 — SSE Stream : 401 Unauthorized (mai 2026)
69
+
70
+ ### Symptôme
71
+ ```
72
+ safetrack-edtech.hf.space/v1/organizations/.../stream → 401 Unauthorized
73
+ ```
74
+ 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.
75
+
76
+ ### Cause racine : deux problèmes distincts
77
+
78
+ **2A — `VITE_API_URL` pointait vers le mauvais service**
79
+ À 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.
80
+
81
+ **2B — L'API `EventSource` du navigateur ne peut pas envoyer de headers**
82
+ ```typescript
83
+ // ❌ AVANT — le JWT n'arrive jamais au serveur
84
+ const es = new EventSource(`${apiBase}/v1/organizations/${orgId}/stream`);
85
+
86
+ // ✅ APRÈS — le JWT passe en query param
87
+ const es = new EventSource(`${apiBase}/v1/organizations/${orgId}/stream?token=${encodeURIComponent(token)}`);
88
+ ```
89
+ 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.
90
+
91
+ ### Correction appliquée
92
+ - **Frontend** (`MainLayout.tsx`, `CrmConversationalDashboard.tsx`) : ajout de `?token=` dans l'URL EventSource
93
+ - **Backend** (`verifyJwt.ts`) : injection du query param dans le header `Authorization` avant `jwtVerify()`
94
+
95
+ ```typescript
96
+ // verifyJwt.ts — fallback query param pour SSE
97
+ const queryToken = (request.query as Record<string, string>)?.token;
98
+ if (queryToken && !request.headers.authorization) {
99
+ request.headers.authorization = `Bearer ${queryToken}`;
100
+ }
101
+ await request.jwtVerify();
102
+ ```
103
+
104
+ ### Règles à retenir
105
+
106
+ > ⚠️ **`EventSource` ne supporte pas les headers custom.**
107
+ > 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.
108
+
109
+ > ⚠️ **`VITE_API_URL` doit toujours pointer vers l'API HuggingFace**, jamais vers le Worker Railway.
110
+ > Valeur correcte sur Netlify : `https://safetrack-edtech.hf.space`
111
+
112
+ > ℹ️ 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.
113
+
114
+ ---
115
+
116
+ ## Post-Mortem #3 — Import Excel : "File chooser dialog can only be shown with a user activation" (mai 2026)
117
+
118
+ ### Symptôme
119
+ 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 :
120
+ ```
121
+ File chooser dialog can only be shown with a user activation.
122
+ onFileUpload @ index-BcnTfhzI.js:598
123
+ ```
124
+
125
+ ### Cause racine
126
+ Deux `<input type="file">` en cascade — architecture incorrecte entre `CrmAIAssistant` et `FileImporter` :
127
+
128
+ ```
129
+ Utilisateur clique "Parcourir mes fichiers"
130
+ └── CrmAIAssistant : <label> ouvre son propre <input type="file"> ← Input 1
131
+
132
+ └── onChange={() => onFileUpload()} ← déclenché après sélection
133
+
134
+ └── document.getElementById('crm-file-upload')?.click()
135
+ │ ← Input 2 (FileImporter)
136
+ └── 🚫 BLOQUÉ — .click() depuis un onChange()
137
+ n'est pas une "user activation" directe
138
+ (Chrome 126+, Safari 17+)
139
+ ```
140
+
141
+ Le fichier sélectionné dans l'Input 1 était **ignoré**. L'Input 2 tentait de s'ouvrir depuis un `onChange` — ce que les navigateurs modernes refusent.
142
+
143
+ ### Correction appliquée
144
+ Suppression de l'input embarqué dans `CrmAIAssistant`. Le `<label>` pointe maintenant directement sur l'input de `FileImporter` via `htmlFor` :
145
+
146
+ ```tsx
147
+ // ❌ AVANT — double input en cascade
148
+ <label>
149
+ Parcourir mes fichiers
150
+ <input type="file" onChange={() => onFileUpload()} /> {/* déclenche un 2ème input */}
151
+ </label>
152
+
153
+ // ✅ APRÈS — un seul input, liaison directe
154
+ <label htmlFor="crm-file-upload">
155
+ Parcourir mes fichiers
156
+ </label>
157
+ {/* FileImporter rend : <input id="crm-file-upload" type="file" onChange={handleFile} /> */}
158
+ ```
159
+
160
+ ### Règles à retenir
161
+
162
+ > ⚠️ **Ne jamais appeler `.click()` sur un `<input type="file">` depuis un handler `onChange`, `setTimeout`, ou toute fonction async.**
163
+ > Les navigateurs modernes exigent une activation utilisateur *directe* (click, keydown) pour ouvrir un sélecteur de fichier.
164
+
165
+ > ✅ **Pattern correct :** utiliser `<label htmlFor="id-de-l-input">` pour déclencher un input caché. C'est natif, fiable dans tous les navigateurs, aucun JavaScript nécessaire.
166
+
167
+ > ⚠️ **Ne jamais avoir deux `<input type="file">` pour le même flux d'upload.**
168
+ > Un seul input, un seul handler. Si plusieurs composants doivent déclencher le même upload, ils partagent tous le même `id` via `htmlFor`.
169
+
170
+ ---
171
+
172
+ ## Checklist avant tout déploiement
173
+
174
+ - [ ] `META_GRAPH_API_VERSION` est défini dans les variables d'env Railway ET est ≥ v21.0
175
+ - [ ] `VITE_API_URL` sur Netlify = `https://safetrack-edtech.hf.space` (HuggingFace, pas Railway)
176
+ - [ ] Toute connexion SSE passe le token en `?token=` dans l'URL
177
+ - [ ] Tout `catch` loggue au minimum l'erreur
178
+ - [ ] Aucun appel direct à `graph.facebook.com` depuis le code HuggingFace — passer par BullMQ
179
+
180
+ ---
181
+
182
+ ## Variables d'environnement critiques
183
+
184
+ | Variable | Service | Valeur correcte | Risque si absent/faux |
185
+ |---|---|---|---|
186
+ | `META_GRAPH_API_VERSION` | Railway API + Worker | `v22.0` (ou version récente) | Daily Limit affiche `—`, erreurs Meta silencieuses |
187
+ | `VITE_API_URL` | Netlify | `https://safetrack-edtech.hf.space` | Tous les appels API retournent 401/404 |
188
+ | `JWT_SECRET` | Railway | chaîne aléatoire ≥ 64 chars | Auth compromise |
189
+ | `ADMIN_API_KEY` | Railway | chaîne aléatoire ≥ 32 chars | Bridge Worker non sécurisé |
190
+ | `REDIS_URL` | Railway + HF | URL Redis partagée | BullMQ ne fonctionne plus, pas de jobs |
191
+ | `DATABASE_URL` | Railway + HF | Neon PostgreSQL | Toute l'app en erreur |