fix: add HMR guard to prevent ConcurrentCallError on SDK double-init

При HMR Vite перевыполняет main.tsx, вызывая повторный init() и mount*()
компонентов SDK. mountThemeParams() и другие бросают ConcurrentCallError
асинхронно, что не ловится синхронным try/catch.

Решение: флаг на window.__tg_sdk_initialized предотвращает повторную
инициализацию при горячей перезагрузке модуля.
This commit is contained in:
c0mrade
2026-02-04 11:25:10 +03:00
parent 61e3910981
commit bcbda17220

View File

@@ -26,35 +26,51 @@ import { getCachedFullscreenEnabled, isTelegramMobile } from './hooks/useTelegra
import './i18n'; import './i18n';
import './styles/globals.css'; import './styles/globals.css';
// Safe mount helper — ignores "already mounted/mounting" errors (HMR, StrictMode) // HMR guard — prevent double init when Vite hot-reloads the module
function safeMountSync(fn: () => void) { const HMR_KEY = '__tg_sdk_initialized';
try { const alreadyInitialized = (window as unknown as Record<string, unknown>)[HMR_KEY] === true;
fn();
} catch { if (!alreadyInitialized) {
// Already mounted or not available (window as unknown as Record<string, unknown>)[HMR_KEY] = true;
}
}
// Initialize Telegram SDK v3
try { try {
init(); init();
restoreInitData(); restoreInitData();
safeMountSync(() => mountMiniApp()); // Mount components — each in its own try/catch so one failure doesn't block others
safeMountSync(() => { try {
mountMiniApp();
} catch {
/* already mounted */
}
try {
mountThemeParams(); mountThemeParams();
bindThemeParamsCssVars(); bindThemeParamsCssVars();
}); } catch {
safeMountSync(() => { /* already mounted */
}
try {
mountSwipeBehavior(); mountSwipeBehavior();
disableVerticalSwipes(); disableVerticalSwipes();
}); } catch {
safeMountSync(() => { /* already mounted */
}
try {
mountClosingBehavior(); mountClosingBehavior();
disableClosingConfirmation(); disableClosingConfirmation();
}); } catch {
safeMountSync(() => mountBackButton()); /* already mounted */
safeMountSync(() => mountMainButton()); }
try {
mountBackButton();
} catch {
/* already mounted */
}
try {
mountMainButton();
} catch {
/* already mounted */
}
// Viewport — async, fullscreen зависит от смонтированного viewport // Viewport — async, fullscreen зависит от смонтированного viewport
mountViewport() mountViewport()
@@ -75,6 +91,7 @@ try {
} catch { } catch {
// Not in Telegram — ok // Not in Telegram — ok
} }
}
// Preload logo from cache immediately on page load // Preload logo from cache immediately on page load
initLogoPreload(); initLogoPreload();