Skip to main content

telegram_webapp_sdk/webapp/
navigation.rs

1// SPDX-FileCopyrightText: 2025 RAprogramm <andrey.rozanov.vl@gmail.com>
2// SPDX-License-Identifier: MIT
3
4use js_sys::{Function, Reflect};
5use serde_wasm_bindgen::to_value;
6use wasm_bindgen::{JsCast, JsValue, prelude::Closure};
7
8use crate::webapp::{
9    TelegramWebApp,
10    core::{await_one_shot, one_shot_promise},
11    types::OpenLinkOptions
12};
13
14impl TelegramWebApp {
15    /// Call `WebApp.openLink(url)`.
16    ///
17    /// # Examples
18    /// ```no_run
19    /// # use telegram_webapp_sdk::webapp::TelegramWebApp;
20    /// # let app = TelegramWebApp::instance().unwrap();
21    /// app.open_link("https://example.com", None).unwrap();
22    /// ```
23    pub fn open_link(&self, url: &str, options: Option<&OpenLinkOptions>) -> Result<(), JsValue> {
24        let f = Reflect::get(&self.inner, &"openLink".into())?;
25        let func = f
26            .dyn_ref::<Function>()
27            .ok_or_else(|| JsValue::from_str("openLink is not a function"))?;
28        match options {
29            Some(opts) => {
30                let value = to_value(opts).map_err(|err| JsValue::from_str(&err.to_string()))?;
31                func.call2(&self.inner, &url.into(), &value)?;
32            }
33            None => {
34                func.call1(&self.inner, &url.into())?;
35            }
36        }
37        Ok(())
38    }
39
40    /// Call `WebApp.openTelegramLink(url)`.
41    ///
42    /// # Examples
43    /// ```no_run
44    /// # use telegram_webapp_sdk::webapp::TelegramWebApp;
45    /// # let app = TelegramWebApp::instance().unwrap();
46    /// app.open_telegram_link("https://t.me/telegram").unwrap();
47    /// ```
48    pub fn open_telegram_link(&self, url: &str) -> Result<(), JsValue> {
49        Reflect::get(&self.inner, &"openTelegramLink".into())?
50            .dyn_into::<Function>()?
51            .call1(&self.inner, &url.into())?;
52        Ok(())
53    }
54
55    /// Call `WebApp.switchInlineQuery(query, choose_chat_types)`.
56    ///
57    /// # Examples
58    /// ```no_run
59    /// # use telegram_webapp_sdk::webapp::TelegramWebApp;
60    /// # let app = TelegramWebApp::instance().unwrap();
61    /// app.switch_inline_query("query", None).unwrap();
62    /// ```
63    ///
64    /// # Errors
65    /// Returns [`JsValue`] if the underlying JS call fails.
66    pub fn switch_inline_query(
67        &self,
68        query: &str,
69        choose_chat_types: Option<&JsValue>
70    ) -> Result<(), JsValue> {
71        let f = Reflect::get(&self.inner, &"switchInlineQuery".into())?;
72        let func = f
73            .dyn_ref::<Function>()
74            .ok_or_else(|| JsValue::from_str("switchInlineQuery is not a function"))?;
75        match choose_chat_types {
76            Some(types) => func.call2(&self.inner, &query.into(), types)?,
77            None => func.call1(&self.inner, &query.into())?
78        };
79        Ok(())
80    }
81
82    /// Callback variant of [`Self::share_message`].
83    ///
84    /// # Errors
85    /// Returns [`JsValue`] if the underlying JS call fails.
86    pub fn share_message_with_callback<F>(&self, msg_id: &str, callback: F) -> Result<(), JsValue>
87    where
88        F: 'static + FnOnce(bool)
89    {
90        let cb = Closure::once_into_js(move |v: JsValue| {
91            callback(v.as_bool().unwrap_or(false));
92        });
93        let f = Reflect::get(&self.inner, &"shareMessage".into())?;
94        let func = f
95            .dyn_ref::<Function>()
96            .ok_or_else(|| JsValue::from_str("shareMessage is not a function"))?;
97        func.call2(&self.inner, &msg_id.into(), &cb)?;
98        Ok(())
99    }
100
101    /// Async wrapper over `WebApp.shareMessage`. Resolves with `true` when the
102    /// prepared message was sent.
103    ///
104    /// # Examples
105    /// ```no_run
106    /// # use telegram_webapp_sdk::webapp::TelegramWebApp;
107    /// # async fn run() -> Result<(), wasm_bindgen::JsValue> {
108    /// let app = TelegramWebApp::try_instance()?;
109    /// let sent: bool = app.share_message("id123").await?;
110    /// let _ = sent;
111    /// # Ok(())
112    /// # }
113    /// ```
114    ///
115    /// # Errors
116    /// Returns [`JsValue`] if the underlying JS call fails.
117    pub async fn share_message(&self, msg_id: &str) -> Result<bool, JsValue> {
118        let webapp = self.inner.clone();
119        let msg_id = msg_id.to_owned();
120        let promise = one_shot_promise(move |resolve, _reject| {
121            let cb = Closure::once_into_js(move |v: JsValue| {
122                let _ = resolve.call1(&JsValue::NULL, &v);
123            });
124            let f = Reflect::get(&webapp, &"shareMessage".into())?;
125            let func = f
126                .dyn_ref::<Function>()
127                .ok_or_else(|| JsValue::from_str("shareMessage is not a function"))?;
128            func.call2(&webapp, &msg_id.into(), &cb)?;
129            Ok(())
130        });
131        let value = await_one_shot(promise).await?;
132        Ok(value.as_bool().unwrap_or(false))
133    }
134
135    /// Call `WebApp.shareToStory(media_url, params)`.
136    ///
137    /// # Examples
138    /// ```no_run
139    /// # use js_sys::Object;
140    /// # use telegram_webapp_sdk::webapp::TelegramWebApp;
141    /// # let app = TelegramWebApp::instance().unwrap();
142    /// let params = Object::new();
143    /// app.share_to_story("https://example.com/image.png", Some(&params.into()))
144    ///     .unwrap();
145    /// ```
146    ///
147    /// # Errors
148    /// Returns [`JsValue`] if the underlying JS call fails.
149    pub fn share_to_story(
150        &self,
151        media_url: &str,
152        params: Option<&JsValue>
153    ) -> Result<(), JsValue> {
154        let f = Reflect::get(&self.inner, &"shareToStory".into())?;
155        let func = f
156            .dyn_ref::<Function>()
157            .ok_or_else(|| JsValue::from_str("shareToStory is not a function"))?;
158        match params {
159            Some(p) => func.call2(&self.inner, &media_url.into(), p)?,
160            None => func.call1(&self.inner, &media_url.into())?
161        };
162        Ok(())
163    }
164
165    /// Call `WebApp.shareURL(url, text)`.
166    ///
167    /// # Examples
168    /// ```no_run
169    /// # use telegram_webapp_sdk::webapp::TelegramWebApp;
170    /// # let app = TelegramWebApp::instance().unwrap();
171    /// app.share_url("https://example.com", Some("Check this"))
172    ///     .unwrap();
173    /// ```
174    ///
175    /// # Errors
176    /// Returns [`JsValue`] if the underlying JS call fails.
177    pub fn share_url(&self, url: &str, text: Option<&str>) -> Result<(), JsValue> {
178        let f = Reflect::get(&self.inner, &"shareURL".into())?;
179        let func = f
180            .dyn_ref::<Function>()
181            .ok_or_else(|| JsValue::from_str("shareURL is not a function"))?;
182        match text {
183            Some(t) => func.call2(&self.inner, &url.into(), &t.into())?,
184            None => func.call1(&self.inner, &url.into())?
185        };
186        Ok(())
187    }
188
189    /// Callback variant of [`Self::request_chat`] (Bot API 9.6+).
190    ///
191    /// # Errors
192    /// Returns [`JsValue`] if the underlying JS call fails.
193    pub fn request_chat_with_callback<F>(&self, req_id: i32, callback: F) -> Result<(), JsValue>
194    where
195        F: 'static + FnOnce(bool)
196    {
197        let cb = Closure::once_into_js(move |v: JsValue| {
198            callback(v.as_bool().unwrap_or(false));
199        });
200        let f = Reflect::get(&self.inner, &"requestChat".into())?;
201        let func = f
202            .dyn_ref::<Function>()
203            .ok_or_else(|| JsValue::from_str("requestChat is not a function"))?;
204        func.call2(&self.inner, &req_id.into(), &cb)?;
205        Ok(())
206    }
207
208    /// Async wrapper over `WebApp.requestChat` (Bot API 9.6+). Resolves with
209    /// `true` when the user picks a chat, `false` on cancel/failure.
210    ///
211    /// # Examples
212    /// ```no_run
213    /// # use telegram_webapp_sdk::webapp::TelegramWebApp;
214    /// # async fn run() -> Result<(), wasm_bindgen::JsValue> {
215    /// let app = TelegramWebApp::try_instance()?;
216    /// let sent: bool = app.request_chat(42).await?;
217    /// let _ = sent;
218    /// # Ok(())
219    /// # }
220    /// ```
221    ///
222    /// # Errors
223    /// Returns [`JsValue`] if the underlying JS call fails (including when the
224    /// running Telegram client predates Bot API 9.6).
225    pub async fn request_chat(&self, req_id: i32) -> Result<bool, JsValue> {
226        let webapp = self.inner.clone();
227        let promise = one_shot_promise(move |resolve, _reject| {
228            let cb = Closure::once_into_js(move |v: JsValue| {
229                let _ = resolve.call1(&JsValue::NULL, &v);
230            });
231            let f = Reflect::get(&webapp, &"requestChat".into())?;
232            let func = f
233                .dyn_ref::<Function>()
234                .ok_or_else(|| JsValue::from_str("requestChat is not a function"))?;
235            func.call2(&webapp, &req_id.into(), &cb)?;
236            Ok(())
237        });
238        let value = await_one_shot(promise).await?;
239        Ok(value.as_bool().unwrap_or(false))
240    }
241
242    /// Call `WebApp.addToHomeScreen()` and return whether the prompt was shown.
243    ///
244    /// # Examples
245    /// ```no_run
246    /// # use telegram_webapp_sdk::webapp::TelegramWebApp;
247    /// # let app = TelegramWebApp::instance().unwrap();
248    /// let _shown = app.add_to_home_screen().unwrap();
249    /// ```
250    pub fn add_to_home_screen(&self) -> Result<bool, JsValue> {
251        let f = Reflect::get(&self.inner, &"addToHomeScreen".into())?;
252        let func = f
253            .dyn_ref::<Function>()
254            .ok_or_else(|| JsValue::from_str("addToHomeScreen is not a function"))?;
255        let result = func.call0(&self.inner)?;
256        Ok(result.as_bool().unwrap_or(false))
257    }
258
259    /// Callback variant of [`Self::check_home_screen_status`].
260    pub fn check_home_screen_status_with_callback<F>(&self, callback: F) -> Result<(), JsValue>
261    where
262        F: 'static + FnOnce(String)
263    {
264        let cb = Closure::once_into_js(move |status: JsValue| {
265            callback(status.as_string().unwrap_or_default());
266        });
267        let f = Reflect::get(&self.inner, &"checkHomeScreenStatus".into())?;
268        let func = f
269            .dyn_ref::<Function>()
270            .ok_or_else(|| JsValue::from_str("checkHomeScreenStatus is not a function"))?;
271        func.call1(&self.inner, &cb)?;
272        Ok(())
273    }
274
275    /// Async wrapper over `WebApp.checkHomeScreenStatus`. Resolves with the
276    /// status string Telegram returns (e.g. `"added"`, `"missed"`).
277    ///
278    /// # Errors
279    /// Returns [`JsValue`] if the underlying JS call fails.
280    pub async fn check_home_screen_status(&self) -> Result<String, JsValue> {
281        let webapp = self.inner.clone();
282        let promise = one_shot_promise(move |resolve, _reject| {
283            let cb = Closure::once_into_js(move |status: JsValue| {
284                let _ = resolve.call1(&JsValue::NULL, &status);
285            });
286            let f = Reflect::get(&webapp, &"checkHomeScreenStatus".into())?;
287            let func = f
288                .dyn_ref::<Function>()
289                .ok_or_else(|| JsValue::from_str("checkHomeScreenStatus is not a function"))?;
290            func.call1(&webapp, &cb)?;
291            Ok(())
292        });
293        let value = await_one_shot(promise).await?;
294        Ok(value.as_string().unwrap_or_default())
295    }
296}