Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Главная

telegram-webapp-sdk — это типобезопасная и эргономичная обёртка на Rust/WASM над JavaScript API Telegram Web Apps. Она позволяет писать Telegram Mini Apps полностью на Rust: на чистом WebAssembly или через готовые интеграции с Yew и Leptos.

Покрытие API

Крейт отслеживает версию Telegram WebApp API 9.6 — это последняя версия Mini App, доступная в telegram-web-app.js. Bot API с тех пор дошёл до 10.1, но версии с 9.7 по 10.1 не добавили новых методов, полей или событий WebApp, поэтому покрытие полное. Bot API 9.5 добавил icon_custom_emoji_id для нижних кнопок; 9.6 добавил WebApp.requestChat и события requestedChatSent / requestedChatFailed — всё реализовано.

Покрытая поверхность включает:

  • Кнопки: Main, Secondary, Back и Settings
  • Диалоги: alert, confirm, popup и сканер QR-кодов
  • Навигацию, ссылки, шеринг и инвойсы
  • Тему, цвета, вьюпорт и safe-area отступы
  • Хранилища: CloudStorage, DeviceStorage и SecureStorage
  • Сенсоры: акселерометр, гироскоп, ориентацию устройства и LocationManager
  • Биометрическую аутентификацию и тактильную отдачу

Полный чек-лист по методам — в файле WEBAPP_API.md.

Флаги фич

По умолчанию фичи не включены (default = []) — подключайте только нужное.

ФичаЧто включает
macrostelegram_app!, telegram_page! и telegram_router! для приложений и роутинга без бойлерплейта
yewuse_telegram_context, реактивные хуки use_viewport / use_theme / use_safe_area и компоненты BottomButton / BackButton / SettingsButton
leptosprovide_telegram_context, те же реактивные хуки use_* и аналогичные компоненты для Leptos
mockНастраиваемый мок Telegram.WebApp для локальной разработки и тестов
fullОбъединяет macros, yew, leptos и mock
telegram-webapp-sdk = { version = "0.11", features = ["leptos", "mock"] }

Оглавление документации

Лицензия

telegram-webapp-sdk распространяется под лицензией MIT.

Установка

Добавление зависимости

Добавьте крейт в Cargo.toml:

[dependencies]
telegram-webapp-sdk = "0.11"

Включение флагов фич

Крейт поставляется без включённых фич (default = []), поэтому вы явно подключаете только нужное:

telegram-webapp-sdk = { version = "0.11", features = ["macros", "yew", "leptos", "mock"] }
ФичаНазначение
macrosМакросы telegram_app!, telegram_page! и telegram_router!
yewХук контекста, реактивные хуки и компоненты системных кнопок для Yew
leptosПровайдер контекста, реактивные хуки и компоненты системных кнопок для Leptos
mockНастраиваемый мок Telegram.WebApp для локальной разработки
fullСокращение для macros + yew + leptos + mock

Обычно включают один фреймворк плюс mock:

# Приложение на Leptos с поддержкой локального мока
telegram-webapp-sdk = { version = "0.11", features = ["leptos", "mock"] }

Минимальная поддерживаемая версия Rust

MSRV — Rust 1.96 (edition 2024). Более старые тулчейны не поддерживаются.

rustup update stable
rustc --version   # должно быть >= 1.96

Цель сборки — браузер

Telegram Mini Apps исполняются как WebAssembly во встроенном браузере Telegram, поэтому собирайте под цель wasm32-unknown-unknown:

rustup target add wasm32-unknown-unknown

Сборка через Trunk или wasm-pack

Используйте WASM-бандлер, чтобы получить итоговый .wasm, JS-обвязку и index.html.

Trunk (рекомендуется для целых приложений — его использует демо):

cargo install trunk
trunk serve   # dev-сервер с автоперезагрузкой
trunk build --release

Минимальный index.html для Trunk подключает скрипт Telegram и ваш крейт:

<!doctype html>
<html>
  <head>
    <script src="https://telegram.org/js/telegram-web-app.js"></script>
    <link data-trunk rel="rust" />
  </head>
  <body></body>
</html>

wasm-pack (для библиотек или интеграции, управляемой из JS):

cargo install wasm-pack
wasm-pack build --target web

Дальше

Быстрый старт

Эта страница проходит через минимальное полезное Mini App: инициализация SDK, получение инстанса WebApp, сигнал готовности, чтение данных запуска и показ MainButton.

1. Инициализация SDK и получение инстанса

init_sdk() разбирает initData и themeParams из Telegram.WebApp и сохраняет их в глобальном контексте. После этого получите живой хендл WebApp.

Есть два аксессора:

  • TelegramWebApp::instance() возвращает Option<TelegramWebApp> (None, если код запущен не внутри Telegram) — удобно для graceful degradation.
  • TelegramWebApp::try_instance() возвращает Result<TelegramWebApp, JsValue> — удобно внутри функций, использующих ?.
use telegram_webapp_sdk::{core::init::init_sdk, webapp::TelegramWebApp};
use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub fn main() -> Result<(), JsValue> {
    init_sdk()?;

    let app = TelegramWebApp::try_instance()?;
    app.ready()?;   // сообщаем Telegram, что интерфейс инициализирован
    app.expand()?;  // разворачиваем Mini App на полную высоту

    Ok(())
}

Если нужен запуск без паники, используйте try_init_sdk() — он возвращает Ok(false), когда Telegram недоступен:

use telegram_webapp_sdk::core::init::try_init_sdk;

match try_init_sdk() {
    Ok(true) => { /* запущено внутри Telegram */ }
    Ok(false) => { /* обычный браузер — используем фолбэк */ }
    Err(e) => eprintln!("ошибка инициализации: {e}")
}

2. Чтение init data и пользователя

Разобранные данные запуска лежат в глобальном TelegramContext. Доступ — через геттер на основе замыкания:

use telegram_webapp_sdk::core::context::TelegramContext;

TelegramContext::get(|ctx| {
    if let Some(user) = &ctx.init_data.user {
        web_sys::console::log_1(&format!("Привет, {}", user.first_name).into());
    }
    let _ = ctx.init_data.auth_date;
    let _ = ctx.init_data.start_param.as_deref();
});

Для серверной проверки подписи возьмите сырую URL-кодированную строку и отправьте её на бэкенд (проверяйте там, с токеном бота — никогда на клиенте):

use telegram_webapp_sdk::TelegramWebApp;

let raw_init_data = TelegramWebApp::get_raw_init_data()?;
// POST /auth  { "init_data": raw_init_data }
Ok::<(), Box<dyn std::error::Error>>(())

3. Минимальный MainButton

Задайте подпись, покажите кнопку и зарегистрируйте колбэк клика. Колбэк возвращает EventHandle, который позже можно передать в remove_main_button_callback.

use telegram_webapp_sdk::webapp::TelegramWebApp;

fn run() -> Result<(), wasm_bindgen::JsValue> {
let app = TelegramWebApp::try_instance()?;

app.set_main_button_text("Отправить заказ")?;
app.set_main_button_color("#2481cc")?;
app.enable_main_button()?;
app.show_main_button()?;

let handle = app.set_main_button_callback(|| {
    if let Some(app) = TelegramWebApp::instance() {
        let _ = app.send_data("order-confirmed");
    }
})?;

// позже, когда кнопка больше не нужна:
app.remove_main_button_callback(handle)?;
app.hide_main_button()?;
Ok(())
}

Что дальше

WebApp API

Почти все возможности доступны через хендл TelegramWebApp (instance() / try_instance()), плюс несколько модулей со свободными функциями в telegram_webapp_sdk::api::* для сенсоров, хранилищ, тактильной отдачи и темы. Эта страница группирует поверхность по разделам. Исчерпывающий чек-лист — в WEBAPP_API.md.

Паттерн *_with_callback против async

У каждого одноразового колбэка Telegram есть два Rust-сиблинга:

  • foo_with_callback(..., F) — синхронная регистрация; ваше замыкание вызывается, когда Telegram присылает результат. Используйте, когда нельзя .await (например, внутри не-async замыкания).
  • async fn foo(...) — возвращает естественный Rust-тип через .await. Предпочтительно в продакшене.
use telegram_webapp_sdk::webapp::TelegramWebApp;

async fn run() -> Result<(), wasm_bindgen::JsValue> {
let app = TelegramWebApp::try_instance()?;

let confirmed: bool = app.show_confirm("Отправить заказ?").await?;
let scanned: String = app.show_scan_qr_popup("Сканируйте QR-код").await?;
let granted: bool = app.request_write_access().await?;
let _ = (confirmed, scanned, granted);
Ok(())
}

То же справедливо для share_message, request_chat, check_home_screen_status, set_emoji_status, request_emoji_status_access, open_invoice, download_file, read_text_from_clipboard, show_popup и invoke_custom_method.

Кнопки

Нижние кнопки Main и Secondary используют enum-селектор BottomButton (BottomButton::Main / BottomButton::Secondary):

use telegram_webapp_sdk::webapp::{BottomButton, BottomButtonParams, TelegramWebApp};

fn run() -> Result<(), wasm_bindgen::JsValue> {
let app = TelegramWebApp::try_instance()?;

app.set_bottom_button_text(BottomButton::Main, "Отправить")?;
app.show_bottom_button(BottomButton::Main)?;

// Атомарное обновление через setParams:
let params = BottomButtonParams {
    text: Some("Отправить"),
    color: Some("#2481cc"),
    text_color: Some("#ffffff"),
    is_active: Some(true),
    is_visible: Some(true),
    icon_custom_emoji_id: Some("123456789"), // Bot API 9.5+
    ..Default::default()
};
app.set_bottom_button_params(BottomButton::Main, &params)?;
Ok(())
}

Есть удобные алиасы для главной кнопки (set_main_button_text, show_main_button, enable_main_button, set_main_button_callback, …) и для вторичной (set_secondary_button_text, show_secondary_button, …).

Кнопки Back и Settings управляются напрямую:

use telegram_webapp_sdk::webapp::TelegramWebApp;
fn run() -> Result<(), wasm_bindgen::JsValue> {
let app = TelegramWebApp::try_instance()?;
app.show_back_button()?;
let handle = app.set_back_button_callback(|| { /* назад */ })?;
app.remove_back_button_callback(handle)?;

app.show_settings_button()?;
let sh = app.set_settings_button_callback(|| { /* настройки */ })?;
app.remove_settings_button_callback(sh)?;
Ok(())
}

Диалоги

use telegram_webapp_sdk::webapp::TelegramWebApp;
async fn run() -> Result<(), wasm_bindgen::JsValue> {
let app = TelegramWebApp::try_instance()?;
app.show_alert("Сохранено!")?;
let ok: bool = app.show_confirm("Удалить элемент?").await?;
let button_id: String = app.show_popup(&wasm_bindgen::JsValue::NULL).await?;
let code: String = app.show_scan_qr_popup("Наведите на QR-код").await?;
app.close_scan_qr_popup()?;
let _ = (ok, button_id, code);
Ok(())
}

Навигация и ссылки

open_link (с опциональным OpenLinkOptions), open_telegram_link, switch_inline_query, share_url, share_message / share_to_story, request_chat, add_to_home_screen и check_home_screen_status.

Тема и цвета

set_header_color, set_background_color, set_bottom_bar_color, color_scheme(), а также свободная функция telegram_webapp_sdk::api::theme::get_theme_params(), возвращающая разобранную палитру.

Вьюпорт и safe-area

viewport_height(), viewport_width(), viewport_stable_height(), expand_viewport() и аксессоры SafeAreaInset: safe_area_inset() / content_safe_area_inset(). Полноэкранный режим и ориентация: request_fullscreen, exit_fullscreen, is_fullscreen, lock_orientation и unlock_orientation.

Хранилища

  • CloudStorageapi::cloud_storage::{get_item, set_item, remove_item, get_items, remove_items, get_keys} (каждая возвращает JS Promise).
  • DeviceStorageapi::device_storage::{set, get, remove, clear} (async).
  • SecureStorageapi::secure_storage::{set, get, restore, remove, clear} (async).
use telegram_webapp_sdk::api::device_storage::{get, set};
async fn run() -> Result<(), wasm_bindgen::JsValue> {
set("theme", "dark").await?;
let value: Option<String> = get("theme").await?;
let _ = value;
Ok(())
}

Сенсоры

Акселерометр, гироскоп и ориентация устройства предоставляют start(), stop(), геттер (get_acceleration(), get_angular_velocity(), get_orientation()) и колбэки жизненного цикла on_started / on_changed / on_stopped / on_failed. LocationManager: init(), get_location(), open_settings(), on_location_manager_updated и on_location_requested.

use telegram_webapp_sdk::api::accelerometer::{get_acceleration, start, stop};
start()?;
let reading = get_acceleration();
stop()?;
let _ = reading;
Ok::<(), wasm_bindgen::JsValue>(())

Биометрия

api::biometric::{init, is_biometric_available, request_access, authenticate, update_biometric_token, open_settings, is_inited, is_access_granted, device_id, …}.

use telegram_webapp_sdk::api::biometric::{authenticate, init, is_biometric_available, request_access};
fn run() -> Result<(), wasm_bindgen::JsValue> {
init()?;
if is_biometric_available()? {
    request_access("auth-key", Some("Разблокировать хранилище"), None)?;
    authenticate("auth-key", None, None)?;
}
Ok(())
}

Тактильная отдача

use telegram_webapp_sdk::api::haptic::{
    impact_occurred, notification_occurred, selection_changed,
    HapticImpactStyle, HapticNotificationType,
};
impact_occurred(HapticImpactStyle::Light)?;
notification_occurred(HapticNotificationType::Success)?;
selection_changed()?;
Ok::<(), wasm_bindgen::JsValue>(())

См. также Интеграция с фреймворками — реактивные обёртки над событиями вьюпорта, темы и safe-area.

Интеграция с фреймворками

Крейт поставляет опциональные интеграции для Leptos (фича leptos) и Yew (фича yew). Обе предоставляют одни и те же три реактивных хука и три компонента системных кнопок. Хуки инициализируются текущими значениями и перерисовывают компонент, когда Telegram присылает события viewportChanged, themeChanged, safeAreaChanged или contentSafeAreaChanged. Подписки очищаются автоматически при размонтировании / уничтожении скоупа.

Leptos

Предоставьте контекст один раз рядом с корнем, затем читайте через use_context:

use leptos::prelude::*;
use telegram_webapp_sdk::{core::context::TelegramContext, leptos::provide_telegram_context};

#[component]
fn App() -> impl IntoView {
    provide_telegram_context().expect("context");
    let ctx = use_context::<TelegramContext>().expect("context");
    view! { <span>{ ctx.init_data.auth_date }</span> }
}

Реактивные хуки

В Leptos хуки возвращают ReadSignal<T>, поэтому читайте их через .get() внутри замыкания:

use leptos::prelude::*;
use telegram_webapp_sdk::leptos::{use_safe_area, use_theme, use_viewport};

#[component]
fn Status() -> impl IntoView {
    let viewport = use_viewport(); // ReadSignal<ViewportState>
    let theme = use_theme();       // ReadSignal<ThemeState>
    let safe = use_safe_area();    // ReadSignal<SafeAreaState>
    view! {
        <div>
            { move || viewport.get().height }
            { move || theme.get().color_scheme.unwrap_or_default() }
            { move || safe.get().area.map(|i| i.top).unwrap_or(0.0) }
        </div>
    }
}

ViewportState содержит height, stable_height и is_expanded. ThemeState содержит color_scheme: Option<String> и разобранную палитру params. SafeAreaState содержит area и content как Option<SafeAreaInset>.

Компоненты

BottomButton принимает селектор button и реактивный text (и опциональные color / text_color / on_click). BackButton и SettingsButton принимают реактивный сигнал visible и опциональное замыкание on_click. Все три управляют нативными кнопками Telegram и очищаются при размонтировании.

use leptos::prelude::*;
use telegram_webapp_sdk::{
    leptos::{provide_telegram_context, BackButton, BottomButton, SettingsButton},
    webapp::BottomButton as Btn,
};

#[component]
fn App() -> impl IntoView {
    provide_telegram_context().expect("context");
    let (text, _set_text) = signal("Отправить".to_owned());
    let visible = RwSignal::new(true);
    view! {
        <BottomButton button=Btn::Main text on_click=move || { /* отправка */ } />
        <BackButton visible=visible on_click=move || { /* назад */ } />
        <SettingsButton visible=visible on_click=move || { /* настройки */ } />
    }
}

Yew

Читайте контекст хуком use_telegram_context. Он возвращает Result<TelegramContext, JsValue> и реактивно резолвится, как только контекст инициализирован:

use telegram_webapp_sdk::yew::use_telegram_context;
use yew::prelude::*;

#[function_component(App)]
fn app() -> Html {
    match use_telegram_context() {
        Ok(ctx) => html! { <span>{ ctx.init_data.auth_date }</span> },
        Err(_) => html! { <div>{ "Загрузка контекста Telegram..." }</div> },
    }
}

Реактивные хуки

В Yew хуки возвращают значение состояния напрямую (не сигнал), поэтому читайте поля прямо с него:

use telegram_webapp_sdk::yew::{use_safe_area, use_theme, use_viewport};
use yew::prelude::*;

#[function_component(Status)]
fn status() -> Html {
    let viewport = use_viewport(); // ViewportState
    let theme = use_theme();       // ThemeState
    let safe = use_safe_area();    // SafeAreaState
    html! {
        <div>
            { viewport.height }
            { theme.color_scheme.clone().unwrap_or_default() }
            { safe.area.map(|i| i.top).unwrap_or(0.0) }
        </div>
    }
}

Компоненты

Компоненты Yew принимают обычные пропсы: BottomButton использует text / color / text_color / on_click (всегда управляет кнопкой Main), а BackButton и SettingsButton принимают проп visible: bool и on_click: Callback<()>.

use telegram_webapp_sdk::yew::{BackButton, BottomButton, SettingsButton};
use yew::prelude::*;

#[function_component(App)]
fn app() -> Html {
    let on_main = Callback::from(|_| {});
    let on_back = Callback::from(|_| {});
    let on_settings = Callback::from(|_| {});
    html! {
        <>
            <BottomButton text="Отправить" color="#000" text_color="#fff" on_click={on_main} />
            <BackButton visible={true} on_click={on_back} />
            <SettingsButton visible={true} on_click={on_settings} />
        </>
    }
}

См. также

Мок и тестирование

Фича mock устанавливает настраиваемый поддельный объект Telegram.WebApp на window, что позволяет разрабатывать и тестировать вне клиента Telegram. Включите её:

telegram-webapp-sdk = { version = "0.11", features = ["mock"] }

Установка мок-окружения

Мок находится в telegram_webapp_sdk::mock. Соберите MockTelegramConfig (он реализует Default) и вызовите mock_telegram_webapp — он внедрит window.Telegram.WebApp с замоканными initData, themeParams, platform и version. Используйте только в debug-сборках.

use telegram_webapp_sdk::mock::{config::MockTelegramConfig, init::mock_telegram_webapp};

fn run() -> Result<(), wasm_bindgen::JsValue> {
let config = MockTelegramConfig::default();
mock_telegram_webapp(config)?;
// window.Telegram.WebApp теперь существует — SDK ведёт себя как внутри Telegram.
Ok(())
}

Настройка замоканных данных

MockTelegramConfig содержит опциональные поля для пользователя, данных авторизации, всех цветов темы и платформы/версии. Замоканный пользователь — это MockTelegramUser:

use telegram_webapp_sdk::mock::{
    config::MockTelegramConfig, data::MockTelegramUser, init::mock_telegram_webapp,
};

fn run() -> Result<(), wasm_bindgen::JsValue> {
let config = MockTelegramConfig {
    user: Some(MockTelegramUser {
        id: 42,
        first_name: "Alice".into(),
        username: Some("alice".into()),
        language_code: Some("en".into()),
        ..Default::default()
    }),
    platform: Some("android".into()),
    version: Some("9.6".into()),
    ..Default::default()
};
mock_telegram_webapp(config)?;
Ok(())
}

Конфиг можно также загрузить из TOML-файла (тот же telegram-webapp.toml, который telegram_app! читает в debug-сборках):

use telegram_webapp_sdk::mock::config::MockTelegramConfig;

let config = MockTelegramConfig::from_file("telegram-webapp.toml")?;
Ok::<(), std::io::Error>(())

Тестирование через wasm_bindgen_test

Поскольку SDK работает с глобальными объектами браузера, тесты выполняются в headless-браузере через wasm-bindgen-test. Добавьте его в dev-зависимости:

[dev-dependencies]
wasm-bindgen-test = "0.3"

Настройте тесты на запуск в браузере и установите мок перед использованием SDK:

use telegram_webapp_sdk::{
    core::init::init_sdk,
    mock::{config::MockTelegramConfig, init::mock_telegram_webapp},
    webapp::TelegramWebApp,
};
use wasm_bindgen_test::{wasm_bindgen_test, wasm_bindgen_test_configure};

wasm_bindgen_test_configure!(run_in_browser);

#[wasm_bindgen_test]
fn instance_is_available_after_mock() {
    mock_telegram_webapp(MockTelegramConfig::default()).expect("mock installed");
    init_sdk().expect("sdk initialized");

    let app = TelegramWebApp::instance().expect("instance");
    app.ready().expect("ready");
}

Запуск набора тестов на реальном браузерном движке:

# Chrome / Chromium
wasm-pack test --headless --chrome

# или Firefox
wasm-pack test --headless --firefox

Советы

  • В коде приложения предпочитайте TelegramWebApp::instance() (возвращает Option), чтобы один и тот же бинарь корректно деградировал, когда нет ни Telegram, ни мока.
  • Все wasm_bindgen_test разделяют один window; устанавливайте мок в начале каждого теста, которому он нужен, а не полагайтесь на порядок выполнения.
  • Учтите, что TelegramContext::init успешно отрабатывает только один раз на поток — проектируйте тесты так, чтобы они не зависели от повторной инициализации глобального контекста.

См. также

Примеры

В репозитории есть несколько запускаемых примеров, встроенных в Cargo-воркспейс. Каждый показывает свой стиль интеграции — от полноценного Mini App на Trunk до обычного WASM-бинарника и серверной части полного стека.

demo/ — WASM-приложение на Trunk

Полноценное Mini App, собранное с фичами macros и mock. Использует telegram_page! / telegram_router! для роутинга и включает компоненты и страницы (demo/src/components, demo/src/pages). Так как включена фича mock, демо работает в обычном браузере во время разработки.

cd demo
trunk serve        # dev-сервер с автоперезагрузкой на http://localhost:8080
trunk build --release

Файл demo/index.html загружает telegram-web-app.js от Telegram и собранный Trunk WASM-бандл.

examples/vanilla — без фреймворка

Пример на чистом WebAssembly (examples/vanilla/src/main.rs + ui.rs), использующий SDK и DOM-хелперы напрямую, без Yew и Leptos. Зависит от крейта с фичей mock, поэтому запускается самостоятельно. Хорошая отправная точка, если нужен полный контроль над DOM.

cd examples/vanilla
trunk serve

examples/bots/rust_bot — Telegram-бот

Бот на teloxide (examples/bots/rust_bot), который открывает демо Mini App через WebApp-кнопки и принимает заказы, отправленные из приложения через sendData. Это серверная часть — нативный бинарник, не WASM.

cd examples/bots/rust_bot
cp .env.example .env          # добавьте свой TELOXIDE_TOKEN
cargo run

Требуется токен бота от @BotFather и URL WebApp по HTTPS.

examples/integration — полный стек

Пример из двух частей, показывающий round-trip фронтенд↔бэкенд:

  • examples/integration/frontend — WASM Mini App, отправляющее JSON-сообщение боту через TelegramWebApp::send_data(...). (Этот член исключён из воркспейса и собирается отдельно через Trunk.)
  • examples/integration/backend — бот на teloxide, принимающий web_app_data из обновления, разбирающий JSON и отвечающий.
# терминал 1 — бэкенд
cd examples/integration/backend
cargo run

# терминал 2 — фронтенд
cd examples/integration/frontend
trunk serve

Поток данных: пользователь открывает Mini App → взаимодействует с UI → приложение вызывает send_data → Telegram доставляет payload боту как web_app_data → бот обрабатывает его и отвечает.

Сборка любого WASM-примера

Все браузерные примеры собираются под wasm32-unknown-unknown и обслуживаются через Trunk:

rustup target add wasm32-unknown-unknown
cargo install trunk

См. также