mirror of
https://github.com/chillpadclub/bedolaga-cabinet.git
synced 2026-07-28 17:43:47 +00:00
fix: revert to native Telegram WebApp API, remove SDK usage
This commit is contained in:
@@ -1,9 +1,4 @@
|
|||||||
import { useCallback, useMemo, useSyncExternalStore } from 'react';
|
import { useCallback, useMemo } from 'react';
|
||||||
import { init, viewport, miniApp, swipeBehavior, themeParams } from '@tma.js/sdk-react';
|
|
||||||
|
|
||||||
// SDK initialization state
|
|
||||||
let sdkInitialized = false;
|
|
||||||
let initCleanup: VoidFunction | null = null;
|
|
||||||
|
|
||||||
const FULLSCREEN_CACHE_KEY = 'cabinet_fullscreen_enabled';
|
const FULLSCREEN_CACHE_KEY = 'cabinet_fullscreen_enabled';
|
||||||
|
|
||||||
@@ -48,7 +43,6 @@ export function isTelegramMobile(): boolean {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get Telegram init data for authentication
|
* Get Telegram init data for authentication
|
||||||
* Returns the raw initData string used for backend authentication
|
|
||||||
*/
|
*/
|
||||||
export function getTelegramInitData(): string | null {
|
export function getTelegramInitData(): string | null {
|
||||||
const webApp = window.Telegram?.WebApp;
|
const webApp = window.Telegram?.WebApp;
|
||||||
@@ -56,86 +50,31 @@ export function getTelegramInitData(): string | null {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize Telegram SDK
|
* Initialize Telegram WebApp (basic setup without SDK)
|
||||||
* Call this once at app startup (in main.tsx)
|
|
||||||
*/
|
*/
|
||||||
export function initTelegramSDK() {
|
export function initTelegramSDK() {
|
||||||
// Only run in Telegram context
|
|
||||||
if (!isInTelegramWebApp()) {
|
if (!isInTelegramWebApp()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prevent double initialization
|
const tg = window.Telegram?.WebApp;
|
||||||
if (sdkInitialized) {
|
if (!tg) return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
// Basic initialization
|
||||||
// Initialize the SDK
|
tg.ready();
|
||||||
initCleanup = init();
|
tg.expand();
|
||||||
sdkInitialized = true;
|
|
||||||
|
|
||||||
// Mount mini app and call ready (sync)
|
// Disable closing confirmation by default
|
||||||
miniApp.mount();
|
tg.disableClosingConfirmation?.();
|
||||||
miniApp.ready();
|
|
||||||
|
|
||||||
// Mount theme params and bind CSS variables (sync)
|
// Auto-enter fullscreen if enabled in settings (mobile only)
|
||||||
try {
|
const fullscreenEnabled = getCachedFullscreenEnabled();
|
||||||
themeParams.mount();
|
if (fullscreenEnabled && isTelegramMobile() && tg.requestFullscreen) {
|
||||||
themeParams.bindCssVars();
|
setTimeout(() => {
|
||||||
} catch {
|
if (!tg.isFullscreen) {
|
||||||
// Theme params may already be mounted or not supported
|
tg.requestFullscreen?.();
|
||||||
}
|
|
||||||
|
|
||||||
// Disable vertical swipes if supported (sync)
|
|
||||||
try {
|
|
||||||
if (swipeBehavior.isSupported()) {
|
|
||||||
swipeBehavior.mount();
|
|
||||||
swipeBehavior.disableVertical();
|
|
||||||
}
|
}
|
||||||
} catch {
|
}, 100);
|
||||||
// Swipe behavior not available
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mount viewport and bind CSS variables (async)
|
|
||||||
viewport
|
|
||||||
.mount()
|
|
||||||
.then(() => {
|
|
||||||
viewport.bindCssVars();
|
|
||||||
// Expand the mini app
|
|
||||||
viewport.expand();
|
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
console.warn('Viewport mount failed:', err);
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
console.warn('Telegram SDK initialization failed:', e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Cleanup SDK (call on app unmount if needed)
|
|
||||||
*/
|
|
||||||
export function cleanupTelegramSDK() {
|
|
||||||
if (initCleanup) {
|
|
||||||
initCleanup();
|
|
||||||
initCleanup = null;
|
|
||||||
sdkInitialized = false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,79 +92,59 @@ export type TelegramPlatform =
|
|||||||
| 'unknown'
|
| 'unknown'
|
||||||
| undefined;
|
| undefined;
|
||||||
|
|
||||||
// Default values for when SDK is not available
|
// Default values
|
||||||
const defaultInsets = { top: 0, bottom: 0, left: 0, right: 0 };
|
const defaultInsets = { top: 0, bottom: 0, left: 0, right: 0 };
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper to subscribe to SDK signals using useSyncExternalStore pattern
|
* Hook for Telegram WebApp integration
|
||||||
* Safely handles unmounted components by returning default values
|
* Uses native window.Telegram.WebApp API
|
||||||
*/
|
|
||||||
function useSDKSignal<T>(
|
|
||||||
signal: { (): T; sub(fn: VoidFunction): VoidFunction } | null,
|
|
||||||
defaultValue: T,
|
|
||||||
): T {
|
|
||||||
return useSyncExternalStore(
|
|
||||||
(callback) => {
|
|
||||||
if (!signal) return () => {};
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
() => defaultValue,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Hook for Telegram SDK integration
|
|
||||||
* Provides fullscreen mode, safe area insets, platform info, and other features
|
|
||||||
*/
|
*/
|
||||||
export function useTelegramSDK() {
|
export function useTelegramSDK() {
|
||||||
const inTelegram = isInTelegramWebApp();
|
const inTelegram = isInTelegramWebApp();
|
||||||
const isReady = inTelegram && sdkInitialized;
|
const tg = window.Telegram?.WebApp;
|
||||||
|
|
||||||
// Get platform from window.Telegram.WebApp (always available in Telegram)
|
|
||||||
const platform = useMemo<TelegramPlatform>(() => {
|
const platform = useMemo<TelegramPlatform>(() => {
|
||||||
if (!inTelegram) return undefined;
|
if (!inTelegram) return undefined;
|
||||||
return window.Telegram?.WebApp?.platform as TelegramPlatform;
|
return tg?.platform as TelegramPlatform;
|
||||||
}, [inTelegram]);
|
}, [inTelegram, tg?.platform]);
|
||||||
|
|
||||||
// Use SDK signals for reactive state
|
const isMobile = platform === 'ios' || platform === 'android';
|
||||||
const isFullscreen = useSDKSignal(isReady ? viewport.isFullscreen : null, false);
|
|
||||||
const safeAreaInsets = useSDKSignal(isReady ? viewport.safeAreaInsets : null, defaultInsets);
|
// Safe area insets from Telegram WebApp
|
||||||
const contentSafeAreaInsets = useSDKSignal(
|
const safeAreaInset = useMemo(() => {
|
||||||
isReady ? viewport.contentSafeAreaInsets : null,
|
if (!inTelegram || !tg?.safeAreaInset) return defaultInsets;
|
||||||
defaultInsets,
|
return {
|
||||||
);
|
top: tg.safeAreaInset.top || 0,
|
||||||
const viewportHeight = useSDKSignal(isReady ? viewport.height : null, 0);
|
bottom: tg.safeAreaInset.bottom || 0,
|
||||||
const viewportStableHeight = useSDKSignal(isReady ? viewport.stableHeight : null, 0);
|
left: tg.safeAreaInset.left || 0,
|
||||||
const viewportWidth = useSDKSignal(isReady ? viewport.width : null, 0);
|
right: tg.safeAreaInset.right || 0,
|
||||||
const isExpanded = useSDKSignal(isReady ? viewport.isExpanded : null, true);
|
};
|
||||||
|
}, [inTelegram, tg?.safeAreaInset]);
|
||||||
|
|
||||||
|
const contentSafeAreaInset = useMemo(() => {
|
||||||
|
if (!inTelegram || !tg?.contentSafeAreaInset) return defaultInsets;
|
||||||
|
return {
|
||||||
|
top: tg.contentSafeAreaInset.top || 0,
|
||||||
|
bottom: tg.contentSafeAreaInset.bottom || 0,
|
||||||
|
left: tg.contentSafeAreaInset.left || 0,
|
||||||
|
right: tg.contentSafeAreaInset.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;
|
||||||
|
|
||||||
const requestFullscreen = useCallback(() => {
|
const requestFullscreen = useCallback(() => {
|
||||||
if (!isReady) return;
|
if (!inTelegram || !tg?.requestFullscreen) return;
|
||||||
viewport.requestFullscreen().catch((e) => {
|
tg.requestFullscreen();
|
||||||
console.warn('Fullscreen not supported:', e);
|
}, [inTelegram, tg]);
|
||||||
});
|
|
||||||
}, [isReady]);
|
|
||||||
|
|
||||||
const exitFullscreen = useCallback(() => {
|
const exitFullscreen = useCallback(() => {
|
||||||
if (!isReady) return;
|
if (!inTelegram || !tg?.exitFullscreen) return;
|
||||||
viewport.exitFullscreen().catch((e) => {
|
tg.exitFullscreen();
|
||||||
console.warn('Exit fullscreen failed:', e);
|
}, [inTelegram, tg]);
|
||||||
});
|
|
||||||
}, [isReady]);
|
|
||||||
|
|
||||||
const toggleFullscreen = useCallback(() => {
|
const toggleFullscreen = useCallback(() => {
|
||||||
if (isFullscreen) {
|
if (isFullscreen) {
|
||||||
@@ -236,58 +155,31 @@ export function useTelegramSDK() {
|
|||||||
}, [isFullscreen, requestFullscreen, exitFullscreen]);
|
}, [isFullscreen, requestFullscreen, exitFullscreen]);
|
||||||
|
|
||||||
const expand = useCallback(() => {
|
const expand = useCallback(() => {
|
||||||
if (!isReady) return;
|
if (!inTelegram || !tg?.expand) return;
|
||||||
try {
|
tg.expand();
|
||||||
viewport.expand();
|
}, [inTelegram, tg]);
|
||||||
} catch {
|
|
||||||
// Viewport not available
|
|
||||||
}
|
|
||||||
}, [isReady]);
|
|
||||||
|
|
||||||
const disableVerticalSwipes = useCallback(() => {
|
const disableVerticalSwipes = useCallback(() => {
|
||||||
if (!isReady) return;
|
if (!inTelegram || !tg?.disableVerticalSwipes) return;
|
||||||
try {
|
tg.disableVerticalSwipes();
|
||||||
if (swipeBehavior.isSupported()) {
|
}, [inTelegram, tg]);
|
||||||
swipeBehavior.disableVertical();
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
// Swipe behavior not available
|
|
||||||
}
|
|
||||||
}, [isReady]);
|
|
||||||
|
|
||||||
const enableVerticalSwipes = useCallback(() => {
|
const enableVerticalSwipes = useCallback(() => {
|
||||||
if (!isReady) return;
|
if (!inTelegram || !tg?.enableVerticalSwipes) return;
|
||||||
try {
|
tg.enableVerticalSwipes();
|
||||||
if (swipeBehavior.isSupported()) {
|
}, [inTelegram, tg]);
|
||||||
swipeBehavior.enableVertical();
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
// Swipe behavior not available
|
|
||||||
}
|
|
||||||
}, [isReady]);
|
|
||||||
|
|
||||||
// Check if fullscreen is supported (Bot API 8.0+)
|
const isFullscreenSupported = inTelegram && typeof tg?.requestFullscreen === 'function';
|
||||||
const isFullscreenSupported = useMemo(() => {
|
|
||||||
if (!isReady) return false;
|
|
||||||
try {
|
|
||||||
return typeof viewport.requestFullscreen === 'function';
|
|
||||||
} catch {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}, [isReady]);
|
|
||||||
|
|
||||||
// Check if it's mobile platform
|
|
||||||
const isMobile = platform === 'ios' || platform === 'android';
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isTelegramWebApp: inTelegram,
|
isTelegramWebApp: inTelegram,
|
||||||
isFullscreen,
|
isFullscreen,
|
||||||
isFullscreenSupported,
|
isFullscreenSupported,
|
||||||
safeAreaInset: safeAreaInsets,
|
safeAreaInset,
|
||||||
contentSafeAreaInset: contentSafeAreaInsets,
|
contentSafeAreaInset,
|
||||||
viewportHeight,
|
viewportHeight,
|
||||||
viewportStableHeight,
|
viewportStableHeight,
|
||||||
viewportWidth,
|
viewportWidth: 0, // Not available in native API
|
||||||
isExpanded,
|
isExpanded,
|
||||||
platform,
|
platform,
|
||||||
isMobile,
|
isMobile,
|
||||||
@@ -297,8 +189,7 @@ export function useTelegramSDK() {
|
|||||||
expand,
|
expand,
|
||||||
disableVerticalSwipes,
|
disableVerticalSwipes,
|
||||||
enableVerticalSwipes,
|
enableVerticalSwipes,
|
||||||
// Expose raw SDK components for advanced usage
|
viewport: null,
|
||||||
viewport: isReady ? viewport : null,
|
miniApp: null,
|
||||||
miniApp: isReady ? miniApp : null,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,3 @@
|
|||||||
import {
|
|
||||||
backButton,
|
|
||||||
mainButton,
|
|
||||||
hapticFeedback,
|
|
||||||
cloudStorage,
|
|
||||||
themeParams,
|
|
||||||
popup,
|
|
||||||
miniApp,
|
|
||||||
} from '@tma.js/sdk-react';
|
|
||||||
import { isInTelegramWebApp } from '@/hooks/useTelegramSDK';
|
import { isInTelegramWebApp } from '@/hooks/useTelegramSDK';
|
||||||
import type {
|
import type {
|
||||||
PlatformContext,
|
PlatformContext,
|
||||||
@@ -24,217 +15,180 @@ import type {
|
|||||||
HapticNotificationType,
|
HapticNotificationType,
|
||||||
} from '@/platform/types';
|
} from '@/platform/types';
|
||||||
|
|
||||||
// Keep reference to raw Telegram WebApp for features not in SDK
|
// Use raw Telegram WebApp API directly - SDK has initialization issues
|
||||||
function getTelegram(): TelegramWebApp | null {
|
function getTelegram(): TelegramWebApp | null {
|
||||||
return window.Telegram?.WebApp ?? null;
|
return window.Telegram?.WebApp ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function safeIsSupported(checkFn: () => boolean): boolean {
|
|
||||||
try {
|
|
||||||
return checkFn();
|
|
||||||
} catch {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function createCapabilities(): PlatformCapabilities {
|
function createCapabilities(): PlatformCapabilities {
|
||||||
const tg = getTelegram();
|
const tg = getTelegram();
|
||||||
const inTelegram = isInTelegramWebApp();
|
const inTelegram = isInTelegramWebApp();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
hasBackButton: inTelegram && safeIsSupported(() => backButton.isSupported()),
|
hasBackButton: inTelegram && !!tg?.BackButton,
|
||||||
hasMainButton: inTelegram,
|
hasMainButton: inTelegram && !!tg?.MainButton,
|
||||||
hasHapticFeedback: inTelegram && safeIsSupported(() => hapticFeedback.isSupported()),
|
hasHapticFeedback: inTelegram && !!tg?.HapticFeedback,
|
||||||
hasNativeDialogs: inTelegram && safeIsSupported(() => popup.isSupported()),
|
hasNativeDialogs: inTelegram && !!tg?.showPopup,
|
||||||
hasThemeSync: inTelegram,
|
hasThemeSync: inTelegram,
|
||||||
hasInvoice: !!tg?.openInvoice,
|
hasInvoice: !!tg?.openInvoice,
|
||||||
hasCloudStorage: inTelegram && safeIsSupported(() => cloudStorage.isSupported()),
|
hasCloudStorage: inTelegram && !!tg?.CloudStorage,
|
||||||
hasShare: true,
|
hasShare: true,
|
||||||
version: tg?.version,
|
version: tg?.version,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function createBackButtonController(): BackButtonController {
|
function createBackButtonController(): BackButtonController {
|
||||||
|
const tg = getTelegram();
|
||||||
const inTelegram = isInTelegramWebApp();
|
const inTelegram = isInTelegramWebApp();
|
||||||
let removeClickListener: VoidFunction | null = null;
|
let currentCallback: (() => void) | null = null;
|
||||||
|
|
||||||
// Mount back button on first use
|
|
||||||
let mounted = false;
|
|
||||||
const ensureMounted = () => {
|
|
||||||
if (!mounted && inTelegram && safeIsSupported(() => backButton.isSupported())) {
|
|
||||||
try {
|
|
||||||
backButton.mount();
|
|
||||||
mounted = true;
|
|
||||||
} catch {
|
|
||||||
// Already mounted or not supported
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
get isVisible() {
|
get isVisible() {
|
||||||
if (!inTelegram || !mounted) return false;
|
if (!inTelegram || !tg?.BackButton) return false;
|
||||||
try {
|
return tg.BackButton.isVisible;
|
||||||
return backButton.isVisible();
|
|
||||||
} catch {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
show(onClick: () => void) {
|
show(onClick: () => void) {
|
||||||
if (!inTelegram || !safeIsSupported(() => backButton.isSupported())) return;
|
if (!inTelegram || !tg?.BackButton) return;
|
||||||
|
|
||||||
ensureMounted();
|
|
||||||
|
|
||||||
// Remove previous callback if exists
|
// Remove previous callback if exists
|
||||||
if (removeClickListener) {
|
if (currentCallback) {
|
||||||
removeClickListener();
|
tg.BackButton.offClick(currentCallback);
|
||||||
removeClickListener = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
removeClickListener = backButton.onClick(onClick);
|
currentCallback = onClick;
|
||||||
backButton.show();
|
tg.BackButton.onClick(onClick);
|
||||||
|
tg.BackButton.show();
|
||||||
},
|
},
|
||||||
|
|
||||||
hide() {
|
hide() {
|
||||||
if (!inTelegram || !safeIsSupported(() => backButton.isSupported())) return;
|
if (!inTelegram || !tg?.BackButton) return;
|
||||||
|
|
||||||
if (removeClickListener) {
|
if (currentCallback) {
|
||||||
removeClickListener();
|
tg.BackButton.offClick(currentCallback);
|
||||||
removeClickListener = null;
|
currentCallback = null;
|
||||||
}
|
}
|
||||||
backButton.hide();
|
tg.BackButton.hide();
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function createMainButtonController(): MainButtonController {
|
function createMainButtonController(): MainButtonController {
|
||||||
|
const tg = getTelegram();
|
||||||
const inTelegram = isInTelegramWebApp();
|
const inTelegram = isInTelegramWebApp();
|
||||||
let removeClickListener: VoidFunction | null = null;
|
let currentCallback: (() => void) | null = null;
|
||||||
|
|
||||||
// Mount main button on first use
|
|
||||||
let mounted = false;
|
|
||||||
const ensureMounted = () => {
|
|
||||||
if (!mounted && inTelegram) {
|
|
||||||
try {
|
|
||||||
mainButton.mount();
|
|
||||||
mounted = true;
|
|
||||||
} catch {
|
|
||||||
// Already mounted or not supported
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
get isVisible() {
|
get isVisible() {
|
||||||
if (!inTelegram || !mounted) return false;
|
if (!inTelegram || !tg?.MainButton) return false;
|
||||||
try {
|
return tg.MainButton.isVisible;
|
||||||
return mainButton.isVisible();
|
|
||||||
} catch {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
show(config: MainButtonConfig) {
|
show(config: MainButtonConfig) {
|
||||||
if (!inTelegram) return;
|
if (!inTelegram || !tg?.MainButton) return;
|
||||||
|
|
||||||
ensureMounted();
|
|
||||||
|
|
||||||
// Remove previous callback if exists
|
// Remove previous callback if exists
|
||||||
if (removeClickListener) {
|
if (currentCallback) {
|
||||||
removeClickListener();
|
tg.MainButton.offClick(currentCallback);
|
||||||
removeClickListener = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set button parameters
|
// Set button parameters
|
||||||
mainButton.setParams({
|
tg.MainButton.setText(config.text);
|
||||||
text: config.text,
|
if (config.color) {
|
||||||
bgColor: config.color as `#${string}` | undefined,
|
tg.MainButton.color = config.color;
|
||||||
textColor: config.textColor as `#${string}` | undefined,
|
}
|
||||||
isEnabled: config.isActive !== false,
|
if (config.textColor) {
|
||||||
isVisible: true,
|
tg.MainButton.textColor = config.textColor;
|
||||||
isLoaderVisible: config.isLoading || false,
|
}
|
||||||
});
|
|
||||||
|
|
||||||
removeClickListener = mainButton.onClick(config.onClick);
|
if (config.isActive === false) {
|
||||||
mainButton.show();
|
tg.MainButton.disable();
|
||||||
|
} else {
|
||||||
|
tg.MainButton.enable();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.isLoading) {
|
||||||
|
tg.MainButton.showProgress();
|
||||||
|
} else {
|
||||||
|
tg.MainButton.hideProgress();
|
||||||
|
}
|
||||||
|
|
||||||
|
currentCallback = config.onClick;
|
||||||
|
tg.MainButton.onClick(config.onClick);
|
||||||
|
tg.MainButton.show();
|
||||||
},
|
},
|
||||||
|
|
||||||
hide() {
|
hide() {
|
||||||
if (!inTelegram) return;
|
if (!inTelegram || !tg?.MainButton) return;
|
||||||
|
|
||||||
if (removeClickListener) {
|
if (currentCallback) {
|
||||||
removeClickListener();
|
tg.MainButton.offClick(currentCallback);
|
||||||
removeClickListener = null;
|
currentCallback = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
mainButton.hideLoader();
|
tg.MainButton.hideProgress();
|
||||||
mainButton.hide();
|
tg.MainButton.hide();
|
||||||
},
|
},
|
||||||
|
|
||||||
showProgress(show: boolean) {
|
showProgress(show: boolean) {
|
||||||
if (!inTelegram) return;
|
if (!inTelegram || !tg?.MainButton) return;
|
||||||
|
|
||||||
if (show) {
|
if (show) {
|
||||||
mainButton.showLoader();
|
tg.MainButton.showProgress();
|
||||||
} else {
|
} else {
|
||||||
mainButton.hideLoader();
|
tg.MainButton.hideProgress();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
setText(text: string) {
|
setText(text: string) {
|
||||||
if (!inTelegram) return;
|
if (!inTelegram || !tg?.MainButton) return;
|
||||||
mainButton.setText(text);
|
tg.MainButton.setText(text);
|
||||||
},
|
},
|
||||||
|
|
||||||
setActive(active: boolean) {
|
setActive(active: boolean) {
|
||||||
if (!inTelegram) return;
|
if (!inTelegram || !tg?.MainButton) return;
|
||||||
|
|
||||||
if (active) {
|
if (active) {
|
||||||
mainButton.enable();
|
tg.MainButton.enable();
|
||||||
} else {
|
} else {
|
||||||
mainButton.disable();
|
tg.MainButton.disable();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function createHapticController(): HapticController {
|
function createHapticController(): HapticController {
|
||||||
|
const tg = getTelegram();
|
||||||
const inTelegram = isInTelegramWebApp();
|
const inTelegram = isInTelegramWebApp();
|
||||||
const isSupported = inTelegram && safeIsSupported(() => hapticFeedback.isSupported());
|
const haptic = tg?.HapticFeedback;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
impact(style: HapticImpactStyle = 'medium') {
|
impact(style: HapticImpactStyle = 'medium') {
|
||||||
if (!isSupported) return;
|
if (!inTelegram || !haptic) return;
|
||||||
hapticFeedback.impactOccurred(style);
|
haptic.impactOccurred(style);
|
||||||
},
|
},
|
||||||
|
|
||||||
notification(type: HapticNotificationType) {
|
notification(type: HapticNotificationType) {
|
||||||
if (!isSupported) return;
|
if (!inTelegram || !haptic) return;
|
||||||
hapticFeedback.notificationOccurred(type);
|
haptic.notificationOccurred(type);
|
||||||
},
|
},
|
||||||
|
|
||||||
selection() {
|
selection() {
|
||||||
if (!isSupported) return;
|
if (!inTelegram || !haptic) return;
|
||||||
hapticFeedback.selectionChanged();
|
haptic.selectionChanged();
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function createDialogController(): DialogController {
|
function createDialogController(): DialogController {
|
||||||
|
const tg = getTelegram();
|
||||||
const inTelegram = isInTelegramWebApp();
|
const inTelegram = isInTelegramWebApp();
|
||||||
const isSupported = inTelegram && safeIsSupported(() => popup.isSupported());
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
alert(message: string, _title?: string): Promise<void> {
|
alert(message: string, _title?: string): Promise<void> {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
if (isSupported) {
|
if (inTelegram && tg?.showPopup) {
|
||||||
popup
|
tg.showPopup({ message, buttons: [{ type: 'ok' }] }, () => resolve());
|
||||||
.show({
|
|
||||||
message,
|
|
||||||
buttons: [{ type: 'ok' }],
|
|
||||||
})
|
|
||||||
.then(() => resolve());
|
|
||||||
} else {
|
} else {
|
||||||
window.alert(message);
|
window.alert(message);
|
||||||
resolve();
|
resolve();
|
||||||
@@ -244,16 +198,17 @@ function createDialogController(): DialogController {
|
|||||||
|
|
||||||
confirm(message: string, _title?: string): Promise<boolean> {
|
confirm(message: string, _title?: string): Promise<boolean> {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
if (isSupported) {
|
if (inTelegram && tg?.showPopup) {
|
||||||
popup
|
tg.showPopup(
|
||||||
.show({
|
{
|
||||||
message,
|
message,
|
||||||
buttons: [
|
buttons: [
|
||||||
{ id: 'ok', type: 'ok' },
|
{ id: 'ok', type: 'ok' },
|
||||||
{ id: 'cancel', type: 'cancel' },
|
{ id: 'cancel', type: 'cancel' },
|
||||||
],
|
],
|
||||||
})
|
},
|
||||||
.then((buttonId) => resolve(buttonId === 'ok'));
|
(buttonId) => resolve(buttonId === 'ok'),
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
resolve(window.confirm(message));
|
resolve(window.confirm(message));
|
||||||
}
|
}
|
||||||
@@ -262,9 +217,9 @@ function createDialogController(): DialogController {
|
|||||||
|
|
||||||
popup(options: PopupOptions): Promise<string | null> {
|
popup(options: PopupOptions): Promise<string | null> {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
if (isSupported) {
|
if (inTelegram && tg?.showPopup) {
|
||||||
popup
|
tg.showPopup(
|
||||||
.show({
|
{
|
||||||
title: options.title,
|
title: options.title,
|
||||||
message: options.message,
|
message: options.message,
|
||||||
buttons: options.buttons?.map((btn) => ({
|
buttons: options.buttons?.map((btn) => ({
|
||||||
@@ -272,8 +227,9 @@ function createDialogController(): DialogController {
|
|||||||
type: btn.type,
|
type: btn.type,
|
||||||
text: btn.text,
|
text: btn.text,
|
||||||
})),
|
})),
|
||||||
})
|
},
|
||||||
.then((buttonId) => resolve(buttonId ?? null));
|
(buttonId) => resolve(buttonId ?? null),
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
const confirmed = window.confirm(options.message);
|
const confirmed = window.confirm(options.message);
|
||||||
resolve(confirmed ? 'ok' : null);
|
resolve(confirmed ? 'ok' : null);
|
||||||
@@ -284,85 +240,87 @@ function createDialogController(): DialogController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function createThemeController(): ThemeController {
|
function createThemeController(): ThemeController {
|
||||||
|
const tg = getTelegram();
|
||||||
const inTelegram = isInTelegramWebApp();
|
const inTelegram = isInTelegramWebApp();
|
||||||
|
|
||||||
// Mount theme params on first use
|
|
||||||
let mounted = false;
|
|
||||||
const ensureMounted = () => {
|
|
||||||
if (!mounted && inTelegram) {
|
|
||||||
try {
|
|
||||||
themeParams.mount();
|
|
||||||
mounted = true;
|
|
||||||
} catch {
|
|
||||||
// Already mounted
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
setHeaderColor(color: string) {
|
setHeaderColor(color: string) {
|
||||||
if (!inTelegram) return;
|
if (!inTelegram || !tg?.setHeaderColor) return;
|
||||||
miniApp.setHeaderColor(color as `#${string}`);
|
tg.setHeaderColor(color as `#${string}`);
|
||||||
},
|
},
|
||||||
|
|
||||||
setBottomBarColor(color: string) {
|
setBottomBarColor(color: string) {
|
||||||
if (!inTelegram) return;
|
if (!inTelegram || !tg?.setBottomBarColor) return;
|
||||||
try {
|
try {
|
||||||
miniApp.setBottomBarColor(color as `#${string}`);
|
tg.setBottomBarColor(color as `#${string}`);
|
||||||
} catch {
|
} catch {
|
||||||
// Not supported in this version
|
// Not supported in this version
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
getThemeParams() {
|
getThemeParams() {
|
||||||
if (!inTelegram) return null;
|
if (!inTelegram || !tg?.themeParams) return null;
|
||||||
ensureMounted();
|
const params = tg.themeParams;
|
||||||
try {
|
return {
|
||||||
const state = themeParams.state();
|
bg_color: params.bg_color,
|
||||||
// Convert SDK format to our format
|
text_color: params.text_color,
|
||||||
return {
|
hint_color: params.hint_color,
|
||||||
bg_color: state.bgColor,
|
link_color: params.link_color,
|
||||||
text_color: state.textColor,
|
button_color: params.button_color,
|
||||||
hint_color: state.hintColor,
|
button_text_color: params.button_text_color,
|
||||||
link_color: state.linkColor,
|
secondary_bg_color: params.secondary_bg_color,
|
||||||
button_color: state.buttonColor,
|
header_bg_color: params.header_bg_color,
|
||||||
button_text_color: state.buttonTextColor,
|
bottom_bar_bg_color: params.bottom_bar_bg_color,
|
||||||
secondary_bg_color: state.secondaryBgColor,
|
accent_text_color: params.accent_text_color,
|
||||||
header_bg_color: state.headerBgColor,
|
section_bg_color: params.section_bg_color,
|
||||||
bottom_bar_bg_color: state.bottomBarBgColor,
|
section_header_text_color: params.section_header_text_color,
|
||||||
accent_text_color: state.accentTextColor,
|
subtitle_text_color: params.subtitle_text_color,
|
||||||
section_bg_color: state.sectionBgColor,
|
destructive_text_color: params.destructive_text_color,
|
||||||
section_header_text_color: state.sectionHeaderTextColor,
|
};
|
||||||
subtitle_text_color: state.subtitleTextColor,
|
|
||||||
destructive_text_color: state.destructiveTextColor,
|
|
||||||
};
|
|
||||||
} catch {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function createCloudStorageController(): CloudStorageController | null {
|
function createCloudStorageController(): CloudStorageController | null {
|
||||||
|
const tg = getTelegram();
|
||||||
const inTelegram = isInTelegramWebApp();
|
const inTelegram = isInTelegramWebApp();
|
||||||
if (!inTelegram || !safeIsSupported(() => cloudStorage.isSupported())) return null;
|
const storage = tg?.CloudStorage;
|
||||||
|
|
||||||
|
if (!inTelegram || !storage) return null;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
async getItem(key: string): Promise<string | null> {
|
async getItem(key: string): Promise<string | null> {
|
||||||
const value = await cloudStorage.getItem(key);
|
return new Promise((resolve) => {
|
||||||
return value || null;
|
storage.getItem(key, (error, value) => {
|
||||||
|
resolve(error ? null : value || null);
|
||||||
|
});
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
async setItem(key: string, value: string): Promise<void> {
|
async setItem(key: string, value: string): Promise<void> {
|
||||||
await cloudStorage.setItem(key, value);
|
return new Promise((resolve, reject) => {
|
||||||
|
storage.setItem(key, value, (error) => {
|
||||||
|
if (error) reject(new Error(String(error)));
|
||||||
|
else resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
async removeItem(key: string): Promise<void> {
|
async removeItem(key: string): Promise<void> {
|
||||||
await cloudStorage.deleteItem(key);
|
return new Promise((resolve, reject) => {
|
||||||
|
storage.removeItem(key, (error) => {
|
||||||
|
if (error) reject(new Error(String(error)));
|
||||||
|
else resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
async getKeys(): Promise<string[]> {
|
async getKeys(): Promise<string[]> {
|
||||||
return cloudStorage.getKeys();
|
return new Promise((resolve) => {
|
||||||
|
storage.getKeys((error, keys) => {
|
||||||
|
resolve(error ? [] : keys || []);
|
||||||
|
});
|
||||||
|
});
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -385,7 +343,6 @@ export function createTelegramAdapter(): PlatformContext {
|
|||||||
if (tg?.openInvoice) {
|
if (tg?.openInvoice) {
|
||||||
tg.openInvoice(url, (status) => resolve(status));
|
tg.openInvoice(url, (status) => resolve(status));
|
||||||
} else {
|
} else {
|
||||||
// Fallback: open in new window
|
|
||||||
window.open(url, '_blank');
|
window.open(url, '_blank');
|
||||||
resolve('pending');
|
resolve('pending');
|
||||||
}
|
}
|
||||||
@@ -411,17 +368,15 @@ export function createTelegramAdapter(): PlatformContext {
|
|||||||
async share(text: string, url?: string): Promise<boolean> {
|
async share(text: string, url?: string): Promise<boolean> {
|
||||||
const shareText = url ? `${text}\n${url}` : text;
|
const shareText = url ? `${text}\n${url}` : text;
|
||||||
|
|
||||||
// Try native share API first (if available on mobile)
|
|
||||||
if (navigator.share) {
|
if (navigator.share) {
|
||||||
try {
|
try {
|
||||||
await navigator.share({ text: shareText, url });
|
await navigator.share({ text: shareText, url });
|
||||||
return true;
|
return true;
|
||||||
} catch {
|
} catch {
|
||||||
// User cancelled or share failed, continue to Telegram share
|
// User cancelled or share failed
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use Telegram share
|
|
||||||
const botUsername = import.meta.env.VITE_TELEGRAM_BOT_USERNAME;
|
const botUsername = import.meta.env.VITE_TELEGRAM_BOT_USERNAME;
|
||||||
if (botUsername && tg?.openTelegramLink) {
|
if (botUsername && tg?.openTelegramLink) {
|
||||||
const encoded = encodeURIComponent(shareText);
|
const encoded = encodeURIComponent(shareText);
|
||||||
|
|||||||
Reference in New Issue
Block a user