Telegram Mini App на Vue 3: інтеграція SDK, керування viewport, sendData та PHP-валідація initData

Інтеграція вебдодатків безпосередньо в інтерфейс месенджера Telegram Mini Apps (TMA) відкриває величезні можливості для бізнесу. Однак при розробці на сучасному фронтенд-стеку, наприклад Vue 3, розробники часто стикаються з неочевидними проблемами: стрибаючий viewport при відкритті клавіатури, некоректна обробка колірних схем Telegram та вразливості при валідації даних користувача на бекенді.

У цій статті ми розберемо, как правильно підключити Telegram WebApp SDK у додаток на Vue 3 (з Composition API), як стабілізувати область перегляду (viewport), коли і як використовувати метод sendData, та як реалізувати надійну криптографічну перевірку отриманих даних на стороні PHP-сервера.

Підключення WebApp SDK у Vue 3 та управління темою оформлення

Для інтеграції Mini App з нативним клієнтом Telegram необхідно підключити стандартний скрипт. Найнадійніший спосіб — додати CDN-скрипт у файл index.html вашого Vue-проєкту. Це гарантує, що глобальний об'єкт window.Telegram.WebApp буде доступний одразу при ініціалізації додатка.

<!-- index.html -->
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<script src="https://telegram.org/js/telegram-web-app.js"></script>
<title>My Telegram Mini App</title>
</head>
<body>
<div id="app"></div>
</body>
</html>

Усередині Vue 3 ми обгортаємо роботу з SDK у компонент або composable-функцію. Важливо правильно обробити колірну схему Telegram, яка передається через об'єкт themeParams. Це дозволить інтерфейсу додатка безшовно мімікрувати під темну або світлу тему месенджера користувача.

<!-- App.vue -->
<template>
<div class="tma-wrapper" :style="themeVariables">
<header class="tma-header">
<p>Привет, {{ username }}!</p>
</header>
<main class="tma-content">
<slot></slot>
<button @click="sendPayload" class="tma-btn">Отправить заказ</button>
</main>
</div>
</template>

<script setup>
import { ref, onMounted, computed } from 'vue';

const username = ref('Гость');
const themeParams = ref({});

onMounted(() => {
const tg = window.Telegram?.WebApp;
if (tg) {
tg.ready();
tg.expand(); // Разворачиваем приложение на максимум

username.value = tg.initDataUnsafe?.user?.first_name || 'Пользователь';
themeParams.value = tg.themeParams || {};

// Следим за изменениями темы на лету
tg.onEvent('themeChanged', () => {
themeParams.value = tg.themeParams;
});
}
});

const themeVariables = computed(() => {
const colors = themeParams.value;
return {
'--bg-color': colors.bg_color || '#ffffff',
'--text-color': colors.text_color || '#000000',
'--hint-color': colors.hint_color || '#999999',
'--link-color': colors.link_color || '#2481cc',
'--button-color': colors.button_color || '#2481cc',
'--button-text-color': colors.button_text_color || '#ffffff'
};
});
</script>

Управління viewport та запобігання згортанню

Однією з головних проблем при розробці TMA є поведінка області перегляду (viewport) на мобільних пристроях, особливо при фокусе на текстових полях введення. Коли з'являється екранна клавіатура, розмір вікна зменшується, що може призводити до небажаного зсуву елементів або спроби згорнути Mini App жестом swipe-down.

Щоб зафіксувати висоту та запобігти випадковому закриттю додатка користувачем, необхідно використовувати методи управління viewport:

  • Telegram.WebApp.expand() — примусово розгортає вікно додатка на максимально доступну висоту.
  • Telegram.WebApp.enableClosingConfirmation() — вмикає підтвердження закриття. Якщо користувач випадково потягне екран вниз, Telegram запитає, чи дійсно він хоче закрити додаток.
  • Відстеження події viewportChanged з параметром isStateStable дозволяє вчасно адаптувати інтерфейс (наприклад, приховувати фіксовані футери при відкритті клавіатури).

Рекомендується викликати tg.enableClosingConfirmation() одразу при монтуванні головного компонента додатка.

Відправка даних боту: sendData проти HTTP API

Для відправки даних з Mini App назад у чат з ботом існує метод Telegram.WebApp.sendData(data). Однак у нього є критичне обмеження, про яке часто забувають розробники-початківці:

Важливе обмеження: Метод sendData працює тільки в тому випадку, якщо Mini App був запущений через звичайну кнопку клавіатури бота (клавіша типу KeyboardButton з параметром web_app). Якщо додаток відкритий через Inline-кнопку в повідомленні, через меню вкладень (Attachment Menu) або за прямим посиланням, виклик sendData не спрацює.

Якщо ваш додаток запущено через Keyboard Button, виклик tg.sendData(JSON.stringify(payload)) миттєво закриє Mini App і відправить текстове повідомлення від імені користувача в чат з ботом. Бот отримає цю подію в об'єкті оновлення Update у полі message.web_app_data.

У реальних проєктах (інтернет-магазини, CRM, запись на услуги) набагато надійніше використовувати стандартні HTTP-запити (через Axios або Fetch) на ваш власний бекенд. Для авторизації запиту в заголовках передається рядок window.Telegram.WebApp.initData. Бекенд перевіряє його валідність і самостійно відправляє повідомлення користувачеві через Bot API.

Безпечна валідація initData на PHP

Ніколи не довіряйте даним, отриманим безпосередньо з фронтенду (включаючи initDataUnsafe). Зловмисник може легко перехопити трафік або підмінити значення полів у браузері. Для верифікації параметрів необхідно використовувати рядок initData і перевірити підпис (хеш) за допомогою секретного ключа, створеного на основі токена вашого бота.

Нижче представлений готовий, безпечний клас на чистому PHP для валідації initData з використанням криптографічного алгоритму HMAC-SHA-256 та захистом від атак за часом (timing-safe comparison).

<?php

declare(strict_types=1);

class TelegramInitDataValidator
{
private string $botToken;
private int $expireSeconds;

public function __construct(string $botToken, int $expireSeconds = 86400)
{
$this.botToken = $botToken;
$this.expireSeconds = $expireSeconds;
}

/**
* Валидирует строку initData и возвращает массив параметров в случае успеха.
*
* @param string $initData Строка запроса из window.Telegram.WebApp.initData
* @return array|null Данные авторизации или null в случае невалидности
*/
public function validate(string $initData): ?array
{
parse_str($initData, $params);

if (!isset($params['hash']) || !isset($params['auth_date'])) {
return null;
}

// 1. Проверяем актуальность данных по времени (защита от повторного использования старых сессий)
if (time() - (int)$params['auth_date'] > $this.expireSeconds) {
return null;
}

$receivedHash = $params['hash'];
unset($params['hash']);

// 2. Сортируем параметры в алфавитном порядке
ksort($params);

// 3. Формируем строку проверки
$dataCheckArr = [];
foreach ($params as $key => $value) {
$dataCheckArr[] = "{$key}={$value}";
}
$dataCheckString = implode("\n", $dataCheckArr);

// 4. Генерируем секретный ключ на основе токена бота
$secretKey = hash_hmac('sha256', $this.botToken, 'WebAppData', true);

// 5. Вычисляем эталонный хэш
$calculatedHash = hash_hmac('sha256', $dataCheckString, $secretKey);

// 6. Сравниваем хэши безопасным методом hash_equals
if (!hash_equals($receivedHash, $calculatedHash)) {
return null;
}

// Декодируем вложенный JSON пользователя для удобства
if (isset($params['user'])) {
$params['user'] = json_decode($params['user'], true);
}

return $params;
}
}

Приклад використання класу у вашому PHP-контролері:

<?php

require_once 'TelegramInitDataValidator.php';

$botToken = getenv('TELEGRAM_BOT_TOKEN') ?: 'YOUR_BOT_TOKEN';
$headers = getallheaders();
$authHeader = $headers['Authorization'] ?? '';

if (preg_match('/^Bearer\s+(.+)$/i', $authHeader, $matches)) {
$initData = $matches[1];
} else {
http_response_code(401);
echo json_encode(['error' => 'Missing authorization header']);
exit;
}

$validator = new TelegramInitDataValidator($botToken);
$userData = $validator->validate($initData);

if ($userData === null) {
http_response_code(403);
echo json_encode(['error' => 'Invalid or expired Telegram initData']);
exit;
}

// Данные успешно проверены, можно доверять $userData['user']['id']
header('Content-Type: application/json');
echo json_encode([
'status' => 'success',
'user_id' => $userData['user']['id'] ?? null,
'username' => $userData['user']['username'] ?? null
]);

Типові помилки при інтеграції Vue та Telegram WebApp

При розробці Mini Apps розробники найчастіше припускаються таких архітектурних помилок:

  1. Використання initDataUnsafe для бізнес-логики. Запам'ятайте: цей об'єкт призначений виключно для відображення інтерфейсу у фронтенді (наприклад, показати аватарку або ім'я користувача). Будь-які операції з балансом, замовленнями або правами доступу повинні проходити перевірку initData на бекенді.
  2. Ігнорування параметра auth_date. Без перевірки часу генерації даних зловмисник може перехопити валідний рядок initData один раз і використовувати його нескінченно для виконання несанкціонованих дій від імені користувача. Обмежуйте час життя сесії (рекомендується від 1 до 24 годин).
  3. Неправильне вирівнювання під клавіатуру. Якщо ви використовуєте у Vue жорстко зафіксовані елементи (position: fixed), обов'язково тестуйте їхню поведінку на реальних мобільних пристроях iOS та Android при відкритій клавіатурі.

Якщо вам потрібна професійна розробка складних інтеграцій та Telegram Mini Apps, команда BotCreator допоможе реалізувати проєкт будь-якої складності на сайті botservice.biz.

Нові статті — у Telegram

Розбираємо, що автоматизувати в бізнесі та як це працює на практиці. Без спаму.