Разработка Telegram Mini Apps (TWA) на современном стекe Vue 3 требует глубокого понимания специфики взаимодействия клиентского WebView с клиентом Telegram и серверным бэкендом. В отличие от стандартных SPA, мини-приложения функционируют внутри изолированного контейнера, где необходимо корректно обрабатывать изменения viewport, подстраиваться под системную цветовую схему пользователя и защищать передаваемые данные от подделки.
В этой статье мы разберем построение клиентской части на Vue 3 с применением Composition API, управление устойчивостью интерфейса, передачу данных боту и строгую валидацию подлинности сессии на стороне PHP.
1. Инициализация Telegram WebApp SDK и Composition API в Vue 3
Подключить Telegram WebApp JS SDK можно через скрипт https://telegram.org/js/telegram-web-app.js в index.html или установив npm-пакет. Для обеспечения реактивности в Vue 3 целесообразно вынести работу с window.Telegram.WebApp в отдельный Composable-модуль useTelegram.
Создадим реактивный хук, который инициализирует WebApp, отслеживает состояние разворачивания экрана (isExpanded) и предоставляет интерфейс для вызова нативных методов.
import { ref, onMounted, onUnmounted } from 'vue';
export function useTelegram() {
const tg = window.Telegram?.WebApp;
const user = ref(tg?.initDataUnsafe?.user || null);
const isExpanded = ref(tg?.isExpanded || false);
const themeParams = ref(tg?.themeParams || {});
const handleViewportChanged = () => {
isExpanded.value = tg?.isExpanded || false;
};
onMounted(() => {
if (!tg) {
console.warn('Telegram WebApp SDK не найден');
return;
}
tg.ready();
tg.expand();
tg.onEvent('viewportChanged', handleViewportChanged);
});
onUnmounted(() => {
if (tg) {
tg.offEvent('viewportChanged', handleViewportChanged);
}
});
const sendDataToBot = (data) => {
if (!tg) return;
tg.sendData(JSON.stringify(data));
};
return {
tg,
user,
isExpanded,
themeParams,
sendDataToBot
};
}2. Управление Viewport и защита от прыгающего интерфейса
Одной из главных проблем при разработке Mini Apps на мобильных устройствах является нестабильность высоты области просмотра (viewport). Вызов клавиатуры или свайп пользователя может приводить к свёртыванию приложения или появлению пустых областей.
Чтобы предотвратить случайное закрытие приложения свайпом вниз и зафиксировать интерфейс, необходимо при монтировании главного компонента вызывать метод expand(), а также применить CSS-переменные для динамической высоты экрана.
import { onMounted } from 'vue';
import { useTelegram } from './useTelegram';
export default {
name: 'App',
setup() {
const { tg, themeParams } = useTelegram();
const applyThemeVariables = () => {
if (!themeParams.value) return;
const root = document.documentElement;
const tp = themeParams.value;
if (tp.bg_color) root.style.setProperty('--tg-theme-bg-color', tp.bg_color);
if (tp.text_color) root.style.setProperty('--tg-theme-text-color', tp.text_color);
if (tp.button_color) root.style.setProperty('--tg-theme-button-color', tp.button_color);
if (tp.button_text_color) root.style.setProperty('--tg-theme-button-text-color', tp.button_text_color);
if (tp.secondary_bg_color) root.style.setProperty('--tg-theme-secondary-bg-color', tp.secondary_bg_color);
};
onMounted(() => {
if (tg) {
tg.enableClosingConfirmation();
applyThemeVariables();
}
});
return {};
}
};3. Передача данных в чат с ботом через sendData
Метод Telegram.WebApp.sendData() позволяет мгновенно отправить строковую полезную нагрузку в диалог с ботом и автоматически закрыть Mini App. Важное техническое ограничение: метод sendData() работает только если веб-приложение было открыто через обычную кнопку клавиатуры (KeyboardButton типа web_app). Если приложение открыто из Inline-кнопки или системного меню, sendData() вызовет ошибку SDK.
Пример отправки структуры заказа из компонента Vue 3:
import { ref } from 'vue';
import { useTelegram } from './useTelegram';
export default {
name: 'OrderForm',
setup() {
const { sendDataToBot } = useTelegram();
const serviceId = ref(102);
const comment = ref('');
const submitForm = () => {
const payload = {
action: 'create_order',
lead_id: Date.now().toString(36),
service_id: serviceId.value,
comment: comment.value
};
sendDataToBot(payload);
};
return { serviceId, comment, submitForm };
}
};4. Валидация initData на бэкенде (PHP HMAC-SHA256)
Объект initDataUnsafe на клиенте может быть легко подделан пользователем через DevTools. Для безопасной аутентификации бэкенд должен получать сырую строку initData (переданную через HTTP-заголовок Authorization или в тела POST-запроса) и валидировать её с использованием токена бота.
Ниже представлен рабочий PHP-скрипт проверку криптографической подписи initData с применением бинарного HMAC-SHA256 и функций hash_equals для защиты от атак по времени (timing attacks):
<?php
function validateTelegramInitData(string $initData, string $botToken): ?array
{
parse_str($initData, $data);
if (!isset($data['hash'])) {
return null;
}
$checkHash = $data['hash'];
unset($data['hash']);
$dataCheckArray = [];
foreach ($data as $key => $value) {
$dataCheckArray[] = $key . '=' . $value;
}
sort($dataCheckArray);
$dataCheckString = implode("
", $dataCheckArray);
// 1. Создаем секретный ключ из токена бота
$secretKey = hash_hmac('sha256', $botToken, 'WebAppData', true);
// 2. Вычисляем контрольный хэш от строки данных
$calculatedHash = hash_hmac('sha256', $dataCheckString, $secretKey);
// 3. Timing-safe сравнение хэшей
if (!hash_equals($calculatedHash, $checkHash)) {
return null;
}
// 4. Проверка устаревания данных (например, не старше 24 часов)
if (isset($data['auth_date']) && (time() - (int)$data['auth_date'] > 86400)) {
return null;
}
return $data;
}
// Пример обработки входящего запроса
header('Content-Type: application/json');
$headers = getallheaders();
$initData = $headers['X-Telegram-Init-Data'] ?? $_POST['init_data'] ?? '';
$botToken = getenv('TELEGRAM_BOT_TOKEN') ?: '';
if (empty($initData) || empty($botToken)) {
http_response_code(400);
echo json_encode(['ok' => false, 'error' => 'Missing required parameters']);
exit;
}
$validated = validateTelegramInitData($initData, $botToken);
if ($validated === null) {
http_response_code(403);
echo json_encode(['ok' => false, 'error' => 'Invalid initData signature or data expired']);
exit;
}
$userData = json_decode($validated['user'] ?? '{}', true);
echo json_encode([
'ok' => true,
'user_id' => $userData['id'] ?? null,
'username' => $userData['username'] ?? null
]);5. Отправка ответов через Bot API на PHP при работе через Inline Mini Apps
Если Mini App запущен из Inline-кнопки, отправка sendData() не сработает. В этом случае фронтенд отправляет AJAX-запрос на ваш PHP-бэкенд, а бэкенд совершает прямой вызов метода Telegram Bot API sendMessage через cURL.
<?php
function sendTelegramMessage(int $chatId, string $text): bool
{
$botToken = getenv('TELEGRAM_BOT_TOKEN');
if (!$botToken) {
error_log('TELEGRAM_BOT_TOKEN is not defined');
return false;
}
$url = "https://api.telegram.org/bot{$botToken}/sendMessage";
$payload = [
'chat_id' => $chatId,
'text' => $text,
'parse_mode' => 'HTML'
];
$ch = curl_init($url);
curl_setopt_array($ch, [
'CURLOPT_POST' => true,
'CURLOPT_RETURNTRANSFER' => true,
'CURLOPT_HTTPHEADER' => ['Content-Type: application/json'],
'CURLOPT_POSTFIELDS' => json_encode($payload),
'CURLOPT_TIMEOUT' => 5,
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$curlError = curl_error($ch);
curl_close($ch);
if ($curlError || $httpCode !== 200) {
error_log("Telegram API Error: HTTP {$httpCode}, cURL: {$curlError}");
return false;
}
$result = json_decode($response, true);
if (json_last_error() !== JSON_ERROR_NONE || !($result['ok'] ?? false)) {
error_log("Telegram API non-ok response: " . $response);
return false;
}
return true;
}6. Типичные ошибки и чеклист перед деплоем
- Завязка на sendData везде: Помните, что
sendDataне работает для Mini Apps, открытых по прямой ссылку (t.me/bot/app) или из InlineKeyboardMarkup. Всегда закладывайте fallback на свой REST API. - Отсутствие валидации auth_date: Обязательно проверяйте поле
auth_dateизinitDataна сервере, иначе старая строкаinitDataможет быть повторно использована третьими лицами (Replay-атака). - Прямое использование window.Telegram: При серверном рендеринге (SSR / Nuxt) или при локальной разработке в браузере объект
window.Telegram.WebAppможет быть недоступен. Всегда проверяйте его существование перед вызовом методов.
Разрабатывать отказоустойчивые вебхуки и архитектуру Mini Apps помогает команда BotCreator.