mirror of
https://github.com/chillpadclub/bedolaga-cabinet.git
synced 2026-07-29 18:13:47 +00:00
refactor: migrate to @telegram-apps/sdk-react v3, remove all window.Telegram.WebApp usage
Полный переход с нативного window.Telegram.WebApp API на @telegram-apps/sdk-react v3. Bridge SDK оборачивал receiveEvent, из-за чего popup_closed не доходил до нативного обработчика — это было основной причиной бага с двойным открытием попапа. Изменения: - Удалён @telegram-apps/react-router-integration (привязан к SDK v1) - Инициализация SDK v3 в main.tsx: init(), restoreInitData(), mount всех компонентов - AppWithNavigator: убран TelegramRouter (initNavigator + useIntegration), все платформы на BrowserRouter, добавлен TelegramBackButton через SDK v3 - useTelegramSDK: детекция через retrieveLaunchParams(), реактивные значения через useSignal() (fullscreen, viewport, safe area insets), initData через retrieveRawInitData() - TelegramAdapter: полная перезапись — попапы через showPopup() (Promise-based, без ручного onEvent/offEvent/timeout), кнопки через setMainButtonParams/showBackButton, haptic через hapticFeedbackImpactOccurred, тема через themeParamsState с конвертацией camelCase→snake_case, cloud storage через getCloudStorageItem/setCloudStorageItem - api/client.ts: initData через retrieveRawInitData() вместо window.Telegram.WebApp.initData - AppHeader: фото пользователя через initDataUser() сигнал - ConnectionModal: openLink через SDK вместо window.Telegram.WebApp.openLink - ColorPicker: детекция Telegram через isInTelegramWebApp() - Удалён интерфейс TelegramWebApp (368 строк) и Window augmentation из vite-env.d.ts - Убрано поле telegram из PlatformContext в types.ts - Обновлён manualChunks в vite.config.ts
This commit is contained in:
@@ -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<T>(
|
||||
params: Parameters<NonNullable<TelegramWebApp['showPopup']>>[0],
|
||||
mapResult: (buttonId: string) => T,
|
||||
fallback: () => T,
|
||||
): Promise<T> {
|
||||
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<void> {
|
||||
return showPopupSafe(
|
||||
{ message, buttons: [{ type: 'ok' }] },
|
||||
() => undefined,
|
||||
() => {
|
||||
window.alert(message);
|
||||
},
|
||||
);
|
||||
async alert(message: string, _title?: string): Promise<void> {
|
||||
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<boolean> {
|
||||
return showPopupSafe(
|
||||
{
|
||||
async confirm(message: string, _title?: string): Promise<boolean> {
|
||||
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<string | null> {
|
||||
return showPopupSafe(
|
||||
{
|
||||
async popup(options: PopupOptions): Promise<string | null> {
|
||||
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<typeof showPopup>[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<string | null> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
storage.removeItem(key, (error) => {
|
||||
if (error) reject(new Error(String(error)));
|
||||
else resolve();
|
||||
});
|
||||
});
|
||||
await deleteCloudStorageItem(key);
|
||||
},
|
||||
|
||||
async getKeys(): Promise<string[]> {
|
||||
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<InvoiceStatus> {
|
||||
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<InvoiceStatus>;
|
||||
} 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,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -271,7 +271,5 @@ export function createWebAdapter(): PlatformContext {
|
||||
window.onbeforeunload = null;
|
||||
}
|
||||
},
|
||||
|
||||
telegram: null,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user