Телеграм WebApp
Начало работы
git clone https://dev.h3llo.cloud/H3LLO.CLOUD/twa_starter // Клонируем проект npm install // Установка зависимостей npm run dev:https // Запуск dev сервера с самозаверенным SSL-сертификатом
Создание бота через @botfather
- Начните диалог с @botfather
- Команда для создания нового бота /newbot
- Следуйте инструкции, бот попросит вас:
- Указать имя вашего бота (прим. "My new WebApp")
- Юзернейм (прим. "mynewbot") Имейте ввиду что юзернейм обязательно должен заканчиваться на bot
После указания нужных данных бот сообщит об успешном создании бота и пришлёт в ответ его токен. Для WebApp токен не требуется, однако он нужен для взаимодействия с ботом (отправка сообщений пользователю и т.п.). Подробнее вы можете почитать тут.
Создание WebApp
- Команда для создания нового приложения /newapp
- BotFather попросит указать к какому боту прикрипить приложение
- Далее потребуется указать: название, краткое описание, загрузить фото 640х360 пикселей, GIF превью (можно пропустить), ссылку на ваше приложение (прим. https://example.com/my-web-app), а также краткое имя приложения.
После этого бот пришлёт вам ссылку, для запуска вашего приложения внутри Telegram.
Важно! Если вы хотите разрабатывать приложение локально, с поддержкой Hot Reload, укажите https://127.0.0.1:{PORT} в качестве ссылки на ваше приложение и запустите локальный сервер с активным SSL-сертификатом (в т.ч. можно использовать самозаверенный сертификат).
Если после открытия WebApp внутри Telegram вы видите ошибку, откройте ваше приложение в браузере и разрешите использование сертификата.
Разработка WebApp, примеры кода и лайфхаки
1. Поскольку приложение спроектировано так, чтобы контент не превышал высоту экрана, фиксируем экран для удобного взаимодействия при свайпах:
// layout.tsx
<body className="absolute inset-0 h-screen w-screen overflow-hidden">
{children}
</body>2. Инициализуем функции, которые предоставляет Telegram в файле init.tsx:
import {backButton, viewport, miniApp, initData, $debug, init as initSDK, swipeBehavior, themeParams} from '@telegram-apps/sdk-react';
/** * Инициализируем приложение и настраиваем зависимости. */
export async function init(debug: boolean): Promise<void> {
// Устанавливает режим отладки для @telegram-apps/sdk-react.
$debug.set(debug);
// Инициализируем специальные обработчики событий для Telegram Desktop, Android, iOS и т.д.
// Также настраиваем пакет.
initSDK();
// Кнопка назад.
if (backButton.isSupported()) {
backButton.mount();
}
// Определяем CSS-переменные, связанные с компонентами.
if (!miniApp.isMounted()) {
miniApp.mount();
}
// Создаём CSS переменные вроде:
// --tg-bg-color: #aabbcc
// --tg-header-color: #aabbcc
if (miniApp.bindCssVars.isAvailable()) {
miniApp.bindCssVars();
}
if (!themeParams.isMounted()) {
themeParams.mount();
}
// Создаём CSS переменные вроде:
// --tg-theme-button-color: #aabbcc
// --tg-theme-accent-text-color: #aabbcc
// --tg-theme-bg-color: #aabbcc
if (themeParams.bindCssVars.isAvailable()) {
themeParams.bindCssVars();
}
if (!viewport.isMounted() && !viewport.isMounting()) {
void viewport.mount().catch((e) => {
console.error("Something went wrong mounting the viewport", e);
}).then(() => {
// Переводим приложение в полноэкранный режим, если это возможно.
if (viewport.requestFullscreen.isSupported() && viewport.requestFullscreen.isAvailable() && !viewport.isFullscreen()) viewport.requestFullscreen() });
}
// Создаём CSS переменные вроде:
// --tg-viewport-height: 675px
// --tg-viewport-width: 320px
// --tg-viewport-stable-height: 675px
if (viewport.bindCssVars.isAvailable()) {
viewport.bindCssVars();
}
// Отключаем вертикальные свайпы (чтобы пользователь случайно не свернул приложение)
if (!swipeBehavior.isMounted()) {
swipeBehavior.mount();
}
if (swipeBehavior.disableVertical.isAvailable()) {
swipeBehavior.disableVertical();
}
// Восстанавливаем initData
initData.restore();
}3. Хендлер для управления кнопкой "Назад":
'use client';
import { backButton } from '@telegram-apps/sdk-react';
import { PropsWithChildren, useEffect } from 'react';
import {useRouter} from "next/navigation";
export function BackButtonHandler({ children, back = true }: PropsWithChildren<{
/**
* True если нужно отображать кнопку, иначе false.
*/
back?: boolean
}>) {
const router = useRouter()
useEffect(() => {
if (back) {
backButton.show();
return backButton.onClick(() => {
router.back();
});
}
backButton.hide();
}, [back, router]);
return children;
}