telegram_webapp_sdk/api/
device_storage.rs

1use js_sys::{Function, Promise, Reflect};
2use wasm_bindgen::{JsCast, JsValue};
3use wasm_bindgen_futures::JsFuture;
4use web_sys::window;
5
6/// Stores a value under the given key in Telegram's device storage.
7///
8/// # Errors
9/// Returns `Err(JsValue)` if the JavaScript call fails or `deviceStorage` is
10/// missing.
11///
12/// # Examples
13/// ```
14/// use telegram_webapp_sdk::api::device_storage::set;
15/// # async fn run() -> Result<(), wasm_bindgen::JsValue> {
16/// set("foo", "bar").await?;
17/// # Ok(()) }
18/// ```
19pub async fn set(key: &str, value: &str) -> Result<(), JsValue> {
20    let storage = device_storage_object()?;
21    let func = Reflect::get(&storage, &JsValue::from_str("set"))?.dyn_into::<Function>()?;
22    let promise = func
23        .call2(&storage, &JsValue::from_str(key), &JsValue::from_str(value))?
24        .dyn_into::<Promise>()?;
25    JsFuture::from(promise).await?;
26    Ok(())
27}
28
29/// Retrieves a value from Telegram's device storage.
30///
31/// # Errors
32/// Returns `Err(JsValue)` if the JavaScript call fails or `deviceStorage` is
33/// missing.
34///
35/// # Examples
36/// ```
37/// use telegram_webapp_sdk::api::device_storage::{get, set};
38/// # async fn run() -> Result<(), wasm_bindgen::JsValue> {
39/// set("foo", "bar").await?;
40/// let value = get("foo").await?;
41/// assert_eq!(value.as_deref(), Some("bar"));
42/// # Ok(()) }
43/// ```
44pub async fn get(key: &str) -> Result<Option<String>, JsValue> {
45    let storage = device_storage_object()?;
46    let func = Reflect::get(&storage, &JsValue::from_str("get"))?.dyn_into::<Function>()?;
47    let promise = func
48        .call1(&storage, &JsValue::from_str(key))?
49        .dyn_into::<Promise>()?;
50    let value = JsFuture::from(promise).await?;
51    Ok(value.as_string())
52}
53
54/// Removes a value from Telegram's device storage.
55///
56/// # Errors
57/// Returns `Err(JsValue)` if the JavaScript call fails or `deviceStorage` is
58/// missing.
59///
60/// # Examples
61/// ```
62/// use telegram_webapp_sdk::api::device_storage::{remove, set};
63/// # async fn run() -> Result<(), wasm_bindgen::JsValue> {
64/// set("foo", "bar").await?;
65/// remove("foo").await?;
66/// # Ok(()) }
67/// ```
68pub async fn remove(key: &str) -> Result<(), JsValue> {
69    let storage = device_storage_object()?;
70    let func = Reflect::get(&storage, &JsValue::from_str("remove"))?.dyn_into::<Function>()?;
71    let promise = func
72        .call1(&storage, &JsValue::from_str(key))?
73        .dyn_into::<Promise>()?;
74    JsFuture::from(promise).await?;
75    Ok(())
76}
77
78/// Clears all entries from Telegram's device storage.
79///
80/// # Errors
81/// Returns `Err(JsValue)` if the JavaScript call fails or `deviceStorage` is
82/// missing.
83///
84/// # Examples
85/// ```
86/// use telegram_webapp_sdk::api::device_storage::{clear, set};
87/// # async fn run() -> Result<(), wasm_bindgen::JsValue> {
88/// set("foo", "bar").await?;
89/// clear().await?;
90/// # Ok(()) }
91/// ```
92pub async fn clear() -> Result<(), JsValue> {
93    let storage = device_storage_object()?;
94    let func = Reflect::get(&storage, &JsValue::from_str("clear"))?.dyn_into::<Function>()?;
95    let promise = func.call0(&storage)?.dyn_into::<Promise>()?;
96    JsFuture::from(promise).await?;
97    Ok(())
98}
99
100fn device_storage_object() -> Result<JsValue, JsValue> {
101    let window = window().ok_or_else(|| JsValue::from_str("no window"))?;
102    let tg = Reflect::get(&window, &JsValue::from_str("Telegram"))?;
103    let webapp = Reflect::get(&tg, &JsValue::from_str("WebApp"))?;
104    Reflect::get(&webapp, &JsValue::from_str("deviceStorage"))
105}
106
107#[cfg(test)]
108mod tests {
109    use js_sys::{Function, Object, Reflect};
110    use wasm_bindgen::prelude::*;
111    use wasm_bindgen_test::{wasm_bindgen_test, wasm_bindgen_test_configure};
112    use web_sys::window;
113
114    use super::*;
115
116    wasm_bindgen_test_configure!(run_in_browser);
117
118    #[allow(dead_code)]
119    fn setup_device_storage() -> Object {
120        let win = window().unwrap();
121        let telegram = Object::new();
122        let webapp = Object::new();
123        let storage = Object::new();
124        let _ = Reflect::set(&win, &"Telegram".into(), &telegram);
125        let _ = Reflect::set(&telegram, &"WebApp".into(), &webapp);
126        let _ = Reflect::set(&webapp, &"deviceStorage".into(), &storage);
127        storage
128    }
129
130    #[wasm_bindgen_test(async)]
131    #[allow(dead_code)]
132    async fn set_calls_js() {
133        let storage = setup_device_storage();
134        let func = Function::new_with_args("k,v", "this[k] = v; return Promise.resolve();");
135        let _ = Reflect::set(&storage, &"set".into(), &func);
136        assert!(set("a", "b").await.is_ok());
137        let val = Reflect::get(&storage, &"a".into()).unwrap();
138        assert_eq!(val.as_string().as_deref(), Some("b"));
139    }
140
141    #[wasm_bindgen_test(async)]
142    #[allow(dead_code)]
143    async fn set_err() {
144        assert!(set("a", "b").await.is_err());
145    }
146
147    #[wasm_bindgen_test(async)]
148    #[allow(dead_code)]
149    async fn get_calls_js() {
150        let storage = setup_device_storage();
151        let func = Function::new_with_args("k", "return this[k];");
152        let _ = Reflect::set(&storage, &"get".into(), &func);
153        let _ = Reflect::set(&storage, &"a".into(), &JsValue::from_str("b"));
154        let value = get("a").await.unwrap();
155        assert_eq!(value.as_deref(), Some("b"));
156    }
157
158    #[wasm_bindgen_test(async)]
159    #[allow(dead_code)]
160    async fn get_err() {
161        assert!(get("a").await.is_err());
162    }
163
164    #[wasm_bindgen_test(async)]
165    #[allow(dead_code)]
166    async fn remove_calls_js() {
167        let storage = setup_device_storage();
168        let func = Function::new_with_args("k", "delete this[k]; return Promise.resolve();");
169        let _ = Reflect::set(&storage, &"remove".into(), &func);
170        let _ = Reflect::set(&storage, &"a".into(), &JsValue::from_str("b"));
171        assert!(remove("a").await.is_ok());
172        let has = Reflect::has(&storage, &"a".into()).unwrap();
173        assert!(!has);
174    }
175
176    #[wasm_bindgen_test(async)]
177    #[allow(dead_code)]
178    async fn remove_err() {
179        assert!(remove("a").await.is_err());
180    }
181
182    #[wasm_bindgen_test(async)]
183    #[allow(dead_code)]
184    async fn clear_calls_js() {
185        let storage = setup_device_storage();
186        let func = Function::new_no_args(
187            "Object.keys(this).forEach(k => delete this[k]); return Promise.resolve();"
188        );
189        let _ = Reflect::set(&storage, &"clear".into(), &func);
190        let _ = Reflect::set(&storage, &"a".into(), &JsValue::from_str("b"));
191        assert!(clear().await.is_ok());
192        let has = Reflect::has(&storage, &"a".into()).unwrap();
193        assert!(!has);
194    }
195
196    #[wasm_bindgen_test(async)]
197    #[allow(dead_code)]
198    async fn clear_err() {
199        assert!(clear().await.is_err());
200    }
201}