refactor: migrate to @telegram-apps/sdk-react v3, remove all window.Telegram.WebApp usage

Полный переход с нативного window.Telegram.WebApp API на @telegram-apps/sdk-react v3.
Bridge SDK оборачивал receiveEvent, из-за чего popup_closed не доходил до нативного
обработчика — это было основной причиной бага с двойным открытием попапа.

Изменения:

- Удалён @telegram-apps/react-router-integration (привязан к SDK v1)
- Инициализация SDK v3 в main.tsx: init(), restoreInitData(), mount всех компонентов
- AppWithNavigator: убран TelegramRouter (initNavigator + useIntegration), все платформы
  на BrowserRouter, добавлен TelegramBackButton через SDK v3
- useTelegramSDK: детекция через retrieveLaunchParams(), реактивные значения через
  useSignal() (fullscreen, viewport, safe area insets), initData через retrieveRawInitData()
- TelegramAdapter: полная перезапись — попапы через showPopup() (Promise-based, без
  ручного onEvent/offEvent/timeout), кнопки через setMainButtonParams/showBackButton,
  haptic через hapticFeedbackImpactOccurred, тема через themeParamsState с конвертацией
  camelCase→snake_case, cloud storage через getCloudStorageItem/setCloudStorageItem
- api/client.ts: initData через retrieveRawInitData() вместо window.Telegram.WebApp.initData
- AppHeader: фото пользователя через initDataUser() сигнал
- ConnectionModal: openLink через SDK вместо window.Telegram.WebApp.openLink
- ColorPicker: детекция Telegram через isInTelegramWebApp()
- Удалён интерфейс TelegramWebApp (368 строк) и Window augmentation из vite-env.d.ts
- Убрано поле telegram из PlatformContext в types.ts
- Обновлён manualChunks в vite.config.ts
This commit is contained in:
c0mrade
2026-02-04 11:06:59 +03:00
parent 2d00a5c21f
commit 023c3ef6b3
17 changed files with 497 additions and 873 deletions

View File

@@ -1,4 +1,20 @@
import { useCallback, useMemo } from 'react';
import {
useSignal,
isFullscreen as isFullscreenSignal,
viewportHeight as viewportHeightSignal,
viewportStableHeight as viewportStableHeightSignal,
isViewportExpanded as isViewportExpandedSignal,
viewportSafeAreaInsets,
viewportContentSafeAreaInsets,
requestFullscreen as sdkRequestFullscreen,
exitFullscreen as sdkExitFullscreen,
disableVerticalSwipes as sdkDisableVerticalSwipes,
enableVerticalSwipes as sdkEnableVerticalSwipes,
expandViewport,
retrieveLaunchParams,
retrieveRawInitData,
} from '@telegram-apps/sdk-react';
const FULLSCREEN_CACHE_KEY = 'cabinet_fullscreen_enabled';
@@ -24,60 +40,47 @@ export const setCachedFullscreenEnabled = (enabled: boolean) => {
}
};
// Cached detection result (evaluated once at module load)
let _isInTelegram: boolean | null = null;
function detectTelegram(): boolean {
if (_isInTelegram === null) {
try {
retrieveLaunchParams();
_isInTelegram = true;
} catch {
_isInTelegram = false;
}
}
return _isInTelegram;
}
/**
* Check if we're actually running inside Telegram Mini App
*/
export function isInTelegramWebApp(): boolean {
const webApp = window.Telegram?.WebApp;
return Boolean(webApp?.initData && webApp.initData.length > 0);
return detectTelegram();
}
/**
* Check if running on mobile Telegram client (iOS/Android)
*/
export function isTelegramMobile(): boolean {
const webApp = window.Telegram?.WebApp;
if (!webApp?.platform) return false;
return webApp.platform === 'ios' || webApp.platform === 'android';
try {
const { tgWebAppPlatform } = retrieveLaunchParams();
return tgWebAppPlatform === 'ios' || tgWebAppPlatform === 'android';
} catch {
return false;
}
}
/**
* Get Telegram init data for authentication
*/
export function getTelegramInitData(): string | null {
const webApp = window.Telegram?.WebApp;
return webApp?.initData || null;
}
/**
* Initialize Telegram WebApp (basic setup without SDK)
*/
export function initTelegramSDK() {
if (!isInTelegramWebApp()) {
return;
}
const tg = window.Telegram?.WebApp;
if (!tg) return;
// Basic initialization
tg.ready();
tg.expand();
// Disable closing confirmation by default
tg.disableClosingConfirmation?.();
// Disable vertical swipes to prevent accidental closures
tg.disableVerticalSwipes?.();
// Auto-enter fullscreen if enabled in settings (mobile only)
const fullscreenEnabled = getCachedFullscreenEnabled();
if (fullscreenEnabled && isTelegramMobile() && tg.requestFullscreen) {
setTimeout(() => {
if (!tg.isFullscreen) {
tg.requestFullscreen?.();
}
}, 100);
try {
return retrieveRawInitData() || null;
} catch {
return null;
}
}
@@ -100,54 +103,72 @@ const defaultInsets = { top: 0, bottom: 0, left: 0, right: 0 };
/**
* Hook for Telegram WebApp integration
* Uses native window.Telegram.WebApp API
* Uses @telegram-apps/sdk-react v3 signals
*/
export function useTelegramSDK() {
const inTelegram = isInTelegramWebApp();
const tg = window.Telegram?.WebApp;
const inTelegram = detectTelegram();
const platform = useMemo<TelegramPlatform>(() => {
if (!inTelegram) return undefined;
return tg?.platform as TelegramPlatform;
}, [inTelegram, tg?.platform]);
try {
return retrieveLaunchParams().tgWebAppPlatform as TelegramPlatform;
} catch {
return undefined;
}
}, []);
const isMobile = platform === 'ios' || platform === 'android';
// Safe area insets from Telegram WebApp
// Always call useSignal unconditionally (Rules of Hooks).
// When not in Telegram, the signals will have their default values.
const fullscreenValue = useSignal(isFullscreenSignal);
const heightValue = useSignal(viewportHeightSignal);
const stableHeightValue = useSignal(viewportStableHeightSignal);
const expandedValue = useSignal(isViewportExpandedSignal);
const safeInsets = useSignal(viewportSafeAreaInsets);
const contentSafeInsets = useSignal(viewportContentSafeAreaInsets);
const isFullscreen = inTelegram ? (fullscreenValue ?? false) : false;
const viewportHeight = inTelegram ? (heightValue ?? 0) : 0;
const viewportStableHeight = inTelegram ? (stableHeightValue ?? 0) : 0;
const isExpanded = inTelegram ? (expandedValue ?? true) : true;
const safeAreaInset = useMemo(() => {
if (!inTelegram || !tg?.safeAreaInset) return defaultInsets;
if (!inTelegram || !safeInsets) return defaultInsets;
return {
top: tg.safeAreaInset.top || 0,
bottom: tg.safeAreaInset.bottom || 0,
left: tg.safeAreaInset.left || 0,
right: tg.safeAreaInset.right || 0,
top: safeInsets.top || 0,
bottom: safeInsets.bottom || 0,
left: safeInsets.left || 0,
right: safeInsets.right || 0,
};
}, [inTelegram, tg?.safeAreaInset]);
}, [inTelegram, safeInsets]);
const contentSafeAreaInset = useMemo(() => {
if (!inTelegram || !tg?.contentSafeAreaInset) return defaultInsets;
if (!inTelegram || !contentSafeInsets) return defaultInsets;
return {
top: tg.contentSafeAreaInset.top || 0,
bottom: tg.contentSafeAreaInset.bottom || 0,
left: tg.contentSafeAreaInset.left || 0,
right: tg.contentSafeAreaInset.right || 0,
top: contentSafeInsets.top || 0,
bottom: contentSafeInsets.bottom || 0,
left: contentSafeInsets.left || 0,
right: contentSafeInsets.right || 0,
};
}, [inTelegram, tg?.contentSafeAreaInset]);
const isFullscreen = tg?.isFullscreen ?? false;
const viewportHeight = tg?.viewportHeight ?? 0;
const viewportStableHeight = tg?.viewportStableHeight ?? 0;
const isExpanded = tg?.isExpanded ?? true;
}, [inTelegram, contentSafeInsets]);
const requestFullscreen = useCallback(() => {
if (!inTelegram || !tg?.requestFullscreen) return;
tg.requestFullscreen();
}, [inTelegram, tg]);
if (!inTelegram) return;
try {
sdkRequestFullscreen();
} catch {
// Not supported
}
}, [inTelegram]);
const exitFullscreen = useCallback(() => {
if (!inTelegram || !tg?.exitFullscreen) return;
tg.exitFullscreen();
}, [inTelegram, tg]);
if (!inTelegram) return;
try {
sdkExitFullscreen();
} catch {
// Not supported
}
}, [inTelegram]);
const toggleFullscreen = useCallback(() => {
if (isFullscreen) {
@@ -158,21 +179,33 @@ export function useTelegramSDK() {
}, [isFullscreen, requestFullscreen, exitFullscreen]);
const expand = useCallback(() => {
if (!inTelegram || !tg?.expand) return;
tg.expand();
}, [inTelegram, tg]);
if (!inTelegram) return;
try {
expandViewport();
} catch {
// Not supported
}
}, [inTelegram]);
const disableVerticalSwipes = useCallback(() => {
if (!inTelegram || !tg?.disableVerticalSwipes) return;
tg.disableVerticalSwipes();
}, [inTelegram, tg]);
if (!inTelegram) return;
try {
sdkDisableVerticalSwipes();
} catch {
// Not supported
}
}, [inTelegram]);
const enableVerticalSwipes = useCallback(() => {
if (!inTelegram || !tg?.enableVerticalSwipes) return;
tg.enableVerticalSwipes();
}, [inTelegram, tg]);
if (!inTelegram) return;
try {
sdkEnableVerticalSwipes();
} catch {
// Not supported
}
}, [inTelegram]);
const isFullscreenSupported = inTelegram && typeof tg?.requestFullscreen === 'function';
const isFullscreenSupported = inTelegram;
return {
isTelegramWebApp: inTelegram,
@@ -182,7 +215,7 @@ export function useTelegramSDK() {
contentSafeAreaInset,
viewportHeight,
viewportStableHeight,
viewportWidth: 0, // Not available in native API
viewportWidth: 0,
isExpanded,
platform,
isMobile,

View File

@@ -11,7 +11,6 @@ export {
setCachedFullscreenEnabled,
isInTelegramWebApp,
isTelegramMobile,
initTelegramSDK as initTelegramWebApp, // Alias for backward compatibility
} from './useTelegramSDK';
/**
@@ -32,7 +31,6 @@ export function useTelegramWebApp() {
toggleFullscreen: sdk.toggleFullscreen,
disableVerticalSwipes: sdk.disableVerticalSwipes,
enableVerticalSwipes: sdk.enableVerticalSwipes,
// For backward compatibility, expose webApp as the raw Telegram object
webApp: window.Telegram?.WebApp ?? null,
webApp: null,
};
}