Створіть віджет Telegram за допомогою React: перевірте initData, @twa-dev/sdk та MainButton

Створіть віджет Telegram за допомогою React: перевірте initData, @twa-dev/sdk та MainButton

Мини-приложение Telegram - це веб-сторінка, яка працює в браузері програми Telegram на iOS, Android та комп 'ютері. З точки зору React, це просто звичайний SPA; відмінності - міст JS (window.Telegram.WebApp), метод автентифікації користувача (підпис initData на бекенді), і нативні елементи інтерфейсу (MainButton, BackButton, змінні теми).

У цьому уроці ми створимо невеликий додаток VITE + React Mini, який:

1. Читання window.Telegram.WebApp (с) @twa-dev/sdk як набрана обгортка). 2. Надсилає initData на сервер PHP, який перевіряє підпис HMAC-SHA-256 щодо маркера бота. 3. Проводи MainButton до обробника React, щоб нативна нижня кнопка керувала станом React.

Ми не будемо висвітлювати публікацію в Telegram, tapps.co каталогу, або BackButton історії — саме те, що обіцяє заголовок.

1. Запустити проєкт

npm create vite@latest miniapp-react -- --template react-ts
cd miniapp-react
npm install @twa-dev/sdk
npm run dev

Для локального тестування в Telegram потрібні https та загальнодоступна URL-адреса; npm run dev де ngrok http 5173 це звичайна установка. Офіційні документи пояснюють тунель.

2. window.Telegram.WebApp АБО @twa-dev/sdk

Коли Telegram відкриває вашу URL-адресу, він вводить скрипт, який показує глобальні:

interface TelegramWebApp {
initData: string; // raw query string, used for backend auth
initDataUnsafe: WebAppUser; // already-parsed user object, UNTRUSTED
ready(): void; // tell Telegram the UI is mounted
expand(): void; // grow to full height
close(): void;
MainButton: {
text: string;
show(): void;
hide(): void;
onClick(cb: () => void): void;
offClick(cb: () => void): void;
setText(t: string): void;
enable(): void;
disable(): void;
};
colorScheme: 'light' | 'dark';
themeParams: Record<string, string>;
}

Ці дві властивості схожі, але не взаємозамінні:

- initData - необроблений рядок запиту (auth_date=...&user=...&hash=...). Це те, що ви надсилаєте на сервер. Сервер перераховує HMAC і порівнює його з хеші використання маркера бота як секрету. - initDataUnsafe.user є об 'єктом, який вже був проаналізований. Зручно, але не варто довіряти жодному полю в ньому для авторизації. Будь-хто може створити сторінку, яка встановлює window.Telegram = { WebApp: { initDataUnsafe: { user: { id: 42 } } } }. Розглядайте це як підказку UX, а не ідентифікацію.

Використання @twa-dev/sdk дає вам той самий API з типами TypeScript і невеликою межею пакета, тому ваш код React не досягає window безпосередньо

import WebApp from '@twa-dev/sdk';

WebApp.ready();
WebApp.expand();
console.log(WebApp.initData); // raw string
console.log(WebApp.initDataUnsafe); // typed object

Для решти підручника ми використовуємо SDK; перехід на глобальний просто видаляє імпорт.

3. Перевірка initData на бекенді

Це крок, який фактично автентифікує користувача. Договір задокументований Telegram: приймати кожну initData поле, крім хеші, побудувати data-check-string колегіальний значення ключа рядки об 'єднані , nОбчислити HMAC-SHA-256(data-check-string, "WebAppData") за допомогою ключа, який SHA-256(bot_token), і порівняти з хеші за допомогою безпечного за часом компаратора. Відхилити все, що старше ~5 хвилин, перевіривши Дата авторизації.

Мінімальна кінцева точка PHP:

<?php
// public/auth.php
declare(strict_types=1);

header('Content-Type: application/json');

$raw = file_get_contents('php://input') ?: '';
$payload = json_decode($raw, true);
if (!is_array($payload) || !isset($payload['initData'])) {
http_response_code(400);
echo json_encode(['error' => 'initData missing']);
return;
}

$botToken = getenv('BOT_TOKEN'); // never hardcode
$secret = hash('sha256', $botToken, true);

parse_str($payload['initData'], $data);
if (!isset($data['hash'], $data['auth_date'], $data['user'])) {
http_response_code(400);
echo json_encode(['error' => 'malformed initData']);
return;
}

$check = [];
foreach ($data as $k => $v) {
if ($k === 'hash') continue;
$check[] = $k . '=' . $v;
}
$checkString = implode("\n", $check);

$calc = hash_hmac('sha256', $checkString, $secret);
if (!hash_equals($calc, (string) $data['hash'])) {
http_response_code(401);
echo json_encode(['error' => 'bad signature']);
return;
}

if (time() - (int) $data['auth_date'] > 300) {
http_response_code(401);
echo json_encode(['error' => 'initData expired']);
return;
}

$user = json_decode($data['user'], true);
$tid = is_array($user) && isset($user['id']) ? (int) $user['id'] : 0;

// At this point you have a verified telegram_id.
// Bind it to your local session/JWT and respond.
echo json_encode(['ok' => true, 'telegram_id' => $tid]);

Ключові моменти parse_str обробляє декодування URL-адрес, hash_equals є тимчасовим компаратором, маркер бота залишається у змінній env, і Дата авторизації дає вам вікно повторного відтворення. Попередня стаття DEV.to про перевірку HMAC описує той самий алгоритм більш детально — розглядайте його як супутника на стороні React.

4. Сторона реагування: виклик бекенду

Тримайте дзвінок невеликим і набраним. Нам потрібно лише initData; initDataUnsafe це підказка UX, яку ми показуємо, а не джерело правди.

// src/api.ts
export type AuthUser = { id: number; first_name: string; username?: string };

export async function authWithTelegram(initData: string): Promise<AuthUser> {
const res = await fetch('/auth.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ initData }),
});
if (!res.ok) throw new Error(`auth failed: ${res.status}`);
const json = await res.json();
return json.user as AuthUser;
}

Викличте його один раз, коли компонент буде встановлено, після WebApp.ready():

// src/App.tsx
import { useEffect, useState } from 'react';
import WebApp from '@twa-dev/sdk';
import { authWithTelegram, AuthUser } from './api';

export default function App() {
const [user, setUser] = useState<AuthUser | null>(null);
const [error, setError] = useState<string | null>(null);

useEffect(() => {
WebApp.ready();
authWithTelegram(WebApp.initData)
.then(setUser)
.catch((e) => setError(String(e)));
}, []);

if (error) return <p>Auth error: {error}</p>;
if (!user) return <p>Loading…</p>;
return <p>Hello, {user.first_name} ({user.id})</p>;
}

WebApp.initDataUnsafe.user доступний негайно, тому ви можете показати ім 'я, поки мережевий запит знаходиться в польоті — просто пам' ятайте, що будь-яке поле, що виходить з нього, не є надійним, поки бекенд не підтвердить.

5-провідний: MainButton до реактивного стану

MainButton постійна кнопка внизу віджета. Два правила зберігають розсудливість:

- Налаштуйте його всередині ефектів React, а не під час рендерингу. - Використовуйте offClick в функції очищення, щоб useEffect повторні запуски не складають обробники.

import { useEffect, useState } from 'react';
import WebApp from '@twa-dev/sdk';

export function ConfirmButton({ onConfirm }: { onConfirm: () => void }) {
const [busy, setBusy] = useState(false);

useEffect(() => {
const mb = WebApp.MainButton;
mb.text = 'CONFIRM';
mb.show();
const handler = async () => {
if (busy) return;
setBusy(true);
mb.showProgress(true);
try {
await onConfirm();
WebApp.close();
} finally {
mb.showProgress(false);
setBusy(false);
}
};
mb.onClick(handler);
return () => {
mb.offClick(handler);
mb.hide();
};
}, [onConfirm, busy]);

return null;
}

Ось що потрібно знати:

- MainButton.showProgress(true) показує невизначений ротатор; підключіть його до Відключити якщо ви також хочете ігнорувати подальші кліки. - WebApp.close() це єдиний спосіб закрити віджет з JS. Не намагайтеся вийти; браузер у додатку просто знову відкриє вас. - MainButton.setText приймає до 64 видимих символів; довші рядки обрізаються.

6. Виробничі примітки (необов 'язково)

- Використовуйте відносний шлях для бекенду та обслуговуйте SPA + API з того ж джерела, інакше CSP Telegram заблокує вибірку. - Pin a Проти? рядок запиту або хеш збірки у вашому HTML-коді, щоб запобігти кешуванню застарілої оболонки Telegram після розгортання. - Кеш перевірено Ідентифікатор Telegram в файлі cookie HttpOnly або недовговічному файлі JWT; не зберігайте його в localStorage якщо ви можете уникнути цього. - Якщо ваш гаджет запускається з startapp параметр або Перейти глибоке посилання, читання WebApp.initDataUnsafe.start_param після автентифікації та надсилання на ньому — але все одно закрийте бік сервера дій. - Для аналітики підраховуйте події на бекенді, набрані перевіреною Ідентифікатор Telegram. WebApp.initDataUnsafe поля підходять для послідовності "яку кнопку вони натиснули", а не для виставлення рахунків.

Якщо ви хочете пропустити шаблон і надіслати віджет з Telegram-ботом, BotCreator надсилає наскрізну роботу продукту — бота, віджет та бекенд. Каталог Telegram Bot API, який вони ведуть за адресою botservice.biz/telegram-bot-api, є хорошим супутником під час читання технічних документів.

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

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