fix: handle unmounted SDK components gracefully

This commit is contained in:
c0mrade
2026-02-01 20:23:45 +03:00
parent edb5be09ae
commit baa57b907e
2 changed files with 78 additions and 51 deletions

View File

@@ -75,23 +75,11 @@ export function initTelegramSDK() {
initCleanup = init(); initCleanup = init();
sdkInitialized = true; sdkInitialized = true;
// Mount viewport and bind CSS variables // Mount mini app and call ready (sync)
viewport
.mount()
.then(() => {
viewport.bindCssVars();
// Expand the mini app
viewport.expand();
})
.catch((err) => {
console.warn('Viewport mount failed:', err);
});
// Mount mini app and call ready
miniApp.mount(); miniApp.mount();
miniApp.ready(); miniApp.ready();
// Mount theme params and bind CSS variables // Mount theme params and bind CSS variables (sync)
try { try {
themeParams.mount(); themeParams.mount();
themeParams.bindCssVars(); themeParams.bindCssVars();
@@ -99,25 +87,38 @@ export function initTelegramSDK() {
// Theme params may already be mounted or not supported // Theme params may already be mounted or not supported
} }
// Disable vertical swipes if supported // Disable vertical swipes if supported (sync)
if (swipeBehavior.isSupported()) { if (swipeBehavior.isSupported()) {
swipeBehavior.mount(); swipeBehavior.mount();
swipeBehavior.disableVertical(); swipeBehavior.disableVertical();
} }
// Auto-enter fullscreen if enabled in settings (mobile only) // Mount viewport and bind CSS variables (async)
const fullscreenEnabled = getCachedFullscreenEnabled(); viewport
if (fullscreenEnabled && isTelegramMobile()) { .mount()
// Wait for viewport to be mounted .then(() => {
setTimeout(() => { viewport.bindCssVars();
const isFullscreen = viewport.isFullscreen(); // Expand the mini app
if (!isFullscreen) { viewport.expand();
viewport.requestFullscreen().catch((e) => {
console.warn('Auto-fullscreen failed:', e); // Auto-enter fullscreen if enabled in settings (mobile only)
}); const fullscreenEnabled = getCachedFullscreenEnabled();
if (fullscreenEnabled && isTelegramMobile()) {
try {
const isFullscreen = viewport.isFullscreen();
if (!isFullscreen) {
viewport.requestFullscreen().catch((e) => {
console.warn('Auto-fullscreen failed:', e);
});
}
} catch {
// Viewport signal not available
}
} }
}, 100); })
} .catch((err) => {
console.warn('Viewport mount failed:', err);
});
} catch (e) { } catch (e) {
console.warn('Telegram SDK initialization failed:', e); console.warn('Telegram SDK initialization failed:', e);
} }
@@ -153,6 +154,7 @@ const defaultInsets = { top: 0, bottom: 0, left: 0, right: 0 };
/** /**
* Helper to subscribe to SDK signals using useSyncExternalStore pattern * Helper to subscribe to SDK signals using useSyncExternalStore pattern
* Safely handles unmounted components by returning default values
*/ */
function useSDKSignal<T>( function useSDKSignal<T>(
signal: { (): T; sub(fn: VoidFunction): VoidFunction } | null, signal: { (): T; sub(fn: VoidFunction): VoidFunction } | null,
@@ -161,9 +163,22 @@ function useSDKSignal<T>(
return useSyncExternalStore( return useSyncExternalStore(
(callback) => { (callback) => {
if (!signal) return () => {}; if (!signal) return () => {};
return signal.sub(callback); try {
return signal.sub(callback);
} catch {
// Signal not available (component unmounted)
return () => {};
}
},
() => {
if (!signal) return defaultValue;
try {
return signal();
} catch {
// Signal not available (component unmounted)
return defaultValue;
}
}, },
() => (signal ? signal() : defaultValue),
() => defaultValue, () => defaultValue,
); );
} }

View File

@@ -65,8 +65,12 @@ function createBackButtonController(): BackButtonController {
return { return {
get isVisible() { get isVisible() {
if (!inTelegram) return false; if (!inTelegram || !mounted) return false;
return backButton.isVisible(); try {
return backButton.isVisible();
} catch {
return false;
}
}, },
show(onClick: () => void) { show(onClick: () => void) {
@@ -115,8 +119,12 @@ function createMainButtonController(): MainButtonController {
return { return {
get isVisible() { get isVisible() {
if (!inTelegram) return false; if (!inTelegram || !mounted) return false;
return mainButton.isVisible(); try {
return mainButton.isVisible();
} catch {
return false;
}
}, },
show(config: MainButtonConfig) { show(config: MainButtonConfig) {
@@ -301,24 +309,28 @@ function createThemeController(): ThemeController {
getThemeParams() { getThemeParams() {
if (!inTelegram) return null; if (!inTelegram) return null;
ensureMounted(); ensureMounted();
const state = themeParams.state(); try {
// Convert SDK format to our format const state = themeParams.state();
return { // Convert SDK format to our format
bg_color: state.bgColor, return {
text_color: state.textColor, bg_color: state.bgColor,
hint_color: state.hintColor, text_color: state.textColor,
link_color: state.linkColor, hint_color: state.hintColor,
button_color: state.buttonColor, link_color: state.linkColor,
button_text_color: state.buttonTextColor, button_color: state.buttonColor,
secondary_bg_color: state.secondaryBgColor, button_text_color: state.buttonTextColor,
header_bg_color: state.headerBgColor, secondary_bg_color: state.secondaryBgColor,
bottom_bar_bg_color: state.bottomBarBgColor, header_bg_color: state.headerBgColor,
accent_text_color: state.accentTextColor, bottom_bar_bg_color: state.bottomBarBgColor,
section_bg_color: state.sectionBgColor, accent_text_color: state.accentTextColor,
section_header_text_color: state.sectionHeaderTextColor, section_bg_color: state.sectionBgColor,
subtitle_text_color: state.subtitleTextColor, section_header_text_color: state.sectionHeaderTextColor,
destructive_text_color: state.destructiveTextColor, subtitle_text_color: state.subtitleTextColor,
}; destructive_text_color: state.destructiveTextColor,
};
} catch {
return null;
}
}, },
}; };
} }