From d6e71f915c23d1f83cd18121f70f51c12819f090 Mon Sep 17 00:00:00 2001 From: Madhura68 Date: Mon, 27 Apr 2026 23:53:50 +0200 Subject: [PATCH] fix(ST-1007): listen for SSE 'state' event so approve-during-connect resolves MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit De SSE-route in ST-1004 stuurt de catch-up payload als `event: state\ndata: …` om een race te dichten: tussen pair/start en SSE-open kan de mobiel approven, de pg_notify fired vóór onze LISTEN actief is en gaat verloren (Postgres queuet niet). De server compenseert door direct na connect een `state`-event te sturen met de huidige status uit de DB. Maar de client luisterde alleen op 'message'. EventSource routeert events met `event: ` enkel naar listeners voor die exacte naam — het catch-up event werd dus genegeerd. Gevolg bij een (zeldzame) race: QR blijft hangen tot expiry omdat noch de notify noch de catch-up doorkomt. Fix: dezelfde onMessage-handler ook aan 'state' binden (en netjes unsubscriben bij cleanup). Geen server-side wijziging nodig — protocol bleef bewust om de semantische scheiding 'initial state' vs 'live notify' te behouden voor toekomstige clients die er onderscheid in willen maken. Severity: middel-laag — kleine race-window, geen data/security-impact, alleen "QR doet niks" tot user op Vernieuwen klikt. Co-Authored-By: Claude Opus 4.7 (1M context) --- app/(auth)/login/qr-login-button.tsx | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/app/(auth)/login/qr-login-button.tsx b/app/(auth)/login/qr-login-button.tsx index 8869243..5332899 100644 --- a/app/(auth)/login/qr-login-button.tsx +++ b/app/(auth)/login/qr-login-button.tsx @@ -106,11 +106,20 @@ export function QrLoginButton() { // Geen actie nodig tenzij we definitief willen falen. } + // De server stuurt direct na connect een `event: state`-payload met de + // huidige pairing-status (catch-up voor de race tussen pair/start en de + // SSE-open: als de mobiel net daarvoor approvet komt de notify door + // vóórdat onze LISTEN actief is en wordt 'ie verloren). EventSource + // routeert events met `event: ` alleen naar listeners voor die + // naam — niet naar 'message'. Dezelfde handler aan beide hangen vangt + // de catch-up én reguliere notifies op. es.addEventListener('message', onMessage) + es.addEventListener('state', onMessage as unknown as EventListener) es.addEventListener('error', onError) return () => { es.removeEventListener('message', onMessage) + es.removeEventListener('state', onMessage as unknown as EventListener) es.removeEventListener('error', onError) es.close() sseRef.current = null