From 209d58a525de8a20c229c22eff8072c4d7175d78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Padez?= Date: Fri, 27 Feb 2026 12:41:46 +0000 Subject: [PATCH] whatsapp qr code polling, validate token before save, fix bot startup timing Co-Authored-By: Claude Opus 4.6 --- .../WhatsAppBotConfig.tsx | 102 +++++++++--------- src/servers/channels/routes.ts | 63 ++--------- 2 files changed, 62 insertions(+), 103 deletions(-) diff --git a/src/apps/officer-web/Screens/Dashboard/Settings/IntegrationsSettings/WhatsAppBotConfig.tsx b/src/apps/officer-web/Screens/Dashboard/Settings/IntegrationsSettings/WhatsAppBotConfig.tsx index 87bde35b..8dc31316 100644 --- a/src/apps/officer-web/Screens/Dashboard/Settings/IntegrationsSettings/WhatsAppBotConfig.tsx +++ b/src/apps/officer-web/Screens/Dashboard/Settings/IntegrationsSettings/WhatsAppBotConfig.tsx @@ -13,10 +13,10 @@ type WhatsAppConfig = { phone: string | null; }; -type SSEEvent = { - type: 'qr' | 'authenticated' | 'disconnected' | 'waiting'; - qr?: string; - phone?: string; +type QRResponse = { + qr: string | null; + running: boolean; + phone: string | null; }; const SetupGuide = () => { @@ -72,7 +72,7 @@ export const WhatsAppBotConfig = () => { const [qrDataUrl, setQrDataUrl] = useState(null); const [isConnecting, setIsConnecting] = useState(false); const [isDisconnecting, setIsDisconnecting] = useState(false); - const eventSourceRef = useRef(null); + const pollingRef = useRef | null>(null); const fetchConfig = useCallback(() => { client @@ -81,60 +81,59 @@ export const WhatsAppBotConfig = () => { .catch(() => {}); }, [client]); - useEffect(() => { - fetchConfig(); - setIsLoading(false); + const stopPolling = useCallback(() => { + if (pollingRef.current) { + clearInterval(pollingRef.current); + pollingRef.current = null; + } }, []); - const connectSSE = useCallback(() => { - if (eventSourceRef.current) { - eventSourceRef.current.close(); - } - - const token = localStorage.getItem('token') ?? sessionStorage.getItem('token') ?? ''; - const url = `/api/channels/whatsapp/qr?token=${encodeURIComponent(token)}`; - const es = new EventSource(url); - eventSourceRef.current = es; - - es.onmessage = async (ev) => { - try { - const data = JSON.parse(ev.data) as SSEEvent; - if (data.type === 'qr' && data.qr) { + const pollQR = useCallback(() => { + client + .get('/channels/whatsapp/qr') + .then(async (data) => { + if (data.running) { + // Authenticated — stop polling, update config + stopPolling(); + setQrDataUrl(null); + fetchConfig(); + return; + } + if (data.qr) { const dataUrl = await QRCode.toDataURL(data.qr, { width: 256, margin: 2 }); setQrDataUrl(dataUrl); - } else if (data.type === 'authenticated') { - setQrDataUrl(null); - es.close(); - fetchConfig(); - } else if (data.type === 'disconnected') { - setQrDataUrl(null); - es.close(); - fetchConfig(); } - } catch { - // ignore parse errors - } - }; + }) + .catch(() => {}); + }, [client, fetchConfig, stopPolling]); - es.onerror = () => { - es.close(); - setQrDataUrl(null); - }; - }, [fetchConfig]); + const startPolling = useCallback(() => { + stopPolling(); + pollQR(); + pollingRef.current = setInterval(pollQR, 2000); + }, [pollQR, stopPolling]); useEffect(() => { - return () => { - if (eventSourceRef.current) { - eventSourceRef.current.close(); - } - }; + client + .get('/channels/whatsapp/config') + .then((data) => { + setConfig(data); + // Auto-poll QR if bot is enabled but waiting for scan + if (data.enabled && !data.running) { + startPolling(); + } + }) + .catch(() => {}) + .finally(() => setIsLoading(false)); + + return stopPolling; }, []); const handleConnect = async () => { setIsConnecting(true); try { await client.put('/channels/whatsapp/config', { enabled: true }); - connectSSE(); + startPolling(); } catch { toast.error('Failed to start WhatsApp connection'); } finally { @@ -146,10 +145,7 @@ export const WhatsAppBotConfig = () => { setIsDisconnecting(true); try { await client.put('/channels/whatsapp/config', { enabled: false }); - if (eventSourceRef.current) { - eventSourceRef.current.close(); - eventSourceRef.current = null; - } + stopPolling(); setQrDataUrl(null); toast.success('WhatsApp disconnected'); fetchConfig(); @@ -170,7 +166,11 @@ export const WhatsAppBotConfig = () => { className={`h-2.5 w-2.5 rounded-full shrink-0 ${config.running ? 'bg-green-500' : 'bg-duck-dark/20 dark:bg-foreground/20'}`} /> - {config.running ? `Connected as +${config.phone}` : config.enabled ? 'Starting...' : 'Not connected'} + {config.running + ? `Connected as +${config.phone}` + : config.enabled + ? 'Waiting for QR scan...' + : 'Not connected'} )} @@ -189,7 +189,7 @@ export const WhatsAppBotConfig = () => { )} - {config?.running ? ( + {config?.running || config?.enabled ? (