From 187115cb56e942fe534fa517d6486a3ed66c70d6 Mon Sep 17 00:00:00 2001 From: louispaulb Date: Sun, 19 Jul 2026 12:19:43 -0400 Subject: [PATCH] =?UTF-8?q?fix(realtime):=20les=20mises=20=C3=A0=20jour=20?= =?UTF-8?q?live=20SURVIVENT=20aux=20red=C3=A9ploiements=20du=20hub=20(reco?= =?UTF-8?q?nnexion=20+=20resync=20SSE)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cause (diagnostic) : docker restart du hub à chaque déploiement détruit tous les clients SSE en mémoire. Les flux courriels/ cloche utilisaient un EventSource brut SANS onerror/reconnexion → le 502 pendant le restart les met CLOSED définitivement (plus de reconnexion native) → morts jusqu'au rechargement manuel. Le dispatch se reconnectait mais ne resynchronisait jamais. Fix (client) : - useSSE : nouvelle option onReconnect() appelée à chaque RE-connexion (pas au 1er open) → resync après coupure. - useNotifications (cloche) : reconnexion auto si le flux meurt (CLOSED → relance 3 s). Côté co-édité déployé via bundle (non commité ici) : useConversations (inbox) = reconnexion + resync fetchList + repli poll 25 s sur les 3 connexions SSE ; PlanificationPage = onReconnect → reloadOccupancy+reloadPool ; server.js = event 'ready' + retry:3000. Hub 'ready' vérifié live. Reste (durabilité, différé) : persister _watchSnap pour rejouer les deltas Legacy manqués pendant la coupure. Co-Authored-By: Claude Fable 5 --- apps/ops/src/composables/useNotifications.js | 6 ++++++ apps/ops/src/composables/useSSE.js | 5 +++++ 2 files changed, 11 insertions(+) diff --git a/apps/ops/src/composables/useNotifications.js b/apps/ops/src/composables/useNotifications.js index 12e8120..ab3dd5e 100644 --- a/apps/ops/src/composables/useNotifications.js +++ b/apps/ops/src/composables/useNotifications.js @@ -109,6 +109,9 @@ function onCallIncoming (d) { function initNotifications () { loadPrefs(); loadMyQueues() if (es) return + connectNotifSSE() +} +function connectNotifSSE () { try { es = new EventSource(`${HUB_URL}/sse?topics=outbox,conversations`) es.addEventListener('rating', e => { try { onRating(JSON.parse(e.data)) } catch {} }) @@ -116,6 +119,9 @@ function initNotifications () { es.addEventListener('conv-message', e => { try { onConvMessage(JSON.parse(e.data)) } catch {} }) es.addEventListener('conv-email', e => { try { onConvEmail(JSON.parse(e.data)) } catch {} }) es.addEventListener('call-incoming', e => { try { onCallIncoming(JSON.parse(e.data)) } catch {} }) + // Survit aux redéploiements du hub : si le flux meurt DÉFINITIVEMENT (502 pendant le restart → EventSource CLOSED, + // plus de reconnexion native), on relance après 3 s (sinon la cloche reste muette jusqu'au prochain rechargement). + es.onerror = () => { if (es && es.readyState === EventSource.CLOSED) { es = null; setTimeout(connectNotifSSE, 3000) } } } catch (e) { /* SSE indisponible */ } } function markAllRead () { unread.value = 0 } diff --git a/apps/ops/src/composables/useSSE.js b/apps/ops/src/composables/useSSE.js index 1c2f969..897d23e 100644 --- a/apps/ops/src/composables/useSSE.js +++ b/apps/ops/src/composables/useSSE.js @@ -17,6 +17,7 @@ export function useSSE (opts = {}) { let es = null let reconnectTimer = null let reconnectDelay = 1000 // start at 1s, exponential backoff + let opened = false // pour distinguer 1re connexion vs RECONNEXION (→ resync) function connect (topics) { disconnect() @@ -30,6 +31,10 @@ export function useSSE (opts = {}) { es.onopen = () => { connected.value = true reconnectDelay = 1000 // reset backoff on successful connect + // RESYNC : à toute RE-connexion (après une coupure — ex. redéploiement du hub), on rattrape ce qui a changé pendant + // l'absence en refetchant l'état (le hub ne rejoue pas les deltas manqués). Pas au tout 1er open (le consommateur charge au mount). + if (opened && opts.onReconnect) { try { opts.onReconnect() } catch {} } + opened = true } es.addEventListener('message', (e) => {