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