telegram_webapp_sdk/api/
biometric.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, prelude::*};
6use web_sys::window;
7
8/// Calls `Telegram.WebApp.BiometricManager.init()`.
9///
10/// # Errors
11/// Returns `Err(JsValue)` if `BiometricManager` or the method is unavailable,
12/// or if the call fails.
13///
14/// # Examples
15/// ```no_run
16/// use telegram_webapp_sdk::api::biometric::init;
17///
18/// let _ = init();
19/// ```
20pub fn init() -> Result<(), JsValue> {
21    let biom = biometric_object()?;
22    let func = Reflect::get(&biom, &JsValue::from_str("init"))?.dyn_into::<Function>()?;
23    func.call0(&biom)?;
24    Ok(())
25}
26
27/// Calls `Telegram.WebApp.BiometricManager.requestAccess(auth_key, reason,
28/// options)`.
29///
30/// # Errors
31/// Returns `Err(JsValue)` if `BiometricManager` or the method is unavailable,
32/// or if the call fails.
33///
34/// # Examples
35/// ```no_run
36/// use telegram_webapp_sdk::api::biometric::request_access;
37///
38/// let _ = request_access("auth-key", None, None);
39/// ```
40pub fn request_access(
41    auth_key: &str,
42    reason: Option<&str>,
43    options: Option<&JsValue>
44) -> Result<(), JsValue> {
45    let biom = biometric_object()?;
46    let func = Reflect::get(&biom, &JsValue::from_str("requestAccess"))?.dyn_into::<Function>()?;
47    let key = JsValue::from_str(auth_key);
48    match (reason, options) {
49        (Some(r), Some(o)) => {
50            let r = JsValue::from_str(r);
51            func.call3(&biom, &key, &r, o)?;
52        }
53        (Some(r), None) => {
54            let r = JsValue::from_str(r);
55            func.call2(&biom, &key, &r)?;
56        }
57        (None, Some(o)) => {
58            func.call3(&biom, &key, &JsValue::UNDEFINED, o)?;
59        }
60        (None, None) => {
61            func.call1(&biom, &key)?;
62        }
63    }
64    Ok(())
65}
66
67/// Calls `Telegram.WebApp.BiometricManager.authenticate(auth_key, reason,
68/// options)`.
69///
70/// # Errors
71/// Returns `Err(JsValue)` if `BiometricManager` or the method is unavailable,
72/// or if the call fails.
73///
74/// # Examples
75/// ```no_run
76/// use telegram_webapp_sdk::api::biometric::authenticate;
77///
78/// let _ = authenticate("auth-key", None, None);
79/// ```
80pub fn authenticate(
81    auth_key: &str,
82    reason: Option<&str>,
83    options: Option<&JsValue>
84) -> Result<(), JsValue> {
85    let biom = biometric_object()?;
86    let func = Reflect::get(&biom, &JsValue::from_str("authenticate"))?.dyn_into::<Function>()?;
87    let key = JsValue::from_str(auth_key);
88    match (reason, options) {
89        (Some(r), Some(o)) => {
90            let r = JsValue::from_str(r);
91            func.call3(&biom, &key, &r, o)?;
92        }
93        (Some(r), None) => {
94            let r = JsValue::from_str(r);
95            func.call2(&biom, &key, &r)?;
96        }
97        (None, Some(o)) => {
98            func.call3(&biom, &key, &JsValue::UNDEFINED, o)?;
99        }
100        (None, None) => {
101            func.call1(&biom, &key)?;
102        }
103    }
104    Ok(())
105}
106
107/// Calls `Telegram.WebApp.BiometricManager.updateBiometricToken(token)`.
108///
109/// # Errors
110/// Returns `Err(JsValue)` if `BiometricManager` or the method is unavailable,
111/// or if the call fails.
112///
113/// # Examples
114/// ```no_run
115/// use telegram_webapp_sdk::api::biometric::update_biometric_token;
116///
117/// let _ = update_biometric_token("token");
118/// ```
119pub fn update_biometric_token(token: &str) -> Result<(), JsValue> {
120    let biom = biometric_object()?;
121    let func =
122        Reflect::get(&biom, &JsValue::from_str("updateBiometricToken"))?.dyn_into::<Function>()?;
123    func.call1(&biom, &JsValue::from_str(token))?;
124    Ok(())
125}
126
127/// Calls `Telegram.WebApp.BiometricManager.openSettings()`.
128///
129/// # Errors
130/// Returns `Err(JsValue)` if `BiometricManager` or the method is unavailable,
131/// or if the call fails.
132///
133/// # Examples
134/// ```no_run
135/// use telegram_webapp_sdk::api::biometric::open_settings;
136///
137/// let _ = open_settings();
138/// ```
139pub fn open_settings() -> Result<(), JsValue> {
140    let biom = biometric_object()?;
141    let func = Reflect::get(&biom, &JsValue::from_str("openSettings"))?.dyn_into::<Function>()?;
142    func.call0(&biom)?;
143    Ok(())
144}
145
146/// Returns `Telegram.WebApp.BiometricManager.isInited`.
147///
148/// # Errors
149/// Returns `Err(JsValue)` if the property is unavailable or not a boolean.
150///
151/// # Examples
152/// ```no_run
153/// use telegram_webapp_sdk::api::biometric::is_inited;
154///
155/// let _ = is_inited();
156/// ```
157pub fn is_inited() -> Result<bool, JsValue> {
158    let biom = biometric_object()?;
159    let value = Reflect::get(&biom, &JsValue::from_str("isInited"))?;
160    value
161        .as_bool()
162        .ok_or_else(|| JsValue::from_str("isInited not a bool"))
163}
164
165/// Returns `Telegram.WebApp.BiometricManager.isBiometricAvailable`.
166///
167/// # Errors
168/// Returns `Err(JsValue)` if the property is unavailable or not a boolean.
169///
170/// # Examples
171/// ```no_run
172/// use telegram_webapp_sdk::api::biometric::is_biometric_available;
173///
174/// let _ = is_biometric_available();
175/// ```
176pub fn is_biometric_available() -> Result<bool, JsValue> {
177    let biom = biometric_object()?;
178    let value = Reflect::get(&biom, &JsValue::from_str("isBiometricAvailable"))?;
179    value
180        .as_bool()
181        .ok_or_else(|| JsValue::from_str("isBiometricAvailable not a bool"))
182}
183
184/// Returns `Telegram.WebApp.BiometricManager.isAccessRequested`.
185///
186/// # Errors
187/// Returns `Err(JsValue)` if the property is unavailable or not a boolean.
188///
189/// # Examples
190/// ```no_run
191/// use telegram_webapp_sdk::api::biometric::is_access_requested;
192///
193/// let _ = is_access_requested();
194/// ```
195pub fn is_access_requested() -> Result<bool, JsValue> {
196    let biom = biometric_object()?;
197    let value = Reflect::get(&biom, &JsValue::from_str("isAccessRequested"))?;
198    value
199        .as_bool()
200        .ok_or_else(|| JsValue::from_str("isAccessRequested not a bool"))
201}
202
203/// Returns `Telegram.WebApp.BiometricManager.isAccessGranted`.
204///
205/// # Errors
206/// Returns `Err(JsValue)` if the property is unavailable or not a boolean.
207///
208/// # Examples
209/// ```no_run
210/// use telegram_webapp_sdk::api::biometric::is_access_granted;
211///
212/// let _ = is_access_granted();
213/// ```
214pub fn is_access_granted() -> Result<bool, JsValue> {
215    let biom = biometric_object()?;
216    let value = Reflect::get(&biom, &JsValue::from_str("isAccessGranted"))?;
217    value
218        .as_bool()
219        .ok_or_else(|| JsValue::from_str("isAccessGranted not a bool"))
220}
221
222/// Returns `Telegram.WebApp.BiometricManager.isBiometricTokenSaved`.
223///
224/// # Errors
225/// Returns `Err(JsValue)` if the property is unavailable or not a boolean.
226///
227/// # Examples
228/// ```no_run
229/// use telegram_webapp_sdk::api::biometric::is_biometric_token_saved;
230///
231/// let _ = is_biometric_token_saved();
232/// ```
233pub fn is_biometric_token_saved() -> Result<bool, JsValue> {
234    let biom = biometric_object()?;
235    let value = Reflect::get(&biom, &JsValue::from_str("isBiometricTokenSaved"))?;
236    value
237        .as_bool()
238        .ok_or_else(|| JsValue::from_str("isBiometricTokenSaved not a bool"))
239}
240
241/// Returns `Telegram.WebApp.BiometricManager.deviceId`.
242///
243/// # Errors
244/// Returns `Err(JsValue)` if the property is unavailable or not a string.
245///
246/// # Examples
247/// ```no_run
248/// use telegram_webapp_sdk::api::biometric::device_id;
249///
250/// let _ = device_id();
251/// ```
252pub fn device_id() -> Result<String, JsValue> {
253    let biom = biometric_object()?;
254    let value = Reflect::get(&biom, &JsValue::from_str("deviceId"))?;
255    value
256        .as_string()
257        .ok_or_else(|| JsValue::from_str("deviceId not a string"))
258}
259
260fn biometric_object() -> Result<JsValue, JsValue> {
261    let win = window().ok_or_else(|| JsValue::from_str("no window"))?;
262    let tg = Reflect::get(&win, &JsValue::from_str("Telegram"))?;
263    let webapp = Reflect::get(&tg, &JsValue::from_str("WebApp"))?;
264    Reflect::get(&webapp, &JsValue::from_str("BiometricManager"))
265}
266
267#[cfg(test)]
268mod tests {
269    use js_sys::{Function, Object, Reflect};
270    use wasm_bindgen::JsValue;
271    use wasm_bindgen_test::{wasm_bindgen_test, wasm_bindgen_test_configure};
272    use web_sys::window;
273
274    use super::*;
275
276    wasm_bindgen_test_configure!(run_in_browser);
277
278    #[allow(dead_code)]
279    fn setup_biometric() -> Object {
280        let win = window().expect("window should be available");
281        let telegram = Object::new();
282        let webapp = Object::new();
283        let biom = Object::new();
284        let _ = Reflect::set(&win, &"Telegram".into(), &telegram);
285        let _ = Reflect::set(&telegram, &"WebApp".into(), &webapp);
286        let _ = Reflect::set(&webapp, &"BiometricManager".into(), &biom);
287        biom
288    }
289
290    #[wasm_bindgen_test]
291    #[allow(dead_code, clippy::unused_unit)]
292    fn init_ok() {
293        let biom = setup_biometric();
294        let func = Function::new_no_args("this.called = true;");
295        let _ = Reflect::set(&biom, &"init".into(), &func);
296        assert!(init().is_ok());
297        assert!(
298            Reflect::get(&biom, &"called".into())
299                .unwrap()
300                .as_bool()
301                .unwrap()
302        );
303    }
304
305    #[wasm_bindgen_test]
306    #[allow(dead_code, clippy::unused_unit)]
307    fn init_err() {
308        let _ = setup_biometric();
309        assert!(init().is_err());
310    }
311
312    #[wasm_bindgen_test]
313    #[allow(dead_code, clippy::unused_unit)]
314    fn request_access_ok() {
315        let biom = setup_biometric();
316        let func = Function::new_with_args("key", "this.called = true; this.key = key;");
317        let _ = Reflect::set(&biom, &"requestAccess".into(), &func);
318        assert!(request_access("abc", None, None).is_ok());
319        assert!(
320            Reflect::get(&biom, &"called".into())
321                .unwrap()
322                .as_bool()
323                .unwrap()
324        );
325        assert_eq!(
326            Reflect::get(&biom, &"key".into())
327                .unwrap()
328                .as_string()
329                .unwrap(),
330            "abc"
331        );
332    }
333
334    #[wasm_bindgen_test]
335    #[allow(dead_code, clippy::unused_unit)]
336    fn request_access_err() {
337        let _ = setup_biometric();
338        assert!(request_access("abc", None, None).is_err());
339    }
340
341    #[wasm_bindgen_test]
342    #[allow(dead_code, clippy::unused_unit)]
343    fn authenticate_ok() {
344        let biom = setup_biometric();
345        let func = Function::new_with_args("key", "this.called = true; this.key = key;");
346        let _ = Reflect::set(&biom, &"authenticate".into(), &func);
347        assert!(authenticate("abc", None, None).is_ok());
348        assert!(
349            Reflect::get(&biom, &"called".into())
350                .unwrap()
351                .as_bool()
352                .unwrap()
353        );
354        assert_eq!(
355            Reflect::get(&biom, &"key".into())
356                .unwrap()
357                .as_string()
358                .unwrap(),
359            "abc"
360        );
361        assert_eq!(
362            Reflect::get(&biom, &"reason".into())
363                .unwrap()
364                .as_string()
365                .unwrap(),
366            "why"
367        );
368    }
369
370    #[wasm_bindgen_test]
371    #[allow(dead_code, clippy::unused_unit)]
372    fn authenticate_err() {
373        let _ = setup_biometric();
374        assert!(authenticate("abc", None, None).is_err());
375    }
376
377    #[wasm_bindgen_test]
378    #[allow(dead_code, clippy::unused_unit)]
379    fn update_biometric_token_ok() {
380        let biom = setup_biometric();
381        let func = Function::new_with_args("token", "this.token = token;");
382        let _ = Reflect::set(&biom, &"updateBiometricToken".into(), &func);
383        assert!(update_biometric_token("abc").is_ok());
384        assert_eq!(
385            Reflect::get(&biom, &"token".into())
386                .unwrap()
387                .as_string()
388                .unwrap(),
389            "abc"
390        );
391    }
392
393    #[wasm_bindgen_test]
394    #[allow(dead_code, clippy::unused_unit)]
395    fn update_biometric_token_err() {
396        let _ = setup_biometric();
397        assert!(update_biometric_token("abc").is_err());
398    }
399
400    #[wasm_bindgen_test]
401    #[allow(dead_code, clippy::unused_unit)]
402    fn open_settings_ok() {
403        let biom = setup_biometric();
404        let func = Function::new_no_args("this.called = true;");
405        let _ = Reflect::set(&biom, &"openSettings".into(), &func);
406        assert!(open_settings().is_ok());
407        assert!(
408            Reflect::get(&biom, &"called".into())
409                .unwrap()
410                .as_bool()
411                .unwrap()
412        );
413    }
414
415    #[wasm_bindgen_test]
416    #[allow(dead_code, clippy::unused_unit)]
417    fn open_settings_err() {
418        let _ = setup_biometric();
419        assert!(open_settings().is_err());
420    }
421
422    #[wasm_bindgen_test]
423    #[allow(dead_code, clippy::unused_unit)]
424    fn is_inited_ok() {
425        let biom = setup_biometric();
426        let _ = Reflect::set(&biom, &"isInited".into(), &JsValue::from(true));
427        assert!(is_inited().expect("is_inited"));
428    }
429
430    #[wasm_bindgen_test]
431    #[allow(dead_code, clippy::unused_unit)]
432    fn is_inited_err() {
433        let _ = setup_biometric();
434        assert!(is_inited().is_err());
435    }
436
437    #[wasm_bindgen_test]
438    #[allow(dead_code, clippy::unused_unit)]
439    fn is_biometric_available_ok() {
440        let biom = setup_biometric();
441        let _ = Reflect::set(&biom, &"isBiometricAvailable".into(), &JsValue::from(true));
442        assert!(is_biometric_available().expect("is_biometric_available"));
443    }
444
445    #[wasm_bindgen_test]
446    #[allow(dead_code, clippy::unused_unit)]
447    fn is_biometric_available_err() {
448        let _ = setup_biometric();
449        assert!(is_biometric_available().is_err());
450    }
451
452    #[wasm_bindgen_test]
453    #[allow(dead_code, clippy::unused_unit)]
454    fn is_access_requested_ok() {
455        let biom = setup_biometric();
456        let _ = Reflect::set(&biom, &"isAccessRequested".into(), &JsValue::from(true));
457        assert!(is_access_requested().expect("is_access_requested"));
458    }
459
460    #[wasm_bindgen_test]
461    #[allow(dead_code, clippy::unused_unit)]
462    fn is_access_requested_err() {
463        let _ = setup_biometric();
464        assert!(is_access_requested().is_err());
465    }
466
467    #[wasm_bindgen_test]
468    #[allow(dead_code, clippy::unused_unit)]
469    fn is_access_granted_ok() {
470        let biom = setup_biometric();
471        let _ = Reflect::set(&biom, &"isAccessGranted".into(), &JsValue::from(true));
472        assert!(is_access_granted().expect("is_access_granted"));
473    }
474
475    #[wasm_bindgen_test]
476    #[allow(dead_code, clippy::unused_unit)]
477    fn is_access_granted_err() {
478        let _ = setup_biometric();
479        assert!(is_access_granted().is_err());
480    }
481
482    #[wasm_bindgen_test]
483    #[allow(dead_code, clippy::unused_unit)]
484    fn is_biometric_token_saved_ok() {
485        let biom = setup_biometric();
486        let _ = Reflect::set(&biom, &"isBiometricTokenSaved".into(), &JsValue::from(true));
487        assert!(is_biometric_token_saved().expect("is_biometric_token_saved"));
488    }
489
490    #[wasm_bindgen_test]
491    #[allow(dead_code, clippy::unused_unit)]
492    fn is_biometric_token_saved_err() {
493        let _ = setup_biometric();
494        assert!(is_biometric_token_saved().is_err());
495    }
496
497    #[wasm_bindgen_test]
498    #[allow(dead_code, clippy::unused_unit)]
499    fn device_id_ok() {
500        let biom = setup_biometric();
501        let _ = Reflect::set(&biom, &"deviceId".into(), &JsValue::from_str("id123"));
502        assert_eq!(device_id().expect("device_id"), "id123");
503    }
504
505    #[wasm_bindgen_test]
506    #[allow(dead_code, clippy::unused_unit)]
507    fn device_id_err() {
508        let _ = setup_biometric();
509        assert!(device_id().is_err());
510    }
511}