telegram_webapp_sdk/api/
gyroscope.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
8use super::events;
9
10/// Angular velocity around three axes in radians per second.
11#[derive(Debug, Clone, Copy, PartialEq)]
12pub struct AngularVelocity {
13    /// Rotation rate around the X axis.
14    pub x: f64,
15    /// Rotation rate around the Y axis.
16    pub y: f64,
17    /// Rotation rate around the Z axis.
18    pub z: f64
19}
20
21impl AngularVelocity {
22    #[allow(dead_code)]
23    const fn new(x: f64, y: f64, z: f64) -> Self {
24        Self {
25            x,
26            y,
27            z
28        }
29    }
30}
31
32/// Starts the gyroscope.
33///
34/// # Errors
35/// Returns [`JsValue`] if the JavaScript call fails or the sensor is
36/// unavailable.
37///
38/// # Examples
39/// ```no_run
40/// # use telegram_webapp_sdk::api::gyroscope::start;
41/// start()?;
42/// # Ok::<(), wasm_bindgen::JsValue>(())
43/// ```
44pub fn start() -> Result<(), JsValue> {
45    let gyro = gyroscope_object()?;
46    let func = Reflect::get(&gyro, &"start".into())?.dyn_into::<Function>()?;
47    func.call0(&gyro)?;
48    Ok(())
49}
50
51/// Stops the gyroscope.
52///
53/// # Errors
54/// Returns [`JsValue`] if the JavaScript call fails or the sensor is
55/// unavailable.
56///
57/// # Examples
58/// ```no_run
59/// # use telegram_webapp_sdk::api::gyroscope::stop;
60/// stop()?;
61/// # Ok::<(), wasm_bindgen::JsValue>(())
62/// ```
63pub fn stop() -> Result<(), JsValue> {
64    let gyro = gyroscope_object()?;
65    let func = Reflect::get(&gyro, &"stop".into())?.dyn_into::<Function>()?;
66    func.call0(&gyro)?;
67    Ok(())
68}
69
70/// Reads the current angular velocity values.
71///
72/// # Examples
73/// ```no_run
74/// # use telegram_webapp_sdk::api::gyroscope::get_angular_velocity;
75/// let _ = get_angular_velocity();
76/// ```
77pub fn get_angular_velocity() -> Option<AngularVelocity> {
78    let gyro = gyroscope_object().ok()?;
79    let x = Reflect::get(&gyro, &"x".into()).ok()?.as_f64()?;
80    let y = Reflect::get(&gyro, &"y".into()).ok()?.as_f64()?;
81    let z = Reflect::get(&gyro, &"z".into()).ok()?.as_f64()?;
82    Some(AngularVelocity {
83        x,
84        y,
85        z
86    })
87}
88
89/// Registers a callback for `gyroscopeStarted` event.
90pub fn on_started(callback: &Closure<dyn Fn()>) -> Result<(), JsValue> {
91    events::on_event("gyroscopeStarted", callback)
92}
93
94/// Registers a callback for `gyroscopeChanged` event.
95pub fn on_changed(callback: &Closure<dyn Fn()>) -> Result<(), JsValue> {
96    events::on_event("gyroscopeChanged", callback)
97}
98
99/// Registers a callback for `gyroscopeStopped` event.
100pub fn on_stopped(callback: &Closure<dyn Fn()>) -> Result<(), JsValue> {
101    events::on_event("gyroscopeStopped", callback)
102}
103
104/// Registers a callback for `gyroscopeFailed` event.
105pub fn on_failed(callback: &Closure<dyn Fn()>) -> Result<(), JsValue> {
106    events::on_event("gyroscopeFailed", callback)
107}
108
109fn gyroscope_object() -> Result<JsValue, JsValue> {
110    let win = window().ok_or_else(|| JsValue::from_str("no window"))?;
111    let tg = Reflect::get(&win, &"Telegram".into())?;
112    let webapp = Reflect::get(&tg, &"WebApp".into())?;
113    Reflect::get(&webapp, &"Gyroscope".into())
114}
115
116#[cfg(test)]
117#[allow(dead_code)]
118mod tests {
119    use js_sys::{Function, Object, Reflect};
120    use wasm_bindgen::{JsValue, closure::Closure};
121    use wasm_bindgen_test::{wasm_bindgen_test, wasm_bindgen_test_configure};
122    use web_sys::window;
123
124    use super::*;
125
126    wasm_bindgen_test_configure!(run_in_browser);
127
128    #[allow(dead_code)]
129    fn setup_gyroscope() -> (Object, Object) {
130        let win = window().unwrap();
131        let telegram = Object::new();
132        let webapp = Object::new();
133        let gyro = Object::new();
134        let _ = Reflect::set(&win, &"Telegram".into(), &telegram);
135        let _ = Reflect::set(&telegram, &"WebApp".into(), &webapp);
136        let _ = Reflect::set(&webapp, &"Gyroscope".into(), &gyro);
137        (webapp, gyro)
138    }
139
140    #[wasm_bindgen_test]
141    #[allow(clippy::unused_unit)]
142    fn start_ok() {
143        let (_webapp, gyro) = setup_gyroscope();
144        let func = Function::new_no_args("this.called = true;");
145        let _ = Reflect::set(&gyro, &"start".into(), &func);
146        assert!(start().is_ok());
147        let called = Reflect::get(&gyro, &"called".into()).unwrap();
148        assert_eq!(called.as_bool(), Some(true));
149    }
150
151    #[wasm_bindgen_test]
152    #[allow(clippy::unused_unit)]
153    fn start_err() {
154        let (_webapp, gyro) = setup_gyroscope();
155        let _ = Reflect::set(&gyro, &"start".into(), &JsValue::from_f64(1.0));
156        assert!(start().is_err());
157    }
158
159    #[wasm_bindgen_test]
160    #[allow(clippy::unused_unit)]
161    fn stop_ok() {
162        let (_webapp, gyro) = setup_gyroscope();
163        let func = Function::new_no_args("this.stopped = true;");
164        let _ = Reflect::set(&gyro, &"stop".into(), &func);
165        assert!(stop().is_ok());
166        let stopped = Reflect::get(&gyro, &"stopped".into()).unwrap();
167        assert_eq!(stopped.as_bool(), Some(true));
168    }
169
170    #[wasm_bindgen_test]
171    fn get_angular_velocity_ok() {
172        let (_webapp, gyro) = setup_gyroscope();
173        let _ = Reflect::set(&gyro, &"x".into(), &JsValue::from_f64(0.1));
174        let _ = Reflect::set(&gyro, &"y".into(), &JsValue::from_f64(0.2));
175        let _ = Reflect::set(&gyro, &"z".into(), &JsValue::from_f64(0.3));
176        let result = get_angular_velocity().unwrap();
177        assert_eq!(
178            result,
179            AngularVelocity {
180                x: 0.1,
181                y: 0.2,
182                z: 0.3
183            }
184        );
185    }
186
187    #[wasm_bindgen_test]
188    fn registers_callbacks() {
189        let (webapp, _gyro) = setup_gyroscope();
190        let on_event = Function::new_with_args("name, cb", "this[name] = cb;");
191        let _ = Reflect::set(&webapp, &"onEvent".into(), &on_event);
192        let cb = Closure::wrap(Box::new(|| {}) as Box<dyn Fn()>);
193        on_started(&cb).expect("on_started");
194        on_changed(&cb).expect("on_changed");
195        on_stopped(&cb).expect("on_stopped");
196        on_failed(&cb).expect("on_failed");
197        assert!(Reflect::has(&webapp, &"gyroscopeStarted".into()).unwrap());
198        assert!(Reflect::has(&webapp, &"gyroscopeChanged".into()).unwrap());
199        assert!(Reflect::has(&webapp, &"gyroscopeStopped".into()).unwrap());
200        assert!(Reflect::has(&webapp, &"gyroscopeFailed".into()).unwrap());
201        cb.forget();
202    }
203}