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,54 +26,71 @@ 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';
const alreadyInitialized = (window as unknown as Record<string, unknown>)[HMR_KEY] === true;
if (!alreadyInitialized) {
(window as unknown as Record<string, unknown>)[HMR_KEY] = true;
try { try {
fn(); init();
} catch { restoreInitData();
// Already mounted or not available
}
}
// Initialize Telegram SDK v3 // Mount components — each in its own try/catch so one failure doesn't block others
try { try {
init(); mountMiniApp();
restoreInitData(); } catch {
/* already mounted */
}
try {
mountThemeParams();
bindThemeParamsCssVars();
} catch {
/* already mounted */
}
try {
mountSwipeBehavior();
disableVerticalSwipes();
} catch {
/* already mounted */
}
try {
mountClosingBehavior();
disableClosingConfirmation();
} catch {
/* already mounted */
}
try {
mountBackButton();
} catch {
/* already mounted */
}
try {
mountMainButton();
} catch {
/* already mounted */
}
safeMountSync(() => mountMiniApp()); // Viewport — async, fullscreen зависит от смонтированного viewport
safeMountSync(() => { mountViewport()
mountThemeParams(); .then(() => {
bindThemeParamsCssVars(); bindViewportCssVars();
}); expandViewport();
safeMountSync(() => {
mountSwipeBehavior();
disableVerticalSwipes();
});
safeMountSync(() => {
mountClosingBehavior();
disableClosingConfirmation();
});
safeMountSync(() => mountBackButton());
safeMountSync(() => mountMainButton());
// Viewport — async, fullscreen зависит от смонтированного viewport // Auto-enter fullscreen if enabled in settings (mobile only)
mountViewport() if (getCachedFullscreenEnabled() && isTelegramMobile()) {
.then(() => { if (!isFullscreen()) {
bindViewportCssVars(); requestFullscreen();
expandViewport(); }
// Auto-enter fullscreen if enabled in settings (mobile only)
if (getCachedFullscreenEnabled() && isTelegramMobile()) {
if (!isFullscreen()) {
requestFullscreen();
} }
} })
}) .catch(() => {});
.catch(() => {});
miniAppReady(); miniAppReady();
} 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