Телеграм 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; }