fix: revert to native Telegram WebApp API, remove SDK usage

This commit is contained in:
c0mrade
2026-02-01 20:37:23 +03:00
parent e5ea09dd3a
commit 6f8bc4fca5
2 changed files with 207 additions and 361 deletions

View File

@@ -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,
}; };
} }

View File

@@ -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);