From 47c77f2e1bb7ec469bd9f242567bc76e2d52232f Mon Sep 17 00:00:00 2001 From: michumichifu Date: Fri, 7 Aug 2026 16:12:17 -0400 Subject: [PATCH] fix(instance): do not open a second connection on repeated clicks Every click on the QR code / pairing code button called GET /instance/connect with no guard. When the instance is closed that endpoint does not return a cached code: it calls connectToWhatsapp() and opens a brand new WhatsApp connection. Clicking twice therefore left two sockets racing for the same instance, each producing its own QR code, and the phone could end up scanning the one that the other socket had already invalidated. Keep a single request in flight: a ref guards against overlapping calls and is checked synchronously, since two fast clicks would both pass a state-based check before React re-renders. The buttons are disabled while the request is running, so the user gets feedback instead of clicking again. --- .../instance/DashboardInstance/index.tsx | 25 ++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/src/pages/instance/DashboardInstance/index.tsx b/src/pages/instance/DashboardInstance/index.tsx index bd013cd..868f31b 100644 --- a/src/pages/instance/DashboardInstance/index.tsx +++ b/src/pages/instance/DashboardInstance/index.tsx @@ -5,7 +5,7 @@ import { Button } from "@evoapi/design-system/button"; import { Card, CardContent, CardFooter, CardHeader, CardTitle } from "@evoapi/design-system/card"; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTrigger } from "@/components/ui/dialog"; import { CircleUser, LogOut, MessageCircle, Power, QrCode, RefreshCw, Send, UsersRound } from "lucide-react"; -import { useEffect, useMemo, useState } from "react"; +import { useEffect, useMemo, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; import QRCode from "react-qr-code"; @@ -28,8 +28,12 @@ function DashboardInstance() { const numberFormatter = new Intl.NumberFormat(i18n.language); const [qrCode, setQRCode] = useState(null); const [pairingCode, setPairingCode] = useState(""); + const [isConnecting, setIsConnecting] = useState(false); const [goQrOpen, setGoQrOpen] = useState(false); const [goSendOpen, setGoSendOpen] = useState(false); + // Ref instead of state: it has to be readable synchronously, before React + // has had a chance to re-render, or two fast clicks both pass the check. + const connectInFlight = useRef(false); const token = getToken(TOKEN_ID.TOKEN); const isGo = getProvider() === "go"; const { theme } = useTheme(); @@ -67,7 +71,19 @@ function DashboardInstance() { } }; + /** + * Ask the server for a QR code or a pairing code. + * + * Only one request may be in flight at a time. When the instance is closed, + * GET /instance/connect opens a brand new WhatsApp connection, so clicking + * the button twice left two sockets racing for the same instance: the phone + * would scan the code of one while the other overwrote the pairing state. + */ const handleConnect = async (instanceName: string, wantPairing: boolean) => { + if (connectInFlight.current) return; + connectInFlight.current = true; + setIsConnecting(true); + try { setQRCode(null); if (!token) return console.error("Token not found."); @@ -81,6 +97,9 @@ function DashboardInstance() { } } catch (error) { console.error("Error:", error); + } finally { + connectInFlight.current = false; + setIsConnecting(false); } }; @@ -187,7 +206,7 @@ function DashboardInstance() {
handleConnect(instance.name, false)} asChild> - @@ -207,7 +226,7 @@ function DashboardInstance() { {instance.number && ( -