telegram_webapp_sdk/webapp/
dialogs.rs1use js_sys::{Function, Object, Reflect};
5use wasm_bindgen::{JsCast, JsValue, prelude::Closure};
6
7use crate::webapp::{
8 TelegramWebApp,
9 core::{await_one_shot, one_shot_promise}
10};
11
12impl TelegramWebApp {
13 pub fn show_alert(&self, msg: &str) -> Result<(), JsValue> {
18 self.call1("showAlert", &msg.into())
19 }
20
21 pub fn show_confirm_with_callback<F>(&self, msg: &str, on_confirm: F) -> Result<(), JsValue>
26 where
27 F: 'static + FnOnce(bool)
28 {
29 let cb = Closure::once_into_js(move |v: JsValue| {
30 on_confirm(v.as_bool().unwrap_or(false));
31 });
32 let f = Reflect::get(&self.inner, &"showConfirm".into())?;
33 let func = f
34 .dyn_ref::<Function>()
35 .ok_or_else(|| JsValue::from_str("showConfirm is not a function"))?;
36 func.call2(&self.inner, &msg.into(), &cb)?;
37 Ok(())
38 }
39
40 pub async fn show_confirm(&self, msg: &str) -> Result<bool, JsValue> {
46 let webapp = self.inner.clone();
47 let msg = msg.to_owned();
48 let promise = one_shot_promise(move |resolve, _reject| {
49 let cb = Closure::once_into_js(move |v: JsValue| {
50 let _ = resolve.call1(&JsValue::NULL, &v);
51 });
52 let f = Reflect::get(&webapp, &"showConfirm".into())?;
53 let func = f
54 .dyn_ref::<Function>()
55 .ok_or_else(|| JsValue::from_str("showConfirm is not a function"))?;
56 func.call2(&webapp, &msg.into(), &cb)?;
57 Ok(())
58 });
59 let value = await_one_shot(promise).await?;
60 Ok(value.as_bool().unwrap_or(false))
61 }
62
63 pub fn show_popup_with_callback<F>(&self, params: &JsValue, callback: F) -> Result<(), JsValue>
78 where
79 F: 'static + FnOnce(String)
80 {
81 let cb = Closure::once_into_js(move |id: JsValue| {
82 callback(id.as_string().unwrap_or_default());
83 });
84 Reflect::get(&self.inner, &"showPopup".into())?
85 .dyn_into::<Function>()?
86 .call2(&self.inner, params, &cb)?;
87 Ok(())
88 }
89
90 pub async fn show_popup(&self, params: &JsValue) -> Result<String, JsValue> {
96 let webapp = self.inner.clone();
97 let params = params.clone();
98 let promise = one_shot_promise(move |resolve, _reject| {
99 let cb = Closure::once_into_js(move |id: JsValue| {
100 let _ = resolve.call1(&JsValue::NULL, &id);
101 });
102 Reflect::get(&webapp, &"showPopup".into())?
103 .dyn_into::<Function>()?
104 .call2(&webapp, ¶ms, &cb)?;
105 Ok(())
106 });
107 let value = await_one_shot(promise).await?;
108 Ok(value.as_string().unwrap_or_default())
109 }
110
111 pub fn show_scan_qr_popup_with_callback<F>(
127 &self,
128 text: &str,
129 callback: F
130 ) -> Result<(), JsValue>
131 where
132 F: 'static + FnOnce(String)
133 {
134 let cb = Closure::once_into_js(move |value: JsValue| {
135 callback(value.as_string().unwrap_or_default());
136 });
137 let params = Object::new();
138 Reflect::set(¶ms, &"text".into(), &text.into())?;
139 Reflect::get(&self.inner, &"showScanQrPopup".into())?
140 .dyn_into::<Function>()?
141 .call2(&self.inner, ¶ms, &cb)?;
142 Ok(())
143 }
144
145 pub async fn show_scan_qr_popup(&self, text: &str) -> Result<String, JsValue> {
151 let webapp = self.inner.clone();
152 let text = text.to_owned();
153 let promise = one_shot_promise(move |resolve, _reject| {
154 let cb = Closure::once_into_js(move |value: JsValue| {
155 let _ = resolve.call1(&JsValue::NULL, &value);
156 });
157 let params = Object::new();
158 Reflect::set(¶ms, &"text".into(), &text.into())?;
159 Reflect::get(&webapp, &"showScanQrPopup".into())?
160 .dyn_into::<Function>()?
161 .call2(&webapp, ¶ms, &cb)?;
162 Ok(())
163 });
164 let value = await_one_shot(promise).await?;
165 Ok(value.as_string().unwrap_or_default())
166 }
167
168 pub fn close_scan_qr_popup(&self) -> Result<(), JsValue> {
177 Reflect::get(&self.inner, &"closeScanQrPopup".into())?
178 .dyn_into::<Function>()?
179 .call0(&self.inner)?;
180 Ok(())
181 }
182}
183
184#[cfg(test)]
185mod tests {
186 use js_sys::{Function, Object, Reflect};
187 use wasm_bindgen::JsValue;
188 use wasm_bindgen_test::{wasm_bindgen_test, wasm_bindgen_test_configure};
189 use web_sys::window;
190
191 use crate::webapp::TelegramWebApp;
192
193 wasm_bindgen_test_configure!(run_in_browser);
194
195 fn setup_webapp() -> Object {
196 let win = window().expect("window");
197 let telegram = Object::new();
198 let webapp = Object::new();
199 let _ = Reflect::set(&win, &"Telegram".into(), &telegram);
200 let _ = Reflect::set(&telegram, &"WebApp".into(), &webapp);
201 webapp
202 }
203
204 #[wasm_bindgen_test]
205 #[allow(dead_code, clippy::unused_unit)]
206 fn show_scan_qr_popup_passes_params_as_object_with_text() {
207 let webapp = setup_webapp();
208 let capture = Function::new_with_args("params, _cb", "this.captured_params = params;");
209 let _ = Reflect::set(&webapp, &"showScanQrPopup".into(), &capture);
210
211 let app = TelegramWebApp::instance().expect("instance");
212 app.show_scan_qr_popup_with_callback("Scan", |_| {})
213 .expect("ok");
214
215 let params = Reflect::get(&webapp, &"captured_params".into()).expect("captured");
216 assert!(!params.is_undefined(), "scan params must be an object");
217 let text = Reflect::get(¶ms, &"text".into())
218 .expect("text field")
219 .as_string();
220 assert_eq!(text.as_deref(), Some("Scan"));
221 }
222
223 #[wasm_bindgen_test]
224 #[allow(dead_code, clippy::unused_unit)]
225 fn show_alert_passes_message() {
226 let webapp = setup_webapp();
227 let capture = Function::new_with_args("msg", "this.captured_alert = msg;");
228 let _ = Reflect::set(&webapp, &"showAlert".into(), &capture);
229
230 let app = TelegramWebApp::instance().expect("instance");
231 app.show_alert("Heads up").expect("ok");
232
233 assert_eq!(
234 Reflect::get(&webapp, &"captured_alert".into())
235 .unwrap()
236 .as_string()
237 .as_deref(),
238 Some("Heads up")
239 );
240 }
241
242 #[wasm_bindgen_test]
243 #[allow(dead_code, clippy::unused_unit)]
244 fn show_confirm_passes_message_and_routes_boolean_back() {
245 let webapp = setup_webapp();
246 let invoke = Function::new_with_args("msg, cb", "this.captured_confirm = msg; cb(true);");
247 let _ = Reflect::set(&webapp, &"showConfirm".into(), &invoke);
248
249 let app = TelegramWebApp::instance().expect("instance");
250 let received = std::rc::Rc::new(std::cell::Cell::new(false));
251 let received_ref = received.clone();
252 app.show_confirm_with_callback("Proceed?", move |ok| received_ref.set(ok))
253 .expect("ok");
254
255 assert_eq!(
256 Reflect::get(&webapp, &"captured_confirm".into())
257 .unwrap()
258 .as_string()
259 .as_deref(),
260 Some("Proceed?")
261 );
262 assert!(received.get());
263 }
264
265 #[wasm_bindgen_test]
266 #[allow(dead_code, clippy::unused_unit)]
267 fn close_scan_qr_popup_calls_js() {
268 let webapp = setup_webapp();
269 let called = std::rc::Rc::new(std::cell::Cell::new(false));
270 let called_ref = called.clone();
271 let close =
272 wasm_bindgen::closure::Closure::<dyn FnMut()>::new(move || called_ref.set(true));
273 let _ = Reflect::set(
274 &webapp,
275 &"closeScanQrPopup".into(),
276 wasm_bindgen::JsCast::unchecked_ref::<Function>(close.as_ref())
277 );
278 close.forget();
279
280 let app = TelegramWebApp::instance().expect("instance");
281 app.close_scan_qr_popup().expect("ok");
282 assert!(called.get());
283 }
284
285 #[wasm_bindgen_test]
286 #[allow(dead_code, clippy::unused_unit)]
287 fn show_scan_qr_popup_callback_receives_scanned_text() {
288 let webapp = setup_webapp();
289 let invoke = Function::new_with_args("_params, cb", "cb('payload');");
292 let _ = Reflect::set(&webapp, &"showScanQrPopup".into(), &invoke);
293
294 let app = TelegramWebApp::instance().expect("instance");
295 let captured = std::rc::Rc::new(std::cell::RefCell::new(String::new()));
296 let captured_ref = captured.clone();
297 app.show_scan_qr_popup_with_callback("", move |t| {
298 *captured_ref.borrow_mut() = t;
299 })
300 .expect("ok");
301
302 assert_eq!(captured.borrow().as_str(), "payload");
303 let _ = JsValue::null();
304 }
305}