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::{core::context::TelegramContext, webapp::TelegramWebApp};
9
10impl TelegramWebApp {
11    /// Get instance of `Telegram.WebApp` or `None` if not present
12    pub fn instance() -> Option<Self> {
13        let win = window()?;
14        let tg = Reflect::get(&win, &"Telegram".into()).ok()?;
15        let webapp = Reflect::get(&tg, &"WebApp".into()).ok()?;
16        webapp.dyn_into::<Object>().ok().map(|inner| Self {
17            inner
18        })
19    }
20
21    /// Try to get instance of `Telegram.WebApp`.
22    ///
23    /// # Errors
24    /// Returns [`JsValue`] if the `Telegram.WebApp` object is missing or
25    /// malformed.
26    pub fn try_instance() -> Result<Self, JsValue> {
27        let win = window().ok_or_else(|| JsValue::from_str("window not available"))?;
28        let tg = Reflect::get(&win, &"Telegram".into())?;
29        let webapp = Reflect::get(&tg, &"WebApp".into())?;
30        let inner = webapp.dyn_into::<Object>()?;
31        Ok(Self {
32            inner
33        })
34    }
35
36    /// Returns the raw initData string as provided by Telegram.
37    ///
38    /// This is the URL-encoded initData string captured during SDK
39    /// initialization, suitable for server-side signature validation.
40    ///
41    /// # Errors
42    ///
43    /// Returns an error if the SDK has not been initialized via
44    /// [`crate::core::init::init_sdk`].
45    ///
46    /// # Examples
47    ///
48    /// ```no_run
49    /// use telegram_webapp_sdk::TelegramWebApp;
50    ///
51    /// match TelegramWebApp::get_raw_init_data() {
52    ///     Ok(raw) => {
53    ///         // Send to backend for validation
54    ///         println!("Raw initData: {}", raw);
55    ///     }
56    ///     Err(e) => eprintln!("SDK not initialized: {}", e)
57    /// }
58    /// ```
59    pub fn get_raw_init_data() -> Result<String, &'static str> {
60        TelegramContext::get_raw_init_data()
61    }
62
63    /// Call `WebApp.sendData(data)`.
64    ///
65    /// # Errors
66    /// Returns [`JsValue`] if the underlying JS call fails.
67    pub fn send_data(&self, data: &str) -> Result<(), JsValue> {
68        self.call1("sendData", &data.into())
69    }
70
71    /// Returns whether the WebApp version is at least the provided value.
72    ///
73    /// # Examples
74    /// ```no_run
75    /// use telegram_webapp_sdk::webapp::TelegramWebApp;
76    ///
77    /// if let Some(app) = TelegramWebApp::instance() {
78    ///     let _ = app.is_version_at_least("9.0");
79    /// }
80    /// ```
81    pub fn is_version_at_least(&self, version: &str) -> Result<bool, JsValue> {
82        let f = Reflect::get(&self.inner, &"isVersionAtLeast".into())?;
83        let func = f
84            .dyn_ref::<Function>()
85            .ok_or_else(|| JsValue::from_str("isVersionAtLeast is not a function"))?;
86        let result = func.call1(&self.inner, &version.into())?;
87        Ok(result.as_bool().unwrap_or(false))
88    }
89
90    /// Call `WebApp.ready()`.
91    ///
92    /// # Errors
93    /// Returns [`JsValue`] if the underlying JS call fails.
94    pub fn ready(&self) -> Result<(), JsValue> {
95        self.call0("ready")
96    }
97
98    // === Internal helper methods ===
99
100    pub(super) fn call0(&self, method: &str) -> Result<(), JsValue> {
101        let f = Reflect::get(&self.inner, &method.into())?;
102        let func = f
103            .dyn_ref::<Function>()
104            .ok_or_else(|| JsValue::from_str("not a function"))?;
105        func.call0(&self.inner)?;
106        Ok(())
107    }
108
109    pub(super) fn call1(&self, method: &str, arg: &JsValue) -> Result<(), JsValue> {
110        let f = Reflect::get(&self.inner, &method.into())?;
111        let func = f
112            .dyn_ref::<Function>()
113            .ok_or_else(|| JsValue::from_str("not a function"))?;
114        func.call1(&self.inner, arg)?;
115        Ok(())
116    }
117
118    pub(super) fn call_nested0(&self, field: &str, method: &str) -> Result<(), JsValue> {
119        let obj = Reflect::get(&self.inner, &field.into())?;
120        let f = Reflect::get(&obj, &method.into())?;
121        let func = f
122            .dyn_ref::<Function>()
123            .ok_or_else(|| JsValue::from_str("not a function"))?;
124        func.call0(&obj)?;
125        Ok(())
126    }
127}