diff --git a/apps/bot/src/ipc/pair-handler.ts b/apps/bot/src/ipc/pair-handler.ts index c8b84fa..ad4d31f 100644 --- a/apps/bot/src/ipc/pair-handler.ts +++ b/apps/bot/src/ipc/pair-handler.ts @@ -128,7 +128,12 @@ export async function handleStartPairing(accountId: string): Promise { count: synced, }); off(); - } else if (event.type === "close" && event.loggedOut) { + } else if (event.type === "close") { + // During the pairing window, ANY close means the QR window + // ended without a successful link — Baileys' default is to + // close after exhausting QR refs (~2.5 min). Surface this to + // the UI so the user gets a "pairing timed out" screen and a + // chance to retry, instead of staring at a stale QR forever. const t = pairTimeouts.get(id); if (t) { clearTimeout(t); diff --git a/apps/bot/src/whatsapp/session-manager.ts b/apps/bot/src/whatsapp/session-manager.ts index 50ff71e..246f50b 100644 --- a/apps/bot/src/whatsapp/session-manager.ts +++ b/apps/bot/src/whatsapp/session-manager.ts @@ -141,14 +141,30 @@ class SessionManager { .set({ status: event.loggedOut ? "logged_out" : "disconnected" }) .where(eq(whatsappAccounts.id, accountId)); - if (!event.loggedOut) { - const timer = setTimeout(() => { - this.reconnectTimers.delete(accountId); - void this.stop(accountId).then(() => this.start(accountId)); - }, 5000); - this.reconnectTimers.set(accountId, timer); - } else { + if (event.loggedOut) { await this.stop(accountId); + } else { + // Only auto-reconnect for accounts that have been linked at least + // once — `lastConnectedAt` is set on `open`. During an initial + // pairing attempt the close event fires every time Baileys + // exhausts QR refs (~every 30s). Reconnecting would restart the + // pair dance and rotate the QR every few seconds — pair-handler + // already manages the pairing window via its own 5-min timeout. + const account = await db.query.whatsappAccounts.findFirst({ + where: (a, { eq }) => eq(a.id, accountId), + columns: { lastConnectedAt: true }, + }); + if (account?.lastConnectedAt) { + const timer = setTimeout(() => { + this.reconnectTimers.delete(accountId); + void this.stop(accountId).then(() => this.start(accountId)); + }, 5000); + this.reconnectTimers.set(accountId, timer); + } else { + // Brand-new account that hasn't authenticated yet — let the + // pair-handler clean up via its timeout. + await this.stop(accountId); + } } } else if (event.type === "qr") { await db