Telegram Mini App на Vue 3: WebApp SDK, теми, sendData та валідація initData на PHP

Розробка Telegram Mini Apps (TWA) на сучасному стеку 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.

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

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