Skip to main content

telegram_webapp_sdk/webapp/
permissions.rs

1// SPDX-FileCopyrightText: 2025-2026 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::{
9    core::types::download_file_params::DownloadFileParams,
10    webapp::{
11        TelegramWebApp,
12        core::{await_one_shot, one_shot_promise}
13    }
14};
15
16impl TelegramWebApp {
17    /// Callback variant of [`Self::request_write_access`].
18    ///
19    /// # Errors
20    /// Returns [`JsValue`] if the underlying JS call fails.
21    pub fn request_write_access_with_callback<F>(&self, callback: F) -> Result<(), JsValue>
22    where
23        F: 'static + FnOnce(bool)
24    {
25        let cb = Closure::once_into_js(move |v: JsValue| {
26            callback(v.as_bool().unwrap_or(false));
27        });
28        self.call1("requestWriteAccess", &cb)
29    }
30
31    /// Async wrapper over `WebApp.requestWriteAccess`.
32    ///
33    /// Resolves with `true` when the user grants permission to receive
34    /// messages from the bot.
35    ///
36    /// # Examples
37    /// ```no_run
38    /// # use telegram_webapp_sdk::webapp::TelegramWebApp;
39    /// # async fn run() -> Result<(), wasm_bindgen::JsValue> {
40    /// let app = TelegramWebApp::try_instance()?;
41    /// let granted: bool = app.request_write_access().await?;
42    /// let _ = granted;
43    /// # Ok(())
44    /// # }
45    /// ```
46    ///
47    /// # Errors
48    /// Returns [`JsValue`] if the underlying JS call fails.
49    pub async fn request_write_access(&self) -> Result<bool, JsValue> {
50        let webapp = self.inner.clone();
51        let promise = one_shot_promise(move |resolve, _reject| {
52            let cb = Closure::once_into_js(move |granted: JsValue| {
53                let _ = resolve.call1(&JsValue::NULL, &granted);
54            });
55            let f = Reflect::get(&webapp, &"requestWriteAccess".into())?;
56            let func = f
57                .dyn_ref::<Function>()
58                .ok_or_else(|| JsValue::from_str("requestWriteAccess is not a function"))?;
59            func.call1(&webapp, &cb)?;
60            Ok(())
61        });
62        let value = await_one_shot(promise).await?;
63        Ok(value.as_bool().unwrap_or(false))
64    }
65
66    /// Callback variant of [`Self::request_emoji_status_access`].
67    ///
68    /// # Errors
69    /// Returns [`JsValue`] if the underlying JS call fails.
70    pub fn request_emoji_status_access_with_callback<F>(&self, callback: F) -> Result<(), JsValue>
71    where
72        F: 'static + FnOnce(bool)
73    {
74        let cb = Closure::once_into_js(move |v: JsValue| {
75            callback(v.as_bool().unwrap_or(false));
76        });
77        let f = Reflect::get(&self.inner, &"requestEmojiStatusAccess".into())?;
78        let func = f
79            .dyn_ref::<Function>()
80            .ok_or_else(|| JsValue::from_str("requestEmojiStatusAccess is not a function"))?;
81        func.call1(&self.inner, &cb)?;
82        Ok(())
83    }
84
85    /// Async wrapper over `WebApp.requestEmojiStatusAccess`.
86    ///
87    /// # Errors
88    /// Returns [`JsValue`] if the underlying JS call fails.
89    pub async fn request_emoji_status_access(&self) -> Result<bool, JsValue> {
90        let webapp = self.inner.clone();
91        let promise = one_shot_promise(move |resolve, _reject| {
92            let cb = Closure::once_into_js(move |granted: JsValue| {
93                let _ = resolve.call1(&JsValue::NULL, &granted);
94            });
95            let f = Reflect::get(&webapp, &"requestEmojiStatusAccess".into())?;
96            let func = f
97                .dyn_ref::<Function>()
98                .ok_or_else(|| JsValue::from_str("requestEmojiStatusAccess is not a function"))?;
99            func.call1(&webapp, &cb)?;
100            Ok(())
101        });
102        let value = await_one_shot(promise).await?;
103        Ok(value.as_bool().unwrap_or(false))
104    }
105
106    /// Callback variant of [`Self::set_emoji_status`].
107    ///
108    /// # Errors
109    /// Returns [`JsValue`] if the underlying JS call fails.
110    pub fn set_emoji_status_with_callback<F>(
111        &self,
112        status: &JsValue,
113        callback: F
114    ) -> Result<(), JsValue>
115    where
116        F: 'static + FnOnce(bool)
117    {
118        let cb = Closure::once_into_js(move |v: JsValue| {
119            callback(v.as_bool().unwrap_or(false));
120        });
121        let f = Reflect::get(&self.inner, &"setEmojiStatus".into())?;
122        let func = f
123            .dyn_ref::<Function>()
124            .ok_or_else(|| JsValue::from_str("setEmojiStatus is not a function"))?;
125        func.call2(&self.inner, status, &cb)?;
126        Ok(())
127    }
128
129    /// Async wrapper over `WebApp.setEmojiStatus`.
130    ///
131    /// # Errors
132    /// Returns [`JsValue`] if the underlying JS call fails.
133    pub async fn set_emoji_status(&self, status: &JsValue) -> Result<bool, JsValue> {
134        let webapp = self.inner.clone();
135        let status = status.clone();
136        let promise = one_shot_promise(move |resolve, _reject| {
137            let cb = Closure::once_into_js(move |v: JsValue| {
138                let _ = resolve.call1(&JsValue::NULL, &v);
139            });
140            let f = Reflect::get(&webapp, &"setEmojiStatus".into())?;
141            let func = f
142                .dyn_ref::<Function>()
143                .ok_or_else(|| JsValue::from_str("setEmojiStatus is not a function"))?;
144            func.call2(&webapp, &status, &cb)?;
145            Ok(())
146        });
147        let value = await_one_shot(promise).await?;
148        Ok(value.as_bool().unwrap_or(false))
149    }
150
151    /// Callback variant of [`Self::open_invoice`].
152    pub fn open_invoice_with_callback<F>(&self, url: &str, callback: F) -> Result<(), JsValue>
153    where
154        F: 'static + FnOnce(String)
155    {
156        let cb = Closure::once_into_js(move |status: JsValue| {
157            callback(status.as_string().unwrap_or_default());
158        });
159        Reflect::get(&self.inner, &"openInvoice".into())?
160            .dyn_into::<Function>()?
161            .call2(&self.inner, &url.into(), &cb)?;
162        Ok(())
163    }
164
165    /// Async wrapper over `WebApp.openInvoice`. Resolves with the invoice
166    /// status string (`paid`, `cancelled`, `failed`, `pending`).
167    ///
168    /// # Errors
169    /// Returns [`JsValue`] if the underlying JS call fails.
170    pub async fn open_invoice(&self, url: &str) -> Result<String, JsValue> {
171        let webapp = self.inner.clone();
172        let url = url.to_owned();
173        let promise = one_shot_promise(move |resolve, _reject| {
174            let cb = Closure::once_into_js(move |status: JsValue| {
175                let _ = resolve.call1(&JsValue::NULL, &status);
176            });
177            Reflect::get(&webapp, &"openInvoice".into())?
178                .dyn_into::<Function>()?
179                .call2(&webapp, &url.into(), &cb)?;
180            Ok(())
181        });
182        let value = await_one_shot(promise).await?;
183        Ok(value.as_string().unwrap_or_default())
184    }
185
186    /// Callback variant of [`Self::download_file`].
187    ///
188    /// # Errors
189    /// Returns [`JsValue`] if the underlying JS call fails or the parameters
190    /// fail to serialize.
191    pub fn download_file_with_callback<F>(
192        &self,
193        params: DownloadFileParams<'_>,
194        callback: F
195    ) -> Result<(), JsValue>
196    where
197        F: 'static + FnOnce(String)
198    {
199        let js_params =
200            to_value(&params).map_err(|e| JsValue::from_str(&format!("serialize params: {e}")))?;
201        let cb = Closure::once_into_js(move |v: JsValue| {
202            callback(v.as_string().unwrap_or_default());
203        });
204        Reflect::get(&self.inner, &"downloadFile".into())?
205            .dyn_into::<Function>()?
206            .call2(&self.inner, &js_params, &cb)?;
207        Ok(())
208    }
209
210    /// Async wrapper over `WebApp.downloadFile`. Resolves with the file id
211    /// string that Telegram returns.
212    ///
213    /// # Errors
214    /// Returns [`JsValue`] if the underlying JS call fails or the parameters
215    /// fail to serialize.
216    pub async fn download_file(&self, params: DownloadFileParams<'_>) -> Result<String, JsValue> {
217        let js_params =
218            to_value(&params).map_err(|e| JsValue::from_str(&format!("serialize params: {e}")))?;
219        let webapp = self.inner.clone();
220        let promise = one_shot_promise(move |resolve, _reject| {
221            let cb = Closure::once_into_js(move |v: JsValue| {
222                let _ = resolve.call1(&JsValue::NULL, &v);
223            });
224            Reflect::get(&webapp, &"downloadFile".into())?
225                .dyn_into::<Function>()?
226                .call2(&webapp, &js_params, &cb)?;
227            Ok(())
228        });
229        let value = await_one_shot(promise).await?;
230        Ok(value.as_string().unwrap_or_default())
231    }
232
233    /// Callback variant of [`Self::read_text_from_clipboard`].
234    ///
235    /// # Errors
236    /// Returns [`JsValue`] if the underlying JS call fails.
237    pub fn read_text_from_clipboard_with_callback<F>(&self, callback: F) -> Result<(), JsValue>
238    where
239        F: 'static + FnOnce(String)
240    {
241        let cb = Closure::once_into_js(move |text: JsValue| {
242            callback(text.as_string().unwrap_or_default());
243        });
244        let f = Reflect::get(&self.inner, &"readTextFromClipboard".into())?;
245        let func = f
246            .dyn_ref::<Function>()
247            .ok_or_else(|| JsValue::from_str("readTextFromClipboard is not a function"))?;
248        func.call1(&self.inner, &cb)?;
249        Ok(())
250    }
251
252    /// Async wrapper over `WebApp.readTextFromClipboard`.
253    ///
254    /// # Errors
255    /// Returns [`JsValue`] if the underlying JS call fails.
256    pub async fn read_text_from_clipboard(&self) -> Result<String, JsValue> {
257        let webapp = self.inner.clone();
258        let promise = one_shot_promise(move |resolve, _reject| {
259            let cb = Closure::once_into_js(move |text: JsValue| {
260                let _ = resolve.call1(&JsValue::NULL, &text);
261            });
262            let f = Reflect::get(&webapp, &"readTextFromClipboard".into())?;
263            let func = f
264                .dyn_ref::<Function>()
265                .ok_or_else(|| JsValue::from_str("readTextFromClipboard is not a function"))?;
266            func.call1(&webapp, &cb)?;
267            Ok(())
268        });
269        let value = await_one_shot(promise).await?;
270        Ok(value.as_string().unwrap_or_default())
271    }
272}