diff --git a/package-lock.json b/package-lock.json
index 0b676a4..113a599 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -24,7 +24,6 @@
"@radix-ui/react-tooltip": "^1.2.8",
"@radix-ui/react-visually-hidden": "^1.2.4",
"@tanstack/react-query": "^5.8.0",
- "@telegram-apps/react-router-integration": "^1.0.1",
"@telegram-apps/sdk-react": "^3.3.9",
"axios": "^1.6.0",
"class-variance-authority": "^0.7.1",
@@ -2640,30 +2639,6 @@
"integrity": "sha512-bqNgF/J8Po7ZtsELm3E1a6aPr7awwxO3sIqD8J6u12urOlGoW5+1KxKKbkPa58mgXuQdsltd8apI+OVy0IYiUA==",
"license": "MIT"
},
- "node_modules/@telegram-apps/react-router-integration": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/@telegram-apps/react-router-integration/-/react-router-integration-1.0.1.tgz",
- "integrity": "sha512-q5fC0ihXyUDqrSdLQ3u3gypmjI/rMU2mTHeAz9IU1F1bl5X+J17LxWmgLp+OlD0Y97bq2XYmc+UbKrYh+eN3Wg==",
- "license": "MIT",
- "peerDependencies": {
- "@telegram-apps/sdk": "^1",
- "@types/react": "^17.0.0 || ^18.0.0",
- "react": "^17.0.0 || ^18.0.0",
- "react-router-dom": "^6.22.3"
- },
- "peerDependenciesMeta": {
- "@types/react": {
- "optional": true
- }
- }
- },
- "node_modules/@telegram-apps/sdk": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/@telegram-apps/sdk/-/sdk-1.1.3.tgz",
- "integrity": "sha512-gvcXNttbCouDNTxjDUbO5FS/BdaHYO0ibSddQxiYitShPIDZDxGon0Eh3OSsgUIVPSQSNdXqkXaeqYgA+jeY0A==",
- "license": "MIT",
- "peer": true
- },
"node_modules/@telegram-apps/sdk-react": {
"version": "3.3.9",
"resolved": "https://registry.npmjs.org/@telegram-apps/sdk-react/-/sdk-react-3.3.9.tgz",
diff --git a/package.json b/package.json
index 9a92f4e..b10e970 100644
--- a/package.json
+++ b/package.json
@@ -32,7 +32,6 @@
"@radix-ui/react-tooltip": "^1.2.8",
"@radix-ui/react-visually-hidden": "^1.2.4",
"@tanstack/react-query": "^5.8.0",
- "@telegram-apps/react-router-integration": "^1.0.1",
"@telegram-apps/sdk-react": "^3.3.9",
"axios": "^1.6.0",
"class-variance-authority": "^0.7.1",
diff --git a/src/AppWithNavigator.tsx b/src/AppWithNavigator.tsx
index fb05276..11ae689 100644
--- a/src/AppWithNavigator.tsx
+++ b/src/AppWithNavigator.tsx
@@ -1,102 +1,76 @@
-import { useEffect, useMemo } from 'react';
-import { BrowserRouter, Router } from 'react-router-dom';
-import { useIntegration } from '@telegram-apps/react-router-integration';
-import { initNavigator } from '@telegram-apps/sdk';
+import { useEffect } from 'react';
+import { BrowserRouter, useLocation, useNavigate } from 'react-router-dom';
+import {
+ showBackButton,
+ hideBackButton,
+ onBackButtonClick,
+ offBackButtonClick,
+} from '@telegram-apps/sdk-react';
import App from './App';
import { PlatformProvider } from './platform/PlatformProvider';
import { ThemeColorsProvider } from './providers/ThemeColorsProvider';
import { WebSocketProvider } from './providers/WebSocketProvider';
import { ToastProvider } from './components/Toast';
import { TooltipProvider } from './components/primitives/Tooltip';
+import { isInTelegramWebApp } from './hooks/useTelegramSDK';
/**
- * Check if running inside Telegram Mini App
- * Uses multiple checks to reliably detect Telegram environment
+ * Manages Telegram BackButton visibility based on navigation location.
+ * Shows back button on non-root routes, hides on root.
*/
-function isTelegramMiniApp(): boolean {
- if (typeof window === 'undefined') return false;
-
- const webApp = window.Telegram?.WebApp;
- if (!webApp) return false;
-
- // Check 1: initDataUnsafe should have user data in real Telegram
- const hasUserData = webApp.initDataUnsafe?.user?.id !== undefined;
-
- // Check 2: Platform should not be 'unknown' (which is default in browser)
- const validPlatform = webApp.platform !== 'unknown' && webApp.platform !== '';
-
- // Check 3: Version should be present (SDK loads in Telegram only)
- const hasVersion = webApp.version !== undefined && webApp.version !== '';
-
- return hasUserData || (validPlatform && hasVersion);
-}
-
-/**
- * Component wrapper for Telegram navigator setup.
- * Integrates Telegram Mini Apps navigator with React Router to provide
- * automatic BackButton management based on navigation history.
- * Falls back to BrowserRouter when not in Telegram Mini App.
- */
-/**
- * Navigator-based router for Telegram Mini App
- */
-function TelegramRouter({ children }: { children: React.ReactNode }) {
- const navigator = useMemo(() => initNavigator('app-navigation-state', { hashMode: null }), []);
- const [location, reactNavigator] = useIntegration(navigator);
+function TelegramBackButton() {
+ const location = useLocation();
+ const navigate = useNavigate();
useEffect(() => {
+ const isRoot = location.pathname === '/' || location.pathname === '';
try {
- navigator.attach();
- return () => {
- try {
- navigator.detach();
- } catch (err) {
- console.warn('Failed to detach navigator:', err);
- }
- };
- } catch (err) {
- console.warn('Failed to attach navigator:', err);
+ if (isRoot) {
+ hideBackButton();
+ } else {
+ showBackButton();
+ }
+ } catch {
+ // Not in Telegram or back button not mounted
}
- }, [navigator]);
+ }, [location]);
- return (
-
- {children}
-
- );
+ useEffect(() => {
+ const handler = () => navigate(-1);
+ try {
+ onBackButtonClick(handler);
+ } catch {
+ // Not in Telegram
+ }
+ return () => {
+ try {
+ offBackButtonClick(handler);
+ } catch {
+ // Not in Telegram
+ }
+ };
+ }, [navigate]);
+
+ return null;
}
export function AppWithNavigator() {
- const isTelegram = useMemo(() => {
- const result = isTelegramMiniApp();
- console.log('[AppWithNavigator] Platform detection:', {
- isTelegram: result,
- platform: window.Telegram?.WebApp?.platform,
- hasUser: window.Telegram?.WebApp?.initDataUnsafe?.user?.id !== undefined,
- version: window.Telegram?.WebApp?.version,
- });
- return result;
- }, []);
+ const isTelegram = isInTelegramWebApp();
- // Common app content
- const appContent = (
-
-
-
-
-
-
-
-
-
-
-
+ return (
+
+ {isTelegram && }
+
+
+
+
+
+
+
+
+
+
+
+
);
-
- // Use Telegram navigator in Mini App, BrowserRouter elsewhere
- if (isTelegram) {
- return {appContent};
- }
-
- return {appContent};
}
diff --git a/src/api/client.ts b/src/api/client.ts
index 40775ac..49d7b0a 100644
--- a/src/api/client.ts
+++ b/src/api/client.ts
@@ -1,4 +1,5 @@
import axios, { AxiosError, InternalAxiosRequestConfig } from 'axios';
+import { retrieveRawInitData } from '@telegram-apps/sdk-react';
import {
tokenStorage,
isTokenExpired,
@@ -41,10 +42,14 @@ function ensureCsrfToken(): string {
const getTelegramInitData = (): string | null => {
if (typeof window === 'undefined') return null;
- const initData = window.Telegram?.WebApp?.initData;
- if (initData) {
- tokenStorage.setTelegramInitData(initData);
- return initData;
+ try {
+ const raw = retrieveRawInitData();
+ if (raw) {
+ tokenStorage.setTelegramInitData(raw);
+ return raw;
+ }
+ } catch {
+ // Not in Telegram or SDK not initialized
}
return tokenStorage.getTelegramInitData();
diff --git a/src/components/ColorPicker.tsx b/src/components/ColorPicker.tsx
index 69de3e5..080e96e 100644
--- a/src/components/ColorPicker.tsx
+++ b/src/components/ColorPicker.tsx
@@ -1,5 +1,6 @@
import { useState, useRef, useEffect, useMemo, useCallback } from 'react';
import { createPortal } from 'react-dom';
+import { isInTelegramWebApp } from '@/hooks/useTelegramSDK';
interface ColorPickerProps {
value: string;
@@ -9,11 +10,6 @@ interface ColorPickerProps {
disabled?: boolean;
}
-// Check if running in Telegram WebApp
-const isTelegramWebApp = (): boolean => {
- return !!(window as unknown as { Telegram?: { WebApp?: unknown } }).Telegram?.WebApp;
-};
-
// Convert hex to RGB
const hexToRgb = (hex: string): { r: number; g: number; b: number } => {
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
@@ -117,7 +113,7 @@ export function ColorPicker({ value, onChange, label, description, disabled }: C
const pickerRef = useRef(null);
const colorInputRef = useRef(null);
- const isTelegram = useMemo(() => isTelegramWebApp(), []);
+ const isTelegram = useMemo(() => isInTelegramWebApp(), []);
// Sync with external value
useEffect(() => {
diff --git a/src/components/ConnectionModal.tsx b/src/components/ConnectionModal.tsx
index 57254a1..7818ce3 100644
--- a/src/components/ConnectionModal.tsx
+++ b/src/components/ConnectionModal.tsx
@@ -2,6 +2,7 @@ import { useState, useMemo, useEffect, useCallback, useRef } from 'react';
import { createPortal } from 'react-dom';
import { useTranslation } from 'react-i18next';
import { useQuery } from '@tanstack/react-query';
+import { openLink as sdkOpenLink } from '@telegram-apps/sdk-react';
import { subscriptionApi } from '../api/subscription';
import { useTelegramWebApp } from '../hooks/useTelegramWebApp';
import { useBackButton, useHaptic } from '@/platform';
@@ -300,19 +301,11 @@ export default function ConnectionModal({ onClose }: ConnectionModalProps) {
// actual deep link URL. This works for both http(s) and custom schemes.
const redirectUrl = `${window.location.origin}/miniapp/redirect.html?url=${encodeURIComponent(deepLink)}&lang=${i18n.language || 'en'}`;
- const tg = (
- window as unknown as {
- Telegram?: { WebApp?: { openLink?: (url: string, options?: object) => void } };
- }
- ).Telegram?.WebApp;
-
- if (tg?.openLink) {
- try {
- tg.openLink(redirectUrl, { try_instant_view: false, try_browser: true });
- return;
- } catch {
- /* fallback */
- }
+ try {
+ sdkOpenLink(redirectUrl, { tryInstantView: false });
+ return;
+ } catch {
+ // SDK not available, fallback
}
window.location.href = redirectUrl;
};
diff --git a/src/components/layout/AppShell/AppHeader.tsx b/src/components/layout/AppShell/AppHeader.tsx
index 002b32d..68fc13d 100644
--- a/src/components/layout/AppShell/AppHeader.tsx
+++ b/src/components/layout/AppShell/AppHeader.tsx
@@ -2,6 +2,7 @@ import { Link, useLocation } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import { useQuery } from '@tanstack/react-query';
import { useState, useEffect } from 'react';
+import { initDataUser } from '@telegram-apps/sdk-react';
import { useAuthStore } from '@/store/auth';
import { useTheme } from '@/hooks/useTheme';
@@ -113,17 +114,12 @@ export function AppHeader({
// Get user photo from Telegram
useEffect(() => {
try {
- const tg = (
- window as unknown as {
- Telegram?: { WebApp?: { initDataUnsafe?: { user?: { photo_url?: string } } } };
- }
- ).Telegram?.WebApp;
- const photoUrl = tg?.initDataUnsafe?.user?.photo_url;
- if (photoUrl) {
- setUserPhotoUrl(photoUrl);
+ const user = initDataUser();
+ if (user?.photo_url) {
+ setUserPhotoUrl(user.photo_url);
}
- } catch (e) {
- console.warn('Failed to get Telegram user photo:', e);
+ } catch {
+ // Not in Telegram or init data not available
}
}, []);
diff --git a/src/hooks/useTelegramSDK.ts b/src/hooks/useTelegramSDK.ts
index 73cdcbe..162dc54 100644
--- a/src/hooks/useTelegramSDK.ts
+++ b/src/hooks/useTelegramSDK.ts
@@ -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(() => {
- 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,
diff --git a/src/hooks/useTelegramWebApp.ts b/src/hooks/useTelegramWebApp.ts
index 0178b89..7fdf0be 100644
--- a/src/hooks/useTelegramWebApp.ts
+++ b/src/hooks/useTelegramWebApp.ts
@@ -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,
};
}
diff --git a/src/main.tsx b/src/main.tsx
index ba8673c..d4ee76d 100644
--- a/src/main.tsx
+++ b/src/main.tsx
@@ -1,14 +1,66 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
+import {
+ init,
+ restoreInitData,
+ mountMiniApp,
+ miniAppReady,
+ mountThemeParams,
+ mountViewport,
+ expandViewport,
+ mountSwipeBehavior,
+ disableVerticalSwipes,
+ mountClosingBehavior,
+ disableClosingConfirmation,
+ mountBackButton,
+ mountMainButton,
+ bindThemeParamsCssVars,
+ bindViewportCssVars,
+} from '@telegram-apps/sdk-react';
import { AppWithNavigator } from './AppWithNavigator';
import { initLogoPreload } from './api/branding';
-import { initTelegramSDK } from './hooks/useTelegramSDK';
+import { getCachedFullscreenEnabled, isTelegramMobile } from './hooks/useTelegramSDK';
import './i18n';
import './styles/globals.css';
-// Initialize Telegram SDK (init, viewport mount, CSS vars binding, swipe control)
-initTelegramSDK();
+// Initialize Telegram SDK v3
+try {
+ init();
+ restoreInitData();
+
+ mountMiniApp();
+ mountThemeParams();
+ bindThemeParamsCssVars();
+ mountSwipeBehavior();
+ disableVerticalSwipes();
+ mountClosingBehavior();
+ disableClosingConfirmation();
+ mountBackButton();
+ mountMainButton();
+
+ mountViewport()
+ .then(() => {
+ bindViewportCssVars();
+ expandViewport();
+ })
+ .catch(() => {});
+
+ miniAppReady();
+
+ // Auto-enter fullscreen if enabled in settings (mobile only)
+ if (getCachedFullscreenEnabled() && isTelegramMobile()) {
+ import('@telegram-apps/sdk-react').then(({ requestFullscreen, isFullscreen }) => {
+ setTimeout(() => {
+ if (!isFullscreen()) {
+ requestFullscreen();
+ }
+ }, 100);
+ });
+ }
+} catch {
+ // Not in Telegram — ok
+}
// Preload logo from cache immediately on page load
initLogoPreload();
diff --git a/src/pages/Login.tsx b/src/pages/Login.tsx
index d1231d7..6c8aada 100644
--- a/src/pages/Login.tsx
+++ b/src/pages/Login.tsx
@@ -115,7 +115,7 @@ export default function Login() {
const initData = getTelegramInitData();
if (isInTelegramWebApp() && initData) {
setIsTelegramWebApp(true);
- // Note: ready() and expand() are already called by initTelegramSDK in main.tsx
+ // Note: ready() and expand() are already called by SDK init in main.tsx
setIsLoading(true);
try {
await loginWithTelegram(initData);
diff --git a/src/pages/TelegramRedirect.tsx b/src/pages/TelegramRedirect.tsx
index 1342519..468e25e 100644
--- a/src/pages/TelegramRedirect.tsx
+++ b/src/pages/TelegramRedirect.tsx
@@ -74,7 +74,7 @@ export default function TelegramRedirect() {
return;
}
- // Note: ready(), expand(), and theme CSS vars are already handled by initTelegramSDK in main.tsx
+ // Note: ready(), expand(), and theme CSS vars are already handled by SDK init in main.tsx
try {
await loginWithTelegram(initData);
diff --git a/src/platform/adapters/TelegramAdapter.ts b/src/platform/adapters/TelegramAdapter.ts
index e6be1b7..07c19f2 100644
--- a/src/platform/adapters/TelegramAdapter.ts
+++ b/src/platform/adapters/TelegramAdapter.ts
@@ -1,4 +1,31 @@
import { isInTelegramWebApp } from '@/hooks/useTelegramSDK';
+import {
+ showBackButton,
+ hideBackButton,
+ onBackButtonClick,
+ offBackButtonClick,
+ isBackButtonVisible,
+ setMainButtonParams,
+ onMainButtonClick,
+ offMainButtonClick,
+ hapticFeedbackImpactOccurred,
+ hapticFeedbackNotificationOccurred,
+ hapticFeedbackSelectionChanged,
+ showPopup,
+ setMiniAppHeaderColor,
+ setMiniAppBottomBarColor,
+ themeParamsState,
+ getCloudStorageItem,
+ setCloudStorageItem,
+ deleteCloudStorageItem,
+ getCloudStorageKeys,
+ openInvoice,
+ openLink,
+ openTelegramLink,
+ shareURL,
+ enableClosingConfirmation,
+ disableClosingConfirmation,
+} from '@telegram-apps/sdk-react';
import type {
PlatformContext,
PlatformCapabilities,
@@ -15,383 +42,342 @@ import type {
HapticNotificationType,
} from '@/platform/types';
-// Use raw Telegram WebApp API directly - SDK has initialization issues
-function getTelegram(): TelegramWebApp | null {
- return window.Telegram?.WebApp ?? null;
-}
-
function createCapabilities(): PlatformCapabilities {
- const tg = getTelegram();
const inTelegram = isInTelegramWebApp();
return {
- hasBackButton: inTelegram && !!tg?.BackButton,
- hasMainButton: inTelegram && !!tg?.MainButton,
- hasHapticFeedback: inTelegram && !!tg?.HapticFeedback,
- hasNativeDialogs: inTelegram && !!tg?.showPopup,
+ hasBackButton: inTelegram,
+ hasMainButton: inTelegram,
+ hasHapticFeedback: inTelegram,
+ hasNativeDialogs: inTelegram,
hasThemeSync: inTelegram,
- hasInvoice: !!tg?.openInvoice,
- hasCloudStorage: inTelegram && !!tg?.CloudStorage,
+ hasInvoice: inTelegram,
+ hasCloudStorage: inTelegram,
hasShare: true,
- version: tg?.version,
+ version: undefined,
};
}
function createBackButtonController(): BackButtonController {
- const tg = getTelegram();
const inTelegram = isInTelegramWebApp();
let currentCallback: (() => void) | null = null;
return {
get isVisible() {
- if (!inTelegram || !tg?.BackButton) return false;
- return tg.BackButton.isVisible;
+ if (!inTelegram) return false;
+ try {
+ return isBackButtonVisible();
+ } catch {
+ return false;
+ }
},
show(onClick: () => void) {
- if (!inTelegram || !tg?.BackButton) return;
+ if (!inTelegram) return;
- // Remove previous callback if exists
if (currentCallback) {
- tg.BackButton.offClick(currentCallback);
+ try {
+ offBackButtonClick(currentCallback);
+ } catch {
+ // ignore
+ }
}
currentCallback = onClick;
- tg.BackButton.onClick(onClick);
- tg.BackButton.show();
+ try {
+ onBackButtonClick(onClick);
+ showBackButton();
+ } catch {
+ // Back button not mounted
+ }
},
hide() {
- if (!inTelegram || !tg?.BackButton) return;
+ if (!inTelegram) return;
if (currentCallback) {
- tg.BackButton.offClick(currentCallback);
+ try {
+ offBackButtonClick(currentCallback);
+ } catch {
+ // ignore
+ }
currentCallback = null;
}
- tg.BackButton.hide();
+ try {
+ hideBackButton();
+ } catch {
+ // Back button not mounted
+ }
},
};
}
function createMainButtonController(): MainButtonController {
- const tg = getTelegram();
const inTelegram = isInTelegramWebApp();
let currentCallback: (() => void) | null = null;
return {
get isVisible() {
- if (!inTelegram || !tg?.MainButton) return false;
- return tg.MainButton.isVisible;
+ return false; // SDK v3 doesn't expose this as a simple getter
},
show(config: MainButtonConfig) {
- if (!inTelegram || !tg?.MainButton) return;
+ if (!inTelegram) return;
- // Remove previous callback if exists
if (currentCallback) {
- tg.MainButton.offClick(currentCallback);
- }
-
- // Set button parameters
- tg.MainButton.setText(config.text);
- if (config.color) {
- tg.MainButton.color = config.color;
- }
- if (config.textColor) {
- tg.MainButton.textColor = config.textColor;
- }
-
- if (config.isActive === false) {
- tg.MainButton.disable();
- } else {
- tg.MainButton.enable();
- }
-
- if (config.isLoading) {
- tg.MainButton.showProgress();
- } else {
- tg.MainButton.hideProgress();
+ try {
+ offMainButtonClick(currentCallback);
+ } catch {
+ // ignore
+ }
}
currentCallback = config.onClick;
- tg.MainButton.onClick(config.onClick);
- tg.MainButton.show();
+
+ try {
+ setMainButtonParams({
+ text: config.text,
+ isVisible: true,
+ isEnabled: config.isActive !== false,
+ isLoaderVisible: config.isLoading ?? false,
+ backgroundColor: config.color as `#${string}` | undefined,
+ textColor: config.textColor as `#${string}` | undefined,
+ });
+ onMainButtonClick(config.onClick);
+ } catch {
+ // Main button not mounted
+ }
},
hide() {
- if (!inTelegram || !tg?.MainButton) return;
+ if (!inTelegram) return;
if (currentCallback) {
- tg.MainButton.offClick(currentCallback);
+ try {
+ offMainButtonClick(currentCallback);
+ } catch {
+ // ignore
+ }
currentCallback = null;
}
- tg.MainButton.hideProgress();
- tg.MainButton.hide();
+ try {
+ setMainButtonParams({ isVisible: false, isLoaderVisible: false });
+ } catch {
+ // Main button not mounted
+ }
},
showProgress(show: boolean) {
- if (!inTelegram || !tg?.MainButton) return;
-
- if (show) {
- tg.MainButton.showProgress();
- } else {
- tg.MainButton.hideProgress();
+ if (!inTelegram) return;
+ try {
+ setMainButtonParams({ isLoaderVisible: show });
+ } catch {
+ // Main button not mounted
}
},
setText(text: string) {
- if (!inTelegram || !tg?.MainButton) return;
- tg.MainButton.setText(text);
+ if (!inTelegram) return;
+ try {
+ setMainButtonParams({ text });
+ } catch {
+ // Main button not mounted
+ }
},
setActive(active: boolean) {
- if (!inTelegram || !tg?.MainButton) return;
-
- if (active) {
- tg.MainButton.enable();
- } else {
- tg.MainButton.disable();
+ if (!inTelegram) return;
+ try {
+ setMainButtonParams({ isEnabled: active });
+ } catch {
+ // Main button not mounted
}
},
};
}
function createHapticController(): HapticController {
- const tg = getTelegram();
const inTelegram = isInTelegramWebApp();
- const haptic = tg?.HapticFeedback;
return {
impact(style: HapticImpactStyle = 'medium') {
- if (!inTelegram || !haptic) return;
- haptic.impactOccurred(style);
+ if (!inTelegram) return;
+ try {
+ hapticFeedbackImpactOccurred(style);
+ } catch {
+ // Haptic not available
+ }
},
notification(type: HapticNotificationType) {
- if (!inTelegram || !haptic) return;
- haptic.notificationOccurred(type);
+ if (!inTelegram) return;
+ try {
+ hapticFeedbackNotificationOccurred(type);
+ } catch {
+ // Haptic not available
+ }
},
selection() {
- if (!inTelegram || !haptic) return;
- haptic.selectionChanged();
+ if (!inTelegram) return;
+ try {
+ hapticFeedbackSelectionChanged();
+ } catch {
+ // Haptic not available
+ }
},
};
}
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,
- fallback: () => T,
- ): Promise {
- const tg = getTelegram();
-
- if (!inTelegram || !tg?.showPopup) {
- return Promise.resolve(fallback());
- }
-
- if (popupOpen) {
- return Promise.resolve(mapResult(''));
- }
-
- return new Promise((resolve) => {
- popupOpen = true;
- let settled = false;
-
- const cleanup = () => {
- if (settled) return;
- settled = true;
- popupOpen = false;
- clearTimeout(timer);
- tg.offEvent?.('popupClosed', onPopupClosed);
- };
-
- const onPopupClosed = ((...args: unknown[]) => {
- if (settled) return;
- const event = args[0] as { button_id?: string } | undefined;
- const buttonId = event?.button_id ?? '';
- cleanup();
- resolve(mapResult(buttonId));
- }) as (...args: unknown[]) => void;
-
- const timer = setTimeout(() => {
- if (settled) return;
- cleanup();
- resolve(mapResult(''));
- }, 30_000);
-
- tg.onEvent?.('popupClosed', onPopupClosed);
-
- try {
- tg.showPopup(params);
- } catch (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());
- }
- });
- }
return {
- alert(message: string, _title?: string): Promise {
- return showPopupSafe(
- { message, buttons: [{ type: 'ok' }] },
- () => undefined,
- () => {
- window.alert(message);
- },
- );
+ async alert(message: string, _title?: string): Promise {
+ if (!inTelegram) {
+ window.alert(message);
+ return;
+ }
+ try {
+ await showPopup({
+ message,
+ buttons: [{ type: 'ok', id: 'ok' }],
+ });
+ } catch {
+ window.alert(message);
+ }
},
- confirm(message: string, _title?: string): Promise {
- return showPopupSafe(
- {
+ async confirm(message: string, _title?: string): Promise {
+ if (!inTelegram) {
+ return window.confirm(message);
+ }
+ try {
+ const buttonId = await showPopup({
message,
buttons: [
- { id: 'ok', type: 'ok' },
- { id: 'cancel', type: 'cancel' },
+ { type: 'ok', id: 'ok' },
+ { type: 'cancel', id: 'cancel' },
],
- },
- (buttonId) => buttonId === 'ok',
- () => window.confirm(message),
- );
+ });
+ return buttonId === 'ok';
+ } catch {
+ return window.confirm(message);
+ }
},
- popup(options: PopupOptions): Promise {
- return showPopupSafe(
- {
+ async popup(options: PopupOptions): Promise {
+ if (!inTelegram) {
+ return window.confirm(options.message) ? 'ok' : null;
+ }
+ try {
+ const buttons = options.buttons?.map((btn) => {
+ // For 'ok', 'close', 'cancel' types: do NOT include text
+ // For 'default', 'destructive' types: text is required
+ if (btn.type === 'ok' || btn.type === 'close' || btn.type === 'cancel') {
+ return { type: btn.type, id: btn.id };
+ }
+ return { type: btn.type ?? ('default' as const), id: btn.id, text: btn.text };
+ });
+
+ const buttonId = await showPopup({
title: options.title,
message: options.message,
- buttons: options.buttons?.map((btn) => ({
- id: btn.id,
- type: btn.type,
- text: btn.text,
- })),
- },
- (buttonId) => buttonId || null,
- () => (window.confirm(options.message) ? 'ok' : null),
- );
+ buttons: buttons as Parameters[0]['buttons'],
+ });
+ return buttonId || null;
+ } catch {
+ return window.confirm(options.message) ? 'ok' : null;
+ }
},
};
}
function createThemeController(): ThemeController {
- const tg = getTelegram();
const inTelegram = isInTelegramWebApp();
return {
setHeaderColor(color: string) {
- if (!inTelegram || !tg?.setHeaderColor) return;
- tg.setHeaderColor(color as `#${string}`);
+ if (!inTelegram) return;
+ try {
+ setMiniAppHeaderColor(color as `#${string}`);
+ } catch {
+ // Not supported
+ }
},
setBottomBarColor(color: string) {
- if (!inTelegram || !tg?.setBottomBarColor) return;
+ if (!inTelegram) return;
try {
- tg.setBottomBarColor(color as `#${string}`);
+ setMiniAppBottomBarColor(color as `#${string}`);
} catch {
- // Not supported in this version
+ // Not supported
}
},
getThemeParams() {
- if (!inTelegram || !tg?.themeParams) return null;
- const params = tg.themeParams;
- return {
- bg_color: params.bg_color,
- text_color: params.text_color,
- hint_color: params.hint_color,
- link_color: params.link_color,
- button_color: params.button_color,
- button_text_color: params.button_text_color,
- secondary_bg_color: params.secondary_bg_color,
- header_bg_color: params.header_bg_color,
- bottom_bar_bg_color: params.bottom_bar_bg_color,
- accent_text_color: params.accent_text_color,
- section_bg_color: params.section_bg_color,
- section_header_text_color: params.section_header_text_color,
- subtitle_text_color: params.subtitle_text_color,
- destructive_text_color: params.destructive_text_color,
- };
+ if (!inTelegram) return null;
+ try {
+ const params = themeParamsState();
+ if (!params) return null;
+ // SDK v3 uses camelCase — convert to snake_case for our interface
+ return {
+ bg_color: params.bgColor,
+ text_color: params.textColor,
+ hint_color: params.hintColor,
+ link_color: params.linkColor,
+ button_color: params.buttonColor,
+ button_text_color: params.buttonTextColor,
+ secondary_bg_color: params.secondaryBgColor,
+ header_bg_color: params.headerBgColor,
+ bottom_bar_bg_color: params.bottomBarBgColor,
+ accent_text_color: params.accentTextColor,
+ section_bg_color: params.sectionBgColor,
+ section_header_text_color: params.sectionHeaderTextColor,
+ subtitle_text_color: params.subtitleTextColor,
+ destructive_text_color: params.destructiveTextColor,
+ };
+ } catch {
+ return null;
+ }
},
};
}
function createCloudStorageController(): CloudStorageController | null {
- const tg = getTelegram();
const inTelegram = isInTelegramWebApp();
- const storage = tg?.CloudStorage;
-
- if (!inTelegram || !storage) return null;
+ if (!inTelegram) return null;
return {
async getItem(key: string): Promise {
- return new Promise((resolve) => {
- storage.getItem(key, (error, value) => {
- resolve(error ? null : value || null);
- });
- });
+ try {
+ const value = await getCloudStorageItem(key);
+ return value === '' ? null : value;
+ } catch {
+ return null;
+ }
},
async setItem(key: string, value: string): Promise {
- return new Promise((resolve, reject) => {
- storage.setItem(key, value, (error) => {
- if (error) reject(new Error(String(error)));
- else resolve();
- });
- });
+ await setCloudStorageItem(key, value);
},
async removeItem(key: string): Promise {
- return new Promise((resolve, reject) => {
- storage.removeItem(key, (error) => {
- if (error) reject(new Error(String(error)));
- else resolve();
- });
- });
+ await deleteCloudStorageItem(key);
},
async getKeys(): Promise {
- return new Promise((resolve) => {
- storage.getKeys((error, keys) => {
- resolve(error ? [] : keys || []);
- });
- });
+ try {
+ return await getCloudStorageKeys();
+ } catch {
+ return [];
+ }
},
};
}
export function createTelegramAdapter(): PlatformContext {
- const tg = getTelegram();
-
return {
platform: 'telegram',
capabilities: createCapabilities(),
@@ -403,28 +389,26 @@ export function createTelegramAdapter(): PlatformContext {
cloudStorage: createCloudStorageController(),
openInvoice(url: string): Promise {
- return new Promise((resolve) => {
- if (tg?.openInvoice) {
- tg.openInvoice(url, (status) => resolve(status));
- } else {
- window.open(url, '_blank');
- resolve('pending');
- }
- });
+ try {
+ return openInvoice(url, 'url') as Promise;
+ } catch {
+ window.open(url, '_blank');
+ return Promise.resolve('pending');
+ }
},
openLink(url: string, options?: { tryInstantView?: boolean }) {
- if (tg?.openLink) {
- tg.openLink(url, { try_instant_view: options?.tryInstantView });
- } else {
+ try {
+ openLink(url, { tryInstantView: options?.tryInstantView });
+ } catch {
window.open(url, '_blank');
}
},
openTelegramLink(url: string) {
- if (tg?.openTelegramLink) {
- tg.openTelegramLink(url);
- } else {
+ try {
+ openTelegramLink(url);
+ } catch {
window.open(url, '_blank');
}
},
@@ -441,24 +425,24 @@ export function createTelegramAdapter(): PlatformContext {
}
}
- const botUsername = import.meta.env.VITE_TELEGRAM_BOT_USERNAME;
- if (botUsername && tg?.openTelegramLink) {
- const encoded = encodeURIComponent(shareText);
- tg.openTelegramLink(`https://t.me/share/url?url=${encoded}`);
+ try {
+ shareURL(url || shareText, text);
return true;
+ } catch {
+ return false;
}
-
- return false;
},
setClosingConfirmation(enabled: boolean) {
- if (enabled) {
- tg?.enableClosingConfirmation?.();
- } else {
- tg?.disableClosingConfirmation?.();
+ try {
+ if (enabled) {
+ enableClosingConfirmation();
+ } else {
+ disableClosingConfirmation();
+ }
+ } catch {
+ // Not supported
}
},
-
- telegram: tg,
};
}
diff --git a/src/platform/adapters/WebAdapter.ts b/src/platform/adapters/WebAdapter.ts
index 68c6e37..8f52b77 100644
--- a/src/platform/adapters/WebAdapter.ts
+++ b/src/platform/adapters/WebAdapter.ts
@@ -271,7 +271,5 @@ export function createWebAdapter(): PlatformContext {
window.onbeforeunload = null;
}
},
-
- telegram: null,
};
}
diff --git a/src/platform/types.ts b/src/platform/types.ts
index 3c7ebab..1f01b10 100644
--- a/src/platform/types.ts
+++ b/src/platform/types.ts
@@ -131,10 +131,4 @@ export interface PlatformContext {
// Closing confirmation
setClosingConfirmation: (enabled: boolean) => void;
-
- // Raw Telegram WebApp access (null in web)
- telegram: TelegramWebApp | null;
}
-
-// Note: TelegramWebApp types are defined in vite-env.d.ts
-// This file only re-exports the interface from the global scope
diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts
index 3b70828..47caca1 100644
--- a/src/vite-env.d.ts
+++ b/src/vite-env.d.ts
@@ -10,372 +10,3 @@ interface ImportMetaEnv {
interface ImportMeta {
readonly env: ImportMetaEnv;
}
-
-// Telegram WebApp types - comprehensive type definitions for Bot API 8.0+
-interface TelegramWebApp {
- // Init data
- initData: string;
- initDataUnsafe: {
- query_id?: string;
- user?: {
- id: number;
- is_bot?: boolean;
- first_name: string;
- last_name?: string;
- username?: string;
- language_code?: string;
- is_premium?: boolean;
- added_to_attachment_menu?: boolean;
- allows_write_to_pm?: boolean;
- photo_url?: string;
- };
- receiver?: {
- id: number;
- is_bot?: boolean;
- first_name: string;
- last_name?: string;
- username?: string;
- language_code?: string;
- is_premium?: boolean;
- added_to_attachment_menu?: boolean;
- allows_write_to_pm?: boolean;
- photo_url?: string;
- };
- chat?: {
- id: number;
- type: 'group' | 'supergroup' | 'channel';
- title: string;
- username?: string;
- photo_url?: string;
- };
- chat_type?: 'sender' | 'private' | 'group' | 'supergroup' | 'channel';
- chat_instance?: string;
- start_param?: string;
- can_send_after?: number;
- auth_date: number;
- hash: string;
- };
-
- // Platform info
- version: string;
- platform: string;
- colorScheme: 'light' | 'dark';
-
- // App state
- isExpanded: boolean;
- isClosingConfirmationEnabled: boolean;
- isVerticalSwipesEnabled: boolean;
- isFullscreen: boolean;
- isOrientationLocked: boolean;
- isActive: boolean;
-
- // Viewport
- viewportHeight: number;
- viewportStableHeight: number;
-
- // Safe areas
- safeAreaInset: { top: number; bottom: number; left: number; right: number };
- contentSafeAreaInset: { top: number; bottom: number; left: number; right: number };
-
- // Theme colors (settable)
- headerColor: string;
- backgroundColor: string;
- bottomBarColor?: string;
-
- // Theme params (from Telegram)
- themeParams: {
- bg_color?: string;
- text_color?: string;
- hint_color?: string;
- link_color?: string;
- button_color?: string;
- button_text_color?: string;
- secondary_bg_color?: string;
- header_bg_color?: string;
- bottom_bar_bg_color?: string;
- accent_text_color?: string;
- section_bg_color?: string;
- section_header_text_color?: string;
- section_separator_color?: string;
- subtitle_text_color?: string;
- destructive_text_color?: string;
- };
-
- // Lifecycle methods
- ready: () => void;
- expand: () => void;
- close: () => void;
-
- // Links
- openLink: (url: string, options?: { try_instant_view?: boolean; try_browser?: boolean }) => void;
- openTelegramLink: (url: string) => void;
-
- // Invoice
- openInvoice: (
- url: string,
- callback?: (status: 'paid' | 'cancelled' | 'failed' | 'pending') => void,
- ) => void;
-
- // Fullscreen API (Bot API 8.0+)
- requestFullscreen: () => void;
- exitFullscreen: () => void;
- lockOrientation: () => void;
- unlockOrientation: () => void;
-
- // Vertical swipes control
- disableVerticalSwipes: () => void;
- enableVerticalSwipes: () => void;
-
- // Closing confirmation
- enableClosingConfirmation: () => void;
- disableClosingConfirmation: () => void;
-
- // Theme color control
- setHeaderColor: (color: string) => void;
- setBackgroundColor: (color: string) => void;
- setBottomBarColor?: (color: string) => void;
-
- // Event handlers
- onEvent: (eventType: string, callback: (...args: unknown[]) => void) => void;
- offEvent: (eventType: string, callback: (...args: unknown[]) => void) => void;
-
- // Data sending
- sendData: (data: string) => void;
-
- // QR Scanner
- showScanQrPopup: (params: { text?: string }, callback?: (text: string) => boolean | void) => void;
- closeScanQrPopup: () => void;
-
- // Clipboard
- readTextFromClipboard: (callback?: (text: string | null) => void) => void;
-
- // Write access
- requestWriteAccess: (callback?: (granted: boolean) => void) => void;
-
- // Contact
- requestContact: (callback?: (granted: boolean) => void) => void;
-
- // Emoji status (Bot API 8.0+)
- requestEmojiStatusAccess: (callback?: (granted: boolean) => void) => void;
- setEmojiStatus: (
- customEmojiId: string,
- params?: { duration?: number },
- callback?: (success: boolean) => void,
- ) => void;
-
- // Download file (Bot API 8.0+)
- downloadFile: (
- params: { url: string; file_name: string },
- callback?: (success: boolean) => void,
- ) => void;
-
- // Share story (Bot API 7.8+)
- shareToStory?: (
- mediaUrl: string,
- params?: { text?: string; widget_link?: { url: string; name?: string } },
- ) => void;
-
- // Version check helper
- isVersionAtLeast: (version: string) => boolean;
-
- // Main Button
- MainButton: {
- text: string;
- color: string;
- textColor: string;
- isVisible: boolean;
- isActive: boolean;
- isProgressVisible: boolean;
- show: () => void;
- hide: () => void;
- enable: () => void;
- disable: () => void;
- showProgress: (leaveActive?: boolean) => void;
- hideProgress: () => void;
- setText: (text: string) => void;
- setParams: (params: {
- text?: string;
- color?: string;
- text_color?: string;
- has_shine_effect?: boolean;
- is_active?: boolean;
- is_visible?: boolean;
- }) => void;
- onClick: (callback: () => void) => void;
- offClick: (callback: () => void) => void;
- };
-
- // Secondary Button (Bot API 7.10+)
- SecondaryButton?: {
- text: string;
- color: string;
- textColor: string;
- isVisible: boolean;
- isActive: boolean;
- isProgressVisible: boolean;
- position: 'left' | 'right' | 'top' | 'bottom';
- show: () => void;
- hide: () => void;
- enable: () => void;
- disable: () => void;
- showProgress: (leaveActive?: boolean) => void;
- hideProgress: () => void;
- setText: (text: string) => void;
- setParams: (params: {
- text?: string;
- color?: string;
- text_color?: string;
- has_shine_effect?: boolean;
- is_active?: boolean;
- is_visible?: boolean;
- position?: 'left' | 'right' | 'top' | 'bottom';
- }) => void;
- onClick: (callback: () => void) => void;
- offClick: (callback: () => void) => void;
- };
-
- // Back Button
- BackButton: {
- isVisible: boolean;
- show: () => void;
- hide: () => void;
- onClick: (callback: () => void) => void;
- offClick: (callback: () => void) => void;
- };
-
- // Settings Button (Bot API 7.0+)
- SettingsButton?: {
- isVisible: boolean;
- show: () => void;
- hide: () => void;
- onClick: (callback: () => void) => void;
- offClick: (callback: () => void) => void;
- };
-
- // Haptic Feedback (Bot API 6.1+)
- HapticFeedback: {
- impactOccurred: (style: 'light' | 'medium' | 'heavy' | 'rigid' | 'soft') => void;
- notificationOccurred: (type: 'success' | 'warning' | 'error') => void;
- selectionChanged: () => void;
- };
-
- // Popups (Bot API 6.2+)
- showPopup: (
- params: {
- title?: string;
- message: string;
- buttons?: Array<{
- id?: string;
- type?: 'default' | 'ok' | 'close' | 'cancel' | 'destructive';
- text?: string;
- }>;
- },
- callback?: (buttonId: string) => void,
- ) => void;
- showAlert: (message: string, callback?: () => void) => void;
- showConfirm: (message: string, callback?: (confirmed: boolean) => void) => void;
-
- // Cloud Storage (Bot API 6.9+)
- CloudStorage: {
- setItem: (
- key: string,
- value: string,
- callback?: (error: Error | null, stored: boolean) => void,
- ) => void;
- getItem: (key: string, callback: (error: Error | null, value: string) => void) => void;
- getItems: (
- keys: string[],
- callback: (error: Error | null, values: Record) => void,
- ) => void;
- removeItem: (key: string, callback?: (error: Error | null, removed: boolean) => void) => void;
- removeItems: (
- keys: string[],
- callback?: (error: Error | null, removed: boolean) => void,
- ) => void;
- getKeys: (callback: (error: Error | null, keys: string[]) => void) => void;
- };
-
- // Biometric (Bot API 7.2+)
- BiometricManager?: {
- isInited: boolean;
- isBiometricAvailable: boolean;
- biometricType: 'finger' | 'face' | 'unknown';
- isAccessRequested: boolean;
- isAccessGranted: boolean;
- isBiometricTokenSaved: boolean;
- deviceId: string;
- init: (callback?: () => void) => void;
- requestAccess: (params: { reason?: string }, callback?: (granted: boolean) => void) => void;
- authenticate: (
- params: { reason?: string },
- callback?: (success: boolean, token?: string) => void,
- ) => void;
- updateBiometricToken: (token: string, callback?: (success: boolean) => void) => void;
- openSettings: () => void;
- };
-
- // Accelerometer (Bot API 8.0+)
- Accelerometer?: {
- isStarted: boolean;
- x: number | null;
- y: number | null;
- z: number | null;
- start: (params?: { refresh_rate?: number }, callback?: (started: boolean) => void) => void;
- stop: (callback?: (stopped: boolean) => void) => void;
- };
-
- // DeviceOrientation (Bot API 8.0+)
- DeviceOrientation?: {
- isStarted: boolean;
- absolute: boolean;
- alpha: number | null;
- beta: number | null;
- gamma: number | null;
- start: (
- params?: { refresh_rate?: number; need_absolute?: boolean },
- callback?: (started: boolean) => void,
- ) => void;
- stop: (callback?: (stopped: boolean) => void) => void;
- };
-
- // Gyroscope (Bot API 8.0+)
- Gyroscope?: {
- isStarted: boolean;
- x: number | null;
- y: number | null;
- z: number | null;
- start: (params?: { refresh_rate?: number }, callback?: (started: boolean) => void) => void;
- stop: (callback?: (stopped: boolean) => void) => void;
- };
-
- // Location Manager (Bot API 8.0+)
- LocationManager?: {
- isInited: boolean;
- isLocationAvailable: boolean;
- isAccessRequested: boolean;
- isAccessGranted: boolean;
- init: (callback?: () => void) => void;
- getLocation: (
- callback: (
- location: {
- latitude: number;
- longitude: number;
- altitude?: number;
- course?: number;
- speed?: number;
- horizontal_accuracy?: number;
- vertical_accuracy?: number;
- course_accuracy?: number;
- speed_accuracy?: number;
- } | null,
- ) => void,
- ) => void;
- openSettings: () => void;
- };
-}
-
-interface Window {
- Telegram?: {
- WebApp: TelegramWebApp;
- };
-}
diff --git a/vite.config.ts b/vite.config.ts
index 9d07f2e..f131284 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -49,11 +49,7 @@ export default defineConfig({
'@radix-ui/react-visually-hidden',
],
'vendor-dnd': ['@dnd-kit/core', '@dnd-kit/sortable', '@dnd-kit/utilities'],
- 'vendor-telegram': [
- '@telegram-apps/sdk',
- '@telegram-apps/sdk-react',
- '@telegram-apps/react-router-integration',
- ],
+ 'vendor-telegram': ['@telegram-apps/sdk-react'],
'vendor-utils': [
'axios',
'zustand',