telegram_webapp_sdk/api/
device_orientation.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/// Device orientation angles in degrees.
11#[derive(Debug, Clone, Copy, PartialEq)]
12pub struct Orientation {
13    /// Rotation around the Z axis.
14    pub alpha: f64,
15    /// Rotation around the X axis.
16    pub beta:  f64,
17    /// Rotation around the Y axis.
18    pub gamma: f64
19}
20
21impl Orientation {
22    #[allow(dead_code)]
23    const fn new(alpha: f64, beta: f64, gamma: f64) -> Self {
24        Self {
25            alpha,
26            beta,
27            gamma
28        }
29    }
30}
31
32/// Starts the device orientation sensor.
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::device_orientation::start;
41/// start()?;
42/// # Ok::<(), wasm_bindgen::JsValue>(())
43/// ```
44pub fn start() -> Result<(), JsValue> {
45    let orientation = device_orientation_object()?;
46    let func = Reflect::get(&orientation, &"start".into())?.dyn_into::<Function>()?;
47    func.call0(&orientation)?;
48    Ok(())
49}
50
51/// Stops the device orientation sensor.
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::device_orientation::stop;
60/// stop()?;
61/// # Ok::<(), wasm_bindgen::JsValue>(())
62/// ```
63pub fn stop() -> Result<(), JsValue> {
64    let orientation = device_orientation_object()?;
65    let func = Reflect::get(&orientation, &"stop".into())?.dyn_into::<Function>()?;
66    func.call0(&orientation)?;
67    Ok(())
68}
69
70/// Reads the current device orientation angles.
71///
72/// # Examples
73/// ```no_run
74/// # use telegram_webapp_sdk::api::device_orientation::get_orientation;
75/// let _ = get_orientation();
76/// ```
77pub fn get_orientation() -> Option<Orientation> {
78    let orientation = device_orientation_object().ok()?;
79    let alpha = Reflect::get(&orientation, &"alpha".into()).ok()?.as_f64()?;
80    let beta = Reflect::get(&orientation, &"beta".into()).ok()?.as_f64()?;
81    let gamma = Reflect::get(&orientation, &"gamma".into()).ok()?.as_f64()?;
82    Some(Orientation {
83        alpha,
84        beta,
85        gamma
86    })
87}
88
89/// Registers a callback for `deviceOrientationStarted` event.
90pub fn on_started(callback: &Closure<dyn Fn()>) -> Result<(), JsValue> {
91    events::on_event("deviceOrientationStarted", callback)
92}
93
94/// Registers a callback for `deviceOrientationChanged` event.
95pub fn on_changed(callback: &Closure<dyn Fn()>) -> Result<(), JsValue> {
96    events::on_event("deviceOrientationChanged", callback)
97}
98
99/// Registers a callback for `deviceOrientationStopped` event.
100pub fn on_stopped(callback: &Closure<dyn Fn()>) -> Result<(), JsValue> {
101    events::on_event("deviceOrientationStopped", callback)
102}
103
104/// Registers a callback for `deviceOrientationFailed` event.
105pub fn on_failed(callback: &Closure<dyn Fn()>) -> Result<(), JsValue> {
106    events::on_event("deviceOrientationFailed", callback)
107}
108
109fn device_orientation_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, &"DeviceOrientation".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_device_orientation() -> (Object, Object) {
130        let win = window().unwrap();
131        let telegram = Object::new();
132        let webapp = Object::new();
133        let orientation = Object::new();
134        let _ = Reflect::set(&win, &"Telegram".into(), &telegram);
135        let _ = Reflect::set(&telegram, &"WebApp".into(), &webapp);
136        let _ = Reflect::set(&webapp, &"DeviceOrientation".into(), &orientation);
137        (webapp, orientation)
138    }
139
140    #[wasm_bindgen_test]
141    #[allow(clippy::unused_unit)]
142    fn start_ok() {
143        let (_webapp, orientation) = setup_device_orientation();
144        let func = Function::new_no_args("this.called = true;");
145        let _ = Reflect::set(&orientation, &"start".into(), &func);
146        assert!(start().is_ok());
147        let called = Reflect::get(&orientation, &"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, orientation) = setup_device_orientation();
155        let _ = Reflect::set(&orientation, &"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, orientation) = setup_device_orientation();
163        let func = Function::new_no_args("this.stopped = true;");
164        let _ = Reflect::set(&orientation, &"stop".into(), &func);
165        assert!(stop().is_ok());
166        let stopped = Reflect::get(&orientation, &"stopped".into()).unwrap();
167        assert_eq!(stopped.as_bool(), Some(true));
168    }
169
170    #[wasm_bindgen_test]
171    fn get_orientation_ok() {
172        let (_webapp, orientation) = setup_device_orientation();
173        let _ = Reflect::set(&orientation, &"alpha".into(), &JsValue::from_f64(10.0));
174        let _ = Reflect::set(&orientation, &"beta".into(), &JsValue::from_f64(20.0));
175        let _ = Reflect::set(&orientation, &"gamma".into(), &JsValue::from_f64(30.0));
176        let result = get_orientation().unwrap();
177        assert_eq!(
178            result,
179            Orientation {
180                alpha: 10.0,
181                beta:  20.0,
182                gamma: 30.0
183            }
184        );
185    }
186
187    #[wasm_bindgen_test]
188    fn registers_callbacks() {
189        let (webapp, _orientation) = setup_device_orientation();
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, &"deviceOrientationStarted".into()).unwrap());
198        assert!(Reflect::has(&webapp, &"deviceOrientationChanged".into()).unwrap());
199        assert!(Reflect::has(&webapp, &"deviceOrientationStopped".into()).unwrap());
200        assert!(Reflect::has(&webapp, &"deviceOrientationFailed".into()).unwrap());
201        cb.forget();
202    }
203}