Telegram Mini App на Vue 3 — це звичайний SPA всередині WebView клієнта. SDK надає тему, висоту viewport, кнопку MainButton і рядок initData. Нижче — практична схема: підключення WebApp, safe area / viewport без «прыжків», різниця між initData та initDataUnsafe, і чому потрібно перевіряти HMAC на бекенді.
Підключення SDK та composable
\nСкрипт Telegram підключають у index.html до бандла Vue. Так завжди актуальна версія клієнта, без окремого npm-пакету:
<!-- index.html -->
<head>
<script src="https://telegram.org/js/telegram-web-app.js"></script>
</head>\nComposable обертає window.Telegram.WebApp, викликає ready() / expand() і надавляє реактивні поля:
// composables/useTelegram.js
import { reactive, readonly } from 'vue'
export function useTelegram() {
const tg = window.Telegram?.WebApp
if (!tg) {
throw new Error('Telegram.WebApp is not available')
}
const state = reactive({
viewportHeight: tg.viewportHeight,
viewportStableHeight: tg.viewportStableHeight,
isExpanded: tg.isExpanded,
colorScheme: tg.colorScheme,
})
tg.ready()
tg.expand()
tg.onEvent('viewportChanged', () => {
state.viewportHeight = tg.viewportHeight
state.viewportStableHeight = tg.viewportStableHeight
state.isExpanded = tg.isExpanded
document.documentElement.style.setProperty(
'--tg-viewport-stable-height',
`${tg.viewportStableHeight}px`
)
})
// первичная установка CSS-переменной
document.documentElement.style.setProperty(
'--tg-viewport-stable-height',
`${tg.viewportStableHeight}px`
)
return {
tg,
state: readonly(state),
initData: tg.initData,
initDataUnsafe: tg.initDataUnsafe,
}
}\nУ корні прикладання:
\n// main.js
import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App)
app.mount('#app')\n// App.vue (script setup)
import { onMounted } from 'vue'
import { useTelegram } from './composables/useTelegram'
const { tg, state } = useTelegram()
onMounted(() => {
// тема из Telegram уже пробрасывается в CSS-переменные --tg-theme-*
document.body.style.backgroundColor = tg.backgroundColor || ''
})\n\nViewport і safe area
\nМобільний WebView змінює висоту при клавіатурі та закритті панелей. 100vh тут вдає собі навіку. Орієнтуйтеся на viewportStableHeight та CSS-перемінні Telegram.
/* styles.css */
html, body, #app {
margin: 0;
min-height: var(--tg-viewport-stable-height, 100vh);
}
.app-shell {
min-height: var(--tg-viewport-stable-height, 100vh);
padding-top: var(--tg-safe-area-inset-top, 0px);
padding-bottom: calc(
var(--tg-safe-area-inset-bottom, 0px) + var(--tg-content-safe-area-inset-bottom, 0px)
);
box-sizing: border-box;
}
.checkout-bar {
position: sticky;
bottom: 0;
/* не перекрывать жестом «домой» и MainButton */
padding-bottom: max(12px, var(--tg-safe-area-inset-bottom, 0px));
}\nСобыття viewportChanged оновлює висоту. Для фіксованої кнопки «Оформить» краще використовувати SDK MainButton або sticky-блок з урахуванням safe area — інакше контент виїжджає під системну панель.
\n\n\nЯкщо потрібен повноцінний екранний режим, викличте
\ntg.expand()одразу післяready(). На частині клієнтів безexpand()додаток відкривається у компактній висоті та «прыгає» при першому прокрутці.
initData проти initDataUnsafe
\nTelegram.WebApp.initData — це подписна рядок (query string): параметри користувача плюс поле hash. Його й треба відправляти на бекенд.
Telegram.WebApp.initDataUnsafe — вже розбірений об'єкт у JS. Винятково корисний для UI («Привіт, {{first_name}}»), але не є доказом, що запит прийшов від Telegram. Кожен може вставити свій user.id у власний фронтенд і постукатися у ваш API.
- \n
- UI, локальні підказки — можна читати
initDataUnsafe. \n - Заказ, баланс, особисті дані — лише після перевірки HMAC від сирого
initDataна сервері. \n
Запит із боку
\nПередавайте рядок цілим, без ручної «складання» параметрів:
\n// api.js
export async function apiPost(path, body) {
const tg = window.Telegram.WebApp
const res = await fetch(path, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Telegram-Init-Data': tg.initData,
},
body: JSON.stringify(body),
})
if (!res.ok) {
throw new Error('API ' + res.status)
}
return res.json()
}\n\nПеревірка HMAC на PHP
\nАлгоритм з документації Telegram: секрет = HMAC-SHA256 від токену бота з ключем WebAppData; рядок перевірки — пара key=value, відсортовані за ключем, без hash, з'єднані через \\n.
<?php
/**
* @return array{ok:bool, user:?array, auth_date:?int, error?:string}
*/
function validateTelegramWebAppInitData(string $initData, string $botToken, int $maxAgeSec = 86400): array
{
parse_str($initData, $data);
if (!isset($data['hash']) || !is_string($data['hash'])) {
return ['ok' => false, 'user' => null, 'auth_date' => null, 'error' => 'no hash'];
}
$hash = $data['hash'];
unset($data['hash']);
ksort($data);
$pairs = [];
foreach ($data as $k => $v) {
$pairs[] = $k . '=' . $v;
}
$dataCheckString = implode("\n", $pairs);
$secretKey = hash_hmac('sha256', $botToken, 'WebAppData', true);
$calculated = hash_hmac('sha256', $dataCheckString, $secretKey);
if (!hash_equals($calculated, $hash)) {
return ['ok' => false, 'user' => null, 'auth_date' => null, 'error' => 'bad hash'];
}
$authDate = isset($data['auth_date']) ? (int) $data['auth_date'] : 0;
if ($authDate <= 0 || (time() - $authDate) > $maxAgeSec) {
return ['ok' => false, 'user' => null, 'auth_date' => $authDate, 'error' => 'expired'];
}
$user = null;
if (!empty($data['user'])) {
$user = json_decode($data['user'], true);
}
return ['ok' => true, 'user' => is_array($user) ? $user : null, 'auth_date' => $authDate];
}
// пример в контроллере
$initData = $_SERVER['HTTP_X_TELEGRAM_INIT_DATA'] ?? '';
$result = validateTelegramWebAppInitData($initData, getenv('TELEGRAM_BOT_TOKEN'));
if (!$result['ok']) {
http_response_code(401);
echo json_encode(['error' => $result['error']]);
exit;
}
$userId = (int) ($result['user']['id'] ?? 0);\nБез цієї перевірки будь-який клієнт може підробити замовлення «від імені» іншого користувача. Срок auth_date обмежує replay старих рядків.
sendData — окрема пастка
\ntg.sendData() працює лише якщо Mini App відкритий з кнопки клавіатури (reply keyboard). З Menu Button, inline-кнопки або пряму посилання метод безздатний. Для замовлень та платежів надішліть дані на свій API + бот відповідає через Bot API.
Короткий чеклист
\n- \n
- SDK у
index.html, composable зready/expand/viewportChanged. \n - Висота і відступи — через viewport/safe-area перемінні, а не через «чистий»
100vh. \n - UI може дивиться у
initDataUnsafe; сервер довіряє лише HMAC відinitData. \n - Токен бота лише на бекенді; на фронте його бути не має. \n
Потрібна допомога з зв'язком Vue Mini App + PHP-валидація під ваш бот — можна набрати схему на botservice.biz.
"}