В последнее время мини-приложения для Telegram стремительно набирают популярность, открывая новые возможности для бизнеса. В этой статье мы рассмотрим процесс создания мини-приложений для Telegram, начиная с создания бота, и заканчивая развертыванием приложения на сервере.
Telegram
Что такое Telegram Mini Apps?
По сути, мини-аппы в Telegram - это обычные веб-приложения, у которых есть доступ к API Telegram, позволяющее более удобно взаимодействовать с пользователем и его устройством. Когда вы открываете такое приложение, то по сути вы просто открываете сайт, который находится в WebView Telegram.
Сам телеграм предоставляет нам часть API через глобальный объект window
, он просто подмешивает в него новый объект — Telegram
, через который и внедряются новые возможности.
Само API находится в объекте window.Telegram.WebApp
, среди разной мета-информации и кучи логических флагов, там есть некоторые интересные API:
Модуль | Ключ | Описание |
---|---|---|
Тема | ThemeParams | Объект с текущими настройками темы в приложении Telegram |
Кнопка “Назад” | BackButton | Объект для управления кнопкой “Назад” в заголовке |
“Главная” кнопка | MainButton | Объект для управления главной кнопкой внизу |
“Вторичная” кнопка | SecondaryButton | Объект для управления второстепенной кнопкой внизу |
Кнопка настроек | SettingsButton | Объект для управления пунктом настроек в контекстном меню |
Вибрация | HapticFeedback | Объект для управления тактильной обратной связью |
Облачное хранилище | CloudStorage | Объект для управления облачным хранилищем |
Биометрия | Biometrics | Объект для управления биометрией на устройстве |
Сенсоры | Accelerometer | Объект для доступа к акселерометру устройства |
Гироскоп | Gyroscope | Объект для доступа к гироскопу устройства |
Геолокация | GeoLocation | Объект для управления местоположением |
Хранилище | Storage | Объект для хранения данных в локальном хранилище |
Безопасное хранилище | SafeStorage | Объект для хранения данных в защищенном хранилище |
SDK
SDK (Software Development Kit) — набор библиотек, инструментов и документации, которые помогают писать решения заточенные под определенные платформы.
У нативного API мини-приложений есть несколько минусов:
- Нет поддержки реактивности
- Некоторый функционал несгруппирован в отдельный логические модули
- Нет поддержки проверки доступности API
- Нет обработки ошибок в случае отсутствия доступа к API
Если с первым еще можно справиться с помощью тех же сигналов (nanostores, Preact Signals, zustand, Alien Signals) или кастомной обработки и хранения сигналов в виде связывания стейта с помощью хуков/композиций, то со следующими пунктами дела обстоят не так хорошо.
Чтобы избежать ненужного бойлерплейта с проверкой доступности разных API, мы можем использовать библиотеку @telegram-apps/sdk, которая предоставляет удобный и безопасный способ работы с API мини-приложений, невелируя кучу бойлерплейта, который нам бы пришлось писать ручками.
Да, у некоторых модулей данная проверка есть, например emojiStatusFailed
, fullscreenFailed
, accelerometerFailed
, однако, у того же модуля BackButton
данной поддержки нет, хотя сама кнопка может не функционировать в некоторых клиентах.
Для того чтобы проверить есть ли у нас API для того же BackButton
, нам бы пришлось писать что-то вроде:
function isBackButtonSupported(): boolean {
return typeof window?.Telegram?.WebApp?.BackButton?.show === 'function';
}
Как общается WebView и клиент Telegram?
Ранее я говорил о том, что мини-приложения работают на основе веб-технологий, однако, я не говорил каким именно образом сообщения отправляются в клиент Telegram.
Сама технология отправки сообщений между WebView и клиентом, который не является браузером — не является новой. Более того, я нашел вопрос датируемый аж 2014 годом “Как WebView может общаться с нативным кодом клиента” на StackOverflow. Проблема старая, как мир, но решения у всех разные.
Старое API
Ранее под разные платформы отправка сообщений была реализована разными способами, об этом прямо указано в документации @telegram-apps/sdk, однако, насколько я понял, там устаревшие данные.
Хоть само API и устаревшее, оно до сих пор работает и для общего понимания важно понимать как оно работает (ведь под капотом у нового API всё та же логика), для удобства я составил табличку:
Платформа | Метод отправки сообщения | Объяснение |
---|---|---|
Web | window.parent.postMessage | В веб-версии Telegram мини-приложения открываются внутри iframe , поэтому обмен сообщениями происходит между двумя изолированными пространствами JS. |
Windows Phone | window.external.notify | В данном случае в роли WebView выступает Internet Explorer, поэтому и API тут устаревшее. Если посмотреть на MDN сам объект external , то некоторое API из него доступно только в IE |
Android/iOS/Desktop | window.TelegramWebviewProxy.postEvent | В данном случае разработчики решили сделать общий интерфейс для современных платформ и унифицировали интерфейс отправки сообщений для iOS WebView, Android WebView и Desktop WebView. |
Сам список ивентов доступных для отправки я перечислять не вижу смысла, так как они предоставлены в официальной документации Telegram Web Events, однако, имеет смысл показать как обращаться с API приведенными выше:
// Web Client
window.parent.postMessage(
JSON.stringify({
eventType: 'web_app_set_background_color',
eventData: {
color: '#fefefe'
}
})
);
// Windows Phone client
window.external.notify(
JSON.stringify({
eventType: 'web_app_set_background_color',
eventData: {
color: '#fefefe'
}
})
);
// Desktop / Android / iOS Client
window.TelegramWebviewProxy.postEvent('web_app_set_background_color', {
color: '#fefefe'
});
После того как мы научились отправлять ивенты клиенту Telegram, неплохо бы ещё научиться получать ответы от клиента, для этого мы можем использовать следующие методы:
window.addEventListener('message', ...)
— дляiframe
;window.TelegramGameProxy.receiveEvent
— для Telegram Desktop;window.Telegram.WebView.receiveEvent
— для iOS / Android;window.TelegramGameProxy_receiveEvent
— для Windows Phone;
Изначально API для общения с клиентом Telegram было реализовано для Telegram Gaming Platform, поэтому некоторое API тянется оттуда до сих пор
Новое API
В Mini Apps API есть возможность ловить / отправлять ивенты и без всех этих костылей, однако, для сравнения можете глянуть на список поддерживаемых ивентов внутри API Mini Apps (тык) и API Web Events (тык), последних сильно больше.
В документации Telegram есть целых три списка ивентов:
- На странице Web Events
- На странице мини-аппов в секции ботов (
Telegram.WebView.receiveEvent
) - На стринце мини-аппов (
Telegram.WebApp.onEvent
)
В первом случае предоставляется список ивентов для работы с Telegram Games, приложений с возможностью оплаты и Web Mini Apps, то есть это полный список ивентов, что есть в Web Events API, который будет работать не только в мини-приложениях.
Во втором случае предоставляется способ обработки ивентов из первого списка, но специфично для мини-приложений, именно поэтому, почти во всех ивентах убран префикс web_app_
, данное API является частью легаси, однако оно до сих пор поддерживается. Также, обратите внимание, что в данном API используется объект Telegram.WebView
, а не Telegram.WebApp
.
В третьем случае предоставляется способ для общения с клиентом, который специфичен для мини-приложений, у которых подключен скрипт telegram-web-app.js
(то есть, для всех современных мини-приложений), данный способ предпочтителен.
Под капотом у нового API используются всё те же принципы, а у некоторых окружений и вовсе не поменялась логика, к примеру, у той же обработки событий внутри iframe
логика абсолютно идентичная.
Бутстраппинг приложения
Для начала стоит сказать, что у @telegram-apps/sdk
есть уже готовые темплейты для приложений на ванильном JS, Vue, React и Solid, для того чтобы быстро забутстрапить приложение, можно ввести следующую команду:
npx @telegram-apps/create-mini-app@latest
Если же мы хотим сделать всё сами, то нам нужно выбрать один из SDK:
- Vanilla: https://docs.telegram-mini-apps.com/packages/telegram-apps-sdk/3-x
- React: https://docs.telegram-mini-apps.com/packages/telegram-apps-sdk-react/3-x
- Solid: https://docs.telegram-mini-apps.com/packages/telegram-apps-sdk-solid/3-x
- Vue: https://docs.telegram-mini-apps.com/packages/telegram-apps-sdk-vue/2-x
- Svelte: https://docs.telegram-mini-apps.com/packages/telegram-apps-sdk-svelte/2-x
Давайте быстренько забутстрапим приложение на React с помощью vite
:
npm create vite@latest tma-example -- --template react-swc-ts
cd tma-example
npm i # Или npm install
После того как мы создали “костяк” для нашего приложения – мы можем приступить к скачиванию SDK.
Я привык работать с SDK без привязки к какому-либо фреймворку и реализовывать реактивность самостоятельно, однако, для упрощения давайте установим SDK с интеграцией React:
npm i @telegram-apps/sdk-react
Отличие @telegram-apps/sdk-react
от обычного @telegram-apps/sdk
в том, что в данной версии SDK сразу идёт хук useSignal
для удобной работы с сигналами, которые
используются под капотом для реализации реактивности.
Чистка проекта
Для удобства, перед тем как начать работать с библиотекой - давайте почистим проект от лишних файлов:
rm -rf src/{assets,index.css,App.css,App.tsx}
Инициализация API
Для того чтобы наша интеграция с web-app.js
(библиотека, которую предоставляет Telegram для мини-приложений) заработала, нам необходимо инициализировать API, мы можем сделать это в main.tsx
:
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { init } from "@telegram-apps/sdk-react";
import App from './App.tsx'
init();
createRoot(document.getElementById("root")!).render(
<StrictMode>
<App></App>
</StrictMode>,
);
Теперь давайте создадим компонент App.tsx
(обратите внимание, что до этого мы удаляли этот файл) и напишем в нём нечто следующее:
export default function App() {
const nickname = '<empty>';
return <div>Hello, telegram user with a nickname {nickname}</div>;
}
На текущий момент, если мы откроем наше приложение, то увидим фразу “Hello, telegram user with a nickname <empty>”, давайте сделаем так, чтобы реальный пользовательский ник выводился:
import { useLaunchParams } from '@telegram-apps/sdk-react';
export default function App() {
const launchParams = useLaunchParams();
const nickname = '<empty>';
return <div>Hello, telegram user with a nickname {nickname}</div>;
}
После использования данного хука, нам сразу же вылетит ошибка, подобная следующей:
Uncaught LaunchParamsRetrieveError: Unable to retrieve launch parameters from any known source. Perhaps, you have opened your app outside Telegram?
📖 Refer to docs for more information:
https://docs.telegram-mini-apps.com/packages/telegram-apps-bridge/environment
Collected errors:
Source: window.location.href / Invalid key: Expected "tgWebAppPlatform" but received undefined
Source: performance navigation entries / Invalid key: Expected "tgWebAppPlatform" but received undefined
Source: local storage / Source is empty
at ee2 (@telegram-apps_sdk-react.js?v=aa678530:2168:9)
at ke2 (@telegram-apps_sdk-react.js?v=aa678530:2171:16)
at Tn (@telegram-apps_sdk-react.js?v=aa678530:2429:55)
at Gl (@telegram-apps_sdk-react.js?v=aa678530:4350:3)
at main.tsx:5:1
Всё дело в том, что мы пытаемся достучаться до API Telegram из браузера, где данное API недоступно. Для того, чтобы это исправить мы должны сделать две вещи:
- Поставить заглушку, на случай, если пользователь открывает наше приложение не из Telegram;
- Мокнуть окружение Telegram, для того чтобы тестировать приложение вне Telegram;
Контроль поведения приложения
Как уже говорилось выше, мы должны предотвратить доступ к приложению вне Telegram. Для этого мы можем использовать метод isTMA
из @telegram-apps/sdk-react
, а также обработать ошибку, которую нам может кинуть метод init
:
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { init } from "@telegram-apps/sdk-react";
import App from "./App.tsx";
import { AppWrapper } from "./components/AppWrapper.tsx";
try {
init();
} catch {
console.warn("Can't initialize tma, skipping...");
}
createRoot(document.getElementById("root")!).render(
<StrictMode>
<AppWrapper>
<App />
</AppWrapper>
</StrictMode>,
);
import { useLaunchParams, isTMA } from "@telegram-apps/sdk-react";
export const AppWrapper: FC<PropsWithChildren> = ({ children }) => {
const isTelegramApp = isTMA();
return (
<div>
{isTelegramApp ? children : <div>Откройте приложение в Telegram</div>}
</div>
);
};
Данный компонент-обёртка позволяет нам контролировать поведение приложения в зависимости от того, запущено оно в Telegram или нет. Если приложение запущено в Telegram, то он отображает дочерние элементы, иначе отображает сообщение о том, что приложение недоступно.
Компонент-обёртка вверху показан чисто для демонстрационных целей, вам не обязательно полностью ограничивать доступ к приложению, но вам нужно исключить использование Mini Apps API вне окружения Telegram.
Подмена окружения
Для того чтобы мы разрабатывали мини-приложение с комфортом, @telegram-apps/sdk-react
предоставляет
функцию mockTelegramEnv
, которая добавляет в окружение браузера фейковые реализации Mini Apps API.
import { mockTelegramEnv, emitEvent } from '@telegram-apps/sdk-react';
const noInsets = {
left: 0,
top: 0,
bottom: 0,
right: 0
} as const;
const themeParams = {
accent_text_color: '#6ab2f2',
bg_color: '#17212b',
button_color: '#5288c1',
button_text_color: '#ffffff',
destructive_text_color: '#ec3942',
header_bg_color: '#17212b',
hint_color: '#708499',
link_color: '#6ab3f3',
secondary_bg_color: '#232e3c',
section_bg_color: '#17212b',
section_header_text_color: '#6ab3f3',
subtitle_text_color: '#708499',
text_color: '#f5f5f5'
} as const;
mockTelegramEnv({
launchParams: {
tgWebAppThemeParams: themeParams,
tgWebAppData: new URLSearchParams([
[
'user',
JSON.stringify({
id: 1,
first_name: 'Pavel'
})
],
['hash', ''],
['signature', ''],
['auth_date', Date.now().toString()]
]),
tgWebAppStartParam: 'debug',
tgWebAppVersion: '8',
tgWebAppPlatform: 'tdesktop'
},
onEvent(e) {
if (e[0] === 'web_app_request_theme') {
return emitEvent('theme_changed', { theme_params: themeParams });
}
if (e[0] === 'web_app_request_viewport') {
return emitEvent('viewport_changed', {
height: window.innerHeight,
width: window.innerWidth,
is_expanded: true,
is_state_stable: true
});
}
if (e[0] === 'web_app_request_content_safe_area') {
return emitEvent('content_safe_area_changed', noInsets);
}
if (e[0] === 'web_app_request_safe_area') {
return emitEvent('safe_area_changed', noInsets);
}
}
});
Импортировать данный файл мы можем следующим образом:
if (import.meta.env.DEV) {
import('./mock.ts');
}
После данного импорта функция useLaunchParams
отдаст нам тестовые данные:
import { useLaunchParams } from '@telegram-apps/sdk-react';
export default function App() {
const launchParams = useLaunchParams();
const nickname = launchParams.tgWebAppData?.user?.username || '<empty>';
return <div>Hello, telegram user with a nickname {nickname}</div>;
}
Использование API
У любого модуля из Mini Apps API есть несколько состояний:
isSupported
- Поддерживается ли API в текущем контекстеisAvailable
- Доступна ли функция из модуля в текущем контекстеisMounted
- Загружен ли модуль
Мы можем использовать один из двух первых вариантов для проверки можно ли использовать API, но второй метод более предпочтителен, так как он не просто проверяет поддерживается ли определенный модуль, но и проверяет что у клиента есть функция, которая нам нужна.
Для примера мы возьмем одно из самых частоиспользуемых API - backButton
. Этот модуль позволяет отображать кнопку “Назад” в верхней части экрана приложения.
Перед тем как начать использовать модуль - нужно его монтировать, для этого у backButton
(да и почти у каждого модуля) есть метод mount
, однако сам метод есть не во всех клиентах. Для того чтобы проверить есть ли в клиенте данный метод, мы можем использовать подметод isSupported
или isAvailable
:
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import { init, backButton } from '@telegram-apps/sdk-react';
import App from './App.tsx';
import { AppWrapper } from './components/AppWrapper.tsx';
try {
init();
if (backButton.mount.isAvailable()) {
backButton.mount();
}
} catch {
console.warn("Can't initialize tma, skipping...");
}
createRoot(document.getElementById('root')!).render(
<StrictMode>
<AppWrapper>
<App />
</AppWrapper>
</StrictMode>
);
После этого, мы можем использовать модуль backButton
для отображения кнопки “Назад” в верхней части экрана приложения:
import { useLaunchParams, backButton } from "@telegram-apps/sdk-react";
export default function App() {
const launchParams = useLaunchParams();
const nickname = launchParams.tgWebAppData?.user?.username || "<empty>";
if (backButton.isMounted()) {
backButton.show();
}
return <div>Hello, telegram user with a nickname {nickname}</div>;
}
Все остальные модули используются точно так же.
Обработка пользовательских событий
Мини-приложения могут подписываться на события, поступающие от клиента Telegram. Это необходимо для корректного реагирования на изменение темы, размера окна, состояния клавиатуры и т.д.
Для подписки на события используется метод onEvent
, который предоставляется SDK:
import { onEvent } from '@telegram-apps/sdk-react';
onEvent('theme_changed', (event) => {
console.log('Новая тема:', event.theme_params);
});
onEvent('viewport_changed', (event) => {
console.log('Изменён вьюпорт:', event);
});
Метод onEvent возвращает функцию unsubscribe
, которую необходимо вызвать при размонтировании компонента, если подписка была установлена внутри React-компонента.
Если же мы будем рассматривать обработку событий в компоненте, то она может выглядеть следующим образом:
import { useEffect } from 'react';
import { onEvent } from '@telegram-apps/sdk-react';
export const ViewportLogger = () => {
useEffect(() => {
const unsub = onEvent('viewport_changed', (event) => {
console.log('Размер экрана изменился:', event);
});
return unsub;
}, []);
return null;
};
Сами ивенты, как уже было сказано до этого, есть в официальной документации Telegram Mini Apps SDK.
Заключение
Мини-приложения Telegram это мощный инструмент для создания интерактивных веб-приложений.
Они позволяют использовать нативные функции устройства, а также плотно интегрироваться с Telegram. Настройка бота, работа с API и тестирование требуют внимания к деталям, но результат оправдывает усилия: вы получаете доступ к огромной аудитории Telegram с минимальными затратами на инфраструктуру.