telegram_webapp_sdk/webapp/
events.rs

1// SPDX-FileCopyrightText: 2025 RAprogramm <andrey.rozanov.vl@gmail.com>
2// SPDX-License-Identifier: MIT
3
4use js_sys::{Function, Reflect};
5use wasm_bindgen::{JsCast, JsValue, prelude::Closure};
6
7use crate::webapp::{
8    TelegramWebApp,
9    types::{BackgroundEvent, EventHandle}
10};
11
12impl TelegramWebApp {
13    /// Register event handler (`web_app_event_name`, callback).
14    ///
15    /// Returns an [`EventHandle`] that can be passed to
16    /// [`off_event`](Self::off_event).
17    ///
18    /// # Errors
19    /// Returns [`JsValue`] if the underlying JS call fails.
20    pub fn on_event<F>(
21        &self,
22        event: &str,
23        callback: F
24    ) -> Result<EventHandle<dyn FnMut(JsValue)>, JsValue>
25    where
26        F: 'static + Fn(JsValue)
27    {
28        let cb = Closure::<dyn FnMut(JsValue)>::new(callback);
29        let f = Reflect::get(&self.inner, &"onEvent".into())?;
30        let func = f
31            .dyn_ref::<Function>()
32            .ok_or_else(|| JsValue::from_str("onEvent is not a function"))?;
33        func.call2(&self.inner, &event.into(), cb.as_ref().unchecked_ref())?;
34        Ok(EventHandle::new(
35            self.inner.clone(),
36            "offEvent",
37            Some(event.to_owned()),
38            cb
39        ))
40    }
41
42    /// Register a callback for a background event.
43    ///
44    /// Returns an [`EventHandle`] that can be passed to
45    /// [`off_event`](Self::off_event).
46    ///
47    /// # Errors
48    /// Returns [`JsValue`] if the underlying JS call fails.
49    pub fn on_background_event<F>(
50        &self,
51        event: BackgroundEvent,
52        callback: F
53    ) -> Result<EventHandle<dyn FnMut(JsValue)>, JsValue>
54    where
55        F: 'static + Fn(JsValue)
56    {
57        let cb = Closure::<dyn FnMut(JsValue)>::new(callback);
58        let f = Reflect::get(&self.inner, &"onEvent".into())?;
59        let func = f
60            .dyn_ref::<Function>()
61            .ok_or_else(|| JsValue::from_str("onEvent is not a function"))?;
62        func.call2(
63            &self.inner,
64            &event.as_str().into(),
65            cb.as_ref().unchecked_ref()
66        )?;
67        Ok(EventHandle::new(
68            self.inner.clone(),
69            "offEvent",
70            Some(event.as_str().to_string()),
71            cb
72        ))
73    }
74
75    /// Deregister a previously registered event handler.
76    ///
77    /// # Errors
78    /// Returns [`JsValue`] if the underlying JS call fails.
79    pub fn off_event<T: ?Sized>(&self, handle: EventHandle<T>) -> Result<(), JsValue> {
80        handle.unregister()
81    }
82
83    /// Register a callback for theme changes.
84    ///
85    /// Returns an [`EventHandle`] that can be passed to
86    /// [`off_event`](Self::off_event).
87    ///
88    /// # Errors
89    /// Returns [`JsValue`] if the underlying JS call fails.
90    pub fn on_theme_changed<F>(&self, callback: F) -> Result<EventHandle<dyn FnMut()>, JsValue>
91    where
92        F: 'static + Fn()
93    {
94        let cb = Closure::<dyn FnMut()>::new(callback);
95        let f = Reflect::get(&self.inner, &"onEvent".into())?;
96        let func = f
97            .dyn_ref::<Function>()
98            .ok_or_else(|| JsValue::from_str("onEvent is not a function"))?;
99        func.call2(
100            &self.inner,
101            &"themeChanged".into(),
102            cb.as_ref().unchecked_ref()
103        )?;
104        Ok(EventHandle::new(
105            self.inner.clone(),
106            "offEvent",
107            Some("themeChanged".to_string()),
108            cb
109        ))
110    }
111
112    /// Register a callback for safe area changes.
113    ///
114    /// Returns an [`EventHandle`] that can be passed to
115    /// [`off_event`](Self::off_event).
116    ///
117    /// # Errors
118    /// Returns [`JsValue`] if the underlying JS call fails.
119    pub fn on_safe_area_changed<F>(&self, callback: F) -> Result<EventHandle<dyn FnMut()>, JsValue>
120    where
121        F: 'static + Fn()
122    {
123        let cb = Closure::<dyn FnMut()>::new(callback);
124        let f = Reflect::get(&self.inner, &"onEvent".into())?;
125        let func = f
126            .dyn_ref::<Function>()
127            .ok_or_else(|| JsValue::from_str("onEvent is not a function"))?;
128        func.call2(
129            &self.inner,
130            &"safeAreaChanged".into(),
131            cb.as_ref().unchecked_ref()
132        )?;
133        Ok(EventHandle::new(
134            self.inner.clone(),
135            "offEvent",
136            Some("safeAreaChanged".to_string()),
137            cb
138        ))
139    }
140
141    /// Register a callback for content safe area changes.
142    ///
143    /// Returns an [`EventHandle`] that can be passed to
144    /// [`off_event`](Self::off_event).
145    ///
146    /// # Errors
147    /// Returns [`JsValue`] if the underlying JS call fails.
148    pub fn on_content_safe_area_changed<F>(
149        &self,
150        callback: F
151    ) -> Result<EventHandle<dyn FnMut()>, JsValue>
152    where
153        F: 'static + Fn()
154    {
155        let cb = Closure::<dyn FnMut()>::new(callback);
156        let f = Reflect::get(&self.inner, &"onEvent".into())?;
157        let func = f
158            .dyn_ref::<Function>()
159            .ok_or_else(|| JsValue::from_str("onEvent is not a function"))?;
160        func.call2(
161            &self.inner,
162            &"contentSafeAreaChanged".into(),
163            cb.as_ref().unchecked_ref()
164        )?;
165        Ok(EventHandle::new(
166            self.inner.clone(),
167            "offEvent",
168            Some("contentSafeAreaChanged".to_string()),
169            cb
170        ))
171    }
172
173    /// Register a callback for viewport changes.
174    ///
175    /// Returns an [`EventHandle`] that can be passed to
176    /// [`off_event`](Self::off_event).
177    ///
178    /// # Errors
179    /// Returns [`JsValue`] if the underlying JS call fails.
180    pub fn on_viewport_changed<F>(&self, callback: F) -> Result<EventHandle<dyn FnMut()>, JsValue>
181    where
182        F: 'static + Fn()
183    {
184        let cb = Closure::<dyn FnMut()>::new(callback);
185        let f = Reflect::get(&self.inner, &"onEvent".into())?;
186        let func = f
187            .dyn_ref::<Function>()
188            .ok_or_else(|| JsValue::from_str("onEvent is not a function"))?;
189        func.call2(
190            &self.inner,
191            &"viewportChanged".into(),
192            cb.as_ref().unchecked_ref()
193        )?;
194        Ok(EventHandle::new(
195            self.inner.clone(),
196            "offEvent",
197            Some("viewportChanged".to_string()),
198            cb
199        ))
200    }
201
202    /// Register a callback for received clipboard text.
203    ///
204    /// Returns an [`EventHandle`] that can be passed to
205    /// [`off_event`](Self::off_event).
206    ///
207    /// # Errors
208    /// Returns [`JsValue`] if the underlying JS call fails.
209    pub fn on_clipboard_text_received<F>(
210        &self,
211        callback: F
212    ) -> Result<EventHandle<dyn FnMut(JsValue)>, JsValue>
213    where
214        F: 'static + Fn(String)
215    {
216        let cb = Closure::<dyn FnMut(JsValue)>::new(move |text: JsValue| {
217            callback(text.as_string().unwrap_or_default());
218        });
219        let f = Reflect::get(&self.inner, &"onEvent".into())?;
220        let func = f
221            .dyn_ref::<Function>()
222            .ok_or_else(|| JsValue::from_str("onEvent is not a function"))?;
223        func.call2(
224            &self.inner,
225            &"clipboardTextReceived".into(),
226            cb.as_ref().unchecked_ref()
227        )?;
228        Ok(EventHandle::new(
229            self.inner.clone(),
230            "offEvent",
231            Some("clipboardTextReceived".to_string()),
232            cb
233        ))
234    }
235
236    /// Register a callback for invoice payment result.
237    ///
238    /// Returns an [`EventHandle`] that can be passed to
239    /// [`off_event`](Self::off_event).
240    ///
241    /// # Examples
242    /// ```no_run
243    /// # use telegram_webapp_sdk::webapp::TelegramWebApp;
244    /// # let app = TelegramWebApp::instance().unwrap();
245    /// let handle = app
246    ///     .on_invoice_closed(|status| {
247    ///         let _ = status;
248    ///     })
249    ///     .unwrap();
250    /// app.off_event(handle).unwrap();
251    /// ```
252    ///
253    /// # Errors
254    /// Returns [`JsValue`] if the underlying JS call fails.
255    pub fn on_invoice_closed<F>(
256        &self,
257        callback: F
258    ) -> Result<EventHandle<dyn FnMut(String)>, JsValue>
259    where
260        F: 'static + Fn(String)
261    {
262        let cb = Closure::<dyn FnMut(String)>::new(callback);
263        let f = Reflect::get(&self.inner, &"onEvent".into())?;
264        let func = f
265            .dyn_ref::<Function>()
266            .ok_or_else(|| JsValue::from_str("onEvent is not a function"))?;
267        func.call2(
268            &self.inner,
269            &"invoiceClosed".into(),
270            cb.as_ref().unchecked_ref()
271        )?;
272        Ok(EventHandle::new(
273            self.inner.clone(),
274            "offEvent",
275            Some("invoiceClosed".to_string()),
276            cb
277        ))
278    }
279}