From 2d00a5c21fff339f229d8fe2001a898d15722cdd Mon Sep 17 00:00:00 2001 From: c0mrade Date: Wed, 4 Feb 2026 10:10:07 +0300 Subject: [PATCH] fix: prevent popup cascade when Telegram callback doesn't fire - Add persistent popupClosed listener that resets state even after per-call listener is removed by timeout - Increase safety timeout from 5s to 30s to give user time to interact - On WebAppPopupOpened error, keep popupOpen=true to block retries instead of resetting and allowing infinite error loop - Remove callback param from showPopup to avoid dual-listener conflicts - Remove debug console logs --- src/platform/adapters/TelegramAdapter.ts | 39 ++++++++++++++++-------- 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/src/platform/adapters/TelegramAdapter.ts b/src/platform/adapters/TelegramAdapter.ts index f4f29dc..e6be1b7 100644 --- a/src/platform/adapters/TelegramAdapter.ts +++ b/src/platform/adapters/TelegramAdapter.ts @@ -184,6 +184,17 @@ function createDialogController(): DialogController { const inTelegram = isInTelegramWebApp(); let popupOpen = false; + // Persistent listener: resets popupOpen whenever Telegram reports popup closed, + // even if our per-call listener was already removed. + if (inTelegram) { + const tg = getTelegram(); + if (tg?.onEvent) { + tg.onEvent('popupClosed', (() => { + popupOpen = false; + }) as (...args: unknown[]) => void); + } + } + function showPopupSafe( params: Parameters>[0], mapResult: (buttonId: string) => T, @@ -203,8 +214,6 @@ function createDialogController(): DialogController { popupOpen = true; let settled = false; - console.log('[popup] opening', JSON.stringify(params)); - const cleanup = () => { if (settled) return; settled = true; @@ -214,7 +223,6 @@ function createDialogController(): DialogController { }; const onPopupClosed = ((...args: unknown[]) => { - console.log('[popup] popupClosed event', JSON.stringify(args)); if (settled) return; const event = args[0] as { button_id?: string } | undefined; const buttonId = event?.button_id ?? ''; @@ -223,24 +231,29 @@ function createDialogController(): DialogController { }) as (...args: unknown[]) => void; const timer = setTimeout(() => { - console.log('[popup] timeout — force reset'); if (settled) return; cleanup(); resolve(mapResult('')); - }, 5_000); + }, 30_000); tg.onEvent?.('popupClosed', onPopupClosed); try { - tg.showPopup(params, (buttonId) => { - console.log('[popup] callback fired, buttonId=', buttonId); - if (settled) return; - cleanup(); - resolve(mapResult(buttonId)); - }); - console.log('[popup] showPopup called OK'); + tg.showPopup(params); } catch (e) { - console.error('[popup] showPopup threw', e); + // Telegram SDK says a popup is already open — don't reset popupOpen, + // so subsequent calls are silently blocked until the popup actually closes. + const isAlreadyOpen = e instanceof Error && e.message?.includes('WebAppPopupOpened'); + + if (isAlreadyOpen) { + settled = true; + clearTimeout(timer); + tg.offEvent?.('popupClosed', onPopupClosed); + // popupOpen stays true — the persistent listener will reset it + resolve(mapResult('')); + return; + } + cleanup(); resolve(fallback()); }