telegram_webapp_sdk/webapp/
core.rs

1// SPDX-FileCopyrightText: 2025 RAprogramm <andrey.rozanov.vl@gmail.com>
2// SPDX-License-Identifier: MIT
3
4use js_sys::{Function, Object, Reflect};
5use wasm_bindgen::{JsCast, JsValue};
6use web_sys::window;
7
8use crate::{
9    core::context::TelegramContext,
10    validate_init_data::{self, ValidationKey},
11    webapp::TelegramWebApp
12};
13
14impl TelegramWebApp {
15    /// Get instance of `Telegram.WebApp` or `None` if not present
16    pub fn instance() -> Option<Self> {
17        let win = window()?;
18        let tg = Reflect::get(&win, &"Telegram".into()).ok()?;
19        let webapp = Reflect::get(&tg, &"WebApp".into()).ok()?;
20        webapp.dyn_into::<Object>().ok().map(|inner| Self {
21            inner
22        })
23    }
24
25    /// Try to get instance of `Telegram.WebApp`.
26    ///
27    /// # Errors
28    /// Returns [`JsValue`] if the `Telegram.WebApp` object is missing or
29    /// malformed.
30    pub fn try_instance() -> Result<Self, JsValue> {
31        let win = window().ok_or_else(|| JsValue::from_str("window not available"))?;
32        let tg = Reflect::get(&win, &"Telegram".into())?;
33        let webapp = Reflect::get(&tg, &"WebApp".into())?;
34        let inner = webapp.dyn_into::<Object>()?;
35        Ok(Self {
36            inner
37        })
38    }
39
40    /// Validate an `initData` payload using either HMAC-SHA256 or Ed25519.
41    ///
42    /// Pass [`ValidationKey::BotToken`] to verify the `hash` parameter using
43    /// the bot token. Use [`ValidationKey::Ed25519PublicKey`] to verify the
44    /// `signature` parameter with an Ed25519 public key.
45    ///
46    /// # Errors
47    /// Returns [`validate_init_data::ValidationError`] if validation fails.
48    ///
49    /// # Examples
50    /// ```no_run
51    /// use telegram_webapp_sdk::{TelegramWebApp, validate_init_data::ValidationKey};
52    /// let bot_token = "123456:ABC";
53    /// let query = "a=1&b=2&hash=9e5e8d7c0b1f9f3a";
54    /// TelegramWebApp::validate_init_data(query, ValidationKey::BotToken(bot_token)).unwrap();
55    /// ```
56    pub fn validate_init_data(
57        init_data: &str,
58        key: ValidationKey
59    ) -> Result<(), validate_init_data::ValidationError> {
60        match key {
61            ValidationKey::BotToken(token) => {
62                validate_init_data::verify_hmac_sha256(init_data, token)
63            }
64            ValidationKey::Ed25519PublicKey(pk) => {
65                validate_init_data::verify_ed25519(init_data, pk)
66            }
67        }
68    }
69
70    /// Returns the raw initData string as provided by Telegram.
71    ///
72    /// This is the URL-encoded initData string captured during SDK
73    /// initialization, suitable for server-side signature validation.
74    ///
75    /// # Errors
76    ///
77    /// Returns an error if the SDK has not been initialized via
78    /// [`crate::core::init::init_sdk`].
79    ///
80    /// # Examples
81    ///
82    /// ```no_run
83    /// use telegram_webapp_sdk::TelegramWebApp;
84    ///
85    /// match TelegramWebApp::get_raw_init_data() {
86    ///     Ok(raw) => {
87    ///         // Send to backend for validation
88    ///         println!("Raw initData: {}", raw);
89    ///     }
90    ///     Err(e) => eprintln!("SDK not initialized: {}", e)
91    /// }
92    /// ```
93    pub fn get_raw_init_data() -> Result<String, &'static str> {
94        TelegramContext::get_raw_init_data()
95    }
96
97    /// Call `WebApp.sendData(data)`.
98    ///
99    /// # Errors
100    /// Returns [`JsValue`] if the underlying JS call fails.
101    pub fn send_data(&self, data: &str) -> Result<(), JsValue> {
102        self.call1("sendData", &data.into())
103    }
104
105    /// Returns whether the WebApp version is at least the provided value.
106    ///
107    /// # Examples
108    /// ```no_run
109    /// use telegram_webapp_sdk::webapp::TelegramWebApp;
110    ///
111    /// if let Some(app) = TelegramWebApp::instance() {
112    ///     let _ = app.is_version_at_least("9.0");
113    /// }
114    /// ```
115    pub fn is_version_at_least(&self, version: &str) -> Result<bool, JsValue> {
116        let f = Reflect::get(&self.inner, &"isVersionAtLeast".into())?;
117        let func = f
118            .dyn_ref::<Function>()
119            .ok_or_else(|| JsValue::from_str("isVersionAtLeast is not a function"))?;
120        let result = func.call1(&self.inner, &version.into())?;
121        Ok(result.as_bool().unwrap_or(false))
122    }
123
124    /// Call `WebApp.ready()`.
125    ///
126    /// # Errors
127    /// Returns [`JsValue`] if the underlying JS call fails.
128    pub fn ready(&self) -> Result<(), JsValue> {
129        self.call0("ready")
130    }
131
132    // === Internal helper methods ===
133
134    pub(super) fn call0(&self, method: &str) -> Result<(), JsValue> {
135        let f = Reflect::get(&self.inner, &method.into())?;
136        let func = f
137            .dyn_ref::<Function>()
138            .ok_or_else(|| JsValue::from_str("not a function"))?;
139        func.call0(&self.inner)?;
140        Ok(())
141    }
142
143    pub(super) fn call1(&self, method: &str, arg: &JsValue) -> Result<(), JsValue> {
144        let f = Reflect::get(&self.inner, &method.into())?;
145        let func = f
146            .dyn_ref::<Function>()
147            .ok_or_else(|| JsValue::from_str("not a function"))?;
148        func.call1(&self.inner, arg)?;
149        Ok(())
150    }
151
152    pub(super) fn call_nested0(&self, field: &str, method: &str) -> Result<(), JsValue> {
153        let obj = Reflect::get(&self.inner, &field.into())?;
154        let f = Reflect::get(&obj, &method.into())?;
155        let func = f
156            .dyn_ref::<Function>()
157            .ok_or_else(|| JsValue::from_str("not a function"))?;
158        func.call0(&obj)?;
159        Ok(())
160    }
161}