Skip to main content

telegram_webapp_sdk/api/
cloud_storage.rs

1// SPDX-FileCopyrightText: 2025 RAprogramm <andrey.rozanov.vl@gmail.com>
2// SPDX-License-Identifier: MIT
3
4use js_sys::{Array, Function, Promise, Reflect};
5use wasm_bindgen::{JsCast, prelude::*};
6use web_sys::window;
7
8/// Returns the `Telegram.WebApp.CloudStorage` object.
9fn cloud_storage_object() -> Result<JsValue, JsValue> {
10    let win = window().ok_or_else(|| JsValue::from_str("no window"))?;
11    let tg = Reflect::get(&win, &JsValue::from_str("Telegram"))?;
12    let webapp = Reflect::get(&tg, &JsValue::from_str("WebApp"))?;
13    Reflect::get(&webapp, &JsValue::from_str("CloudStorage"))
14}
15
16/// Calls `Telegram.WebApp.CloudStorage.getItem()`.
17///
18/// # Errors
19/// Returns `Err(JsValue)` if CloudStorage or the method is unavailable, or if
20/// the call fails.
21///
22/// # Examples
23/// ```no_run
24/// use telegram_webapp_sdk::api::cloud_storage::get_item;
25/// use wasm_bindgen_futures::JsFuture;
26/// # async fn run() -> Result<(), wasm_bindgen::JsValue> {
27/// let value = JsFuture::from(get_item("key")?).await?;
28/// # Ok(())
29/// # }
30/// ```
31pub fn get_item(key: &str) -> Result<Promise, JsValue> {
32    let storage = cloud_storage_object()?;
33    let func = Reflect::get(&storage, &JsValue::from_str("getItem"))?.dyn_into::<Function>()?;
34    func.call1(&storage, &JsValue::from_str(key))?
35        .dyn_into::<Promise>()
36}
37
38/// Calls `Telegram.WebApp.CloudStorage.setItem()`.
39///
40/// # Errors
41/// Returns `Err(JsValue)` if CloudStorage or the method is unavailable, or if
42/// the call fails.
43///
44/// # Examples
45/// ```no_run
46/// use telegram_webapp_sdk::api::cloud_storage::set_item;
47/// use wasm_bindgen_futures::JsFuture;
48/// # async fn run() -> Result<(), wasm_bindgen::JsValue> {
49/// JsFuture::from(set_item("key", "value")?).await?;
50/// # Ok(())
51/// # }
52/// ```
53pub fn set_item(key: &str, value: &str) -> Result<Promise, JsValue> {
54    let storage = cloud_storage_object()?;
55    let func = Reflect::get(&storage, &JsValue::from_str("setItem"))?.dyn_into::<Function>()?;
56    func.call2(&storage, &JsValue::from_str(key), &JsValue::from_str(value))?
57        .dyn_into::<Promise>()
58}
59
60/// Calls `Telegram.WebApp.CloudStorage.removeItem()`.
61///
62/// # Errors
63/// Returns `Err(JsValue)` if CloudStorage or the method is unavailable, or if
64/// the call fails.
65///
66/// # Examples
67/// ```no_run
68/// use telegram_webapp_sdk::api::cloud_storage::remove_item;
69/// use wasm_bindgen_futures::JsFuture;
70/// # async fn run() -> Result<(), wasm_bindgen::JsValue> {
71/// JsFuture::from(remove_item("key")?).await?;
72/// # Ok(())
73/// # }
74/// ```
75pub fn remove_item(key: &str) -> Result<Promise, JsValue> {
76    let storage = cloud_storage_object()?;
77    let func = Reflect::get(&storage, &JsValue::from_str("removeItem"))?.dyn_into::<Function>()?;
78    func.call1(&storage, &JsValue::from_str(key))?
79        .dyn_into::<Promise>()
80}
81
82/// Calls `Telegram.WebApp.CloudStorage.getItems()`.
83///
84/// # Errors
85/// Returns `Err(JsValue)` if CloudStorage or the method is unavailable, or if
86/// the call fails.
87///
88/// # Examples
89/// ```no_run
90/// use telegram_webapp_sdk::api::cloud_storage::get_items;
91/// use wasm_bindgen_futures::JsFuture;
92/// # async fn run() -> Result<(), wasm_bindgen::JsValue> {
93/// let _ = JsFuture::from(get_items(&["a", "b"])?).await?;
94/// # Ok(())
95/// # }
96/// ```
97pub fn get_items(keys: &[&str]) -> Result<Promise, JsValue> {
98    let storage = cloud_storage_object()?;
99    let func = Reflect::get(&storage, &JsValue::from_str("getItems"))?.dyn_into::<Function>()?;
100    let array = Array::new();
101    for key in keys {
102        array.push(&JsValue::from_str(key));
103    }
104    func.call1(&storage, &array.into())?.dyn_into::<Promise>()
105}
106
107/// Calls `Telegram.WebApp.CloudStorage.removeItems()`.
108///
109/// # Errors
110/// Returns `Err(JsValue)` if CloudStorage or the method is unavailable, or if
111/// the call fails.
112///
113/// # Examples
114/// ```no_run
115/// use telegram_webapp_sdk::api::cloud_storage::remove_items;
116/// use wasm_bindgen_futures::JsFuture;
117/// # async fn run() -> Result<(), wasm_bindgen::JsValue> {
118/// JsFuture::from(remove_items(&["a", "b"])?).await?;
119/// # Ok(())
120/// # }
121/// ```
122pub fn remove_items(keys: &[&str]) -> Result<Promise, JsValue> {
123    let storage = cloud_storage_object()?;
124    let func =
125        Reflect::get(&storage, &JsValue::from_str("removeItems"))?.dyn_into::<Function>()?;
126    let array = Array::new();
127    for key in keys {
128        array.push(&JsValue::from_str(key));
129    }
130    func.call1(&storage, &array.into())?.dyn_into::<Promise>()
131}
132
133/// Calls `Telegram.WebApp.CloudStorage.getKeys()`.
134///
135/// # Errors
136/// Returns `Err(JsValue)` if CloudStorage or the method is unavailable, or if
137/// the call fails.
138///
139/// # Examples
140/// ```no_run
141/// use telegram_webapp_sdk::api::cloud_storage::get_keys;
142/// use wasm_bindgen_futures::JsFuture;
143/// # async fn run() -> Result<(), wasm_bindgen::JsValue> {
144/// let _ = JsFuture::from(get_keys()?).await?;
145/// # Ok(())
146/// # }
147/// ```
148pub fn get_keys() -> Result<Promise, JsValue> {
149    let storage = cloud_storage_object()?;
150    let func = Reflect::get(&storage, &JsValue::from_str("getKeys"))?.dyn_into::<Function>()?;
151    func.call0(&storage)?.dyn_into::<Promise>()
152}
153
154#[cfg(test)]
155mod tests {
156    #![allow(dead_code)]
157    use js_sys::{Array, Function, Object, Reflect};
158    use wasm_bindgen_futures::JsFuture;
159    use wasm_bindgen_test::{wasm_bindgen_test, wasm_bindgen_test_configure};
160    use web_sys::window;
161
162    use super::*;
163
164    wasm_bindgen_test_configure!(run_in_browser);
165
166    fn setup_cloud_storage() -> Object {
167        let win = window().unwrap();
168        let telegram = Object::new();
169        let webapp = Object::new();
170        let storage = Object::new();
171        let _ = Reflect::set(&win, &"Telegram".into(), &telegram);
172        let _ = Reflect::set(&telegram, &"WebApp".into(), &webapp);
173        let _ = Reflect::set(&webapp, &"CloudStorage".into(), &storage);
174        storage
175    }
176
177    #[wasm_bindgen_test(async)]
178    async fn get_item_ok() {
179        let storage = setup_cloud_storage();
180        let func =
181            Function::new_with_args("key", "this.called = key; return Promise.resolve('val');");
182        let _ = Reflect::set(&storage, &"getItem".into(), &func);
183        let value = JsFuture::from(get_item("test").unwrap()).await.unwrap();
184        assert_eq!(value.as_string(), Some("val".to_string()));
185        assert_eq!(
186            Reflect::get(&storage, &"called".into())
187                .unwrap()
188                .as_string(),
189            Some("test".into())
190        );
191    }
192
193    #[wasm_bindgen_test]
194    fn get_item_err() {
195        let _ = setup_cloud_storage();
196        assert!(get_item("test").is_err());
197    }
198
199    #[wasm_bindgen_test(async)]
200    async fn set_item_ok() {
201        let storage = setup_cloud_storage();
202        let func = Function::new_with_args(
203            "key, value",
204            "this.called = key + ':' + value; return Promise.resolve();"
205        );
206        let _ = Reflect::set(&storage, &"setItem".into(), &func);
207        JsFuture::from(set_item("a", "b").unwrap()).await.unwrap();
208        assert_eq!(
209            Reflect::get(&storage, &"called".into())
210                .unwrap()
211                .as_string(),
212            Some("a:b".into())
213        );
214    }
215
216    #[wasm_bindgen_test]
217    fn set_item_err() {
218        let _ = setup_cloud_storage();
219        assert!(set_item("a", "b").is_err());
220    }
221
222    #[wasm_bindgen_test(async)]
223    async fn remove_item_ok() {
224        let storage = setup_cloud_storage();
225        let func = Function::new_with_args("key", "this.called = key; return Promise.resolve();");
226        let _ = Reflect::set(&storage, &"removeItem".into(), &func);
227        JsFuture::from(remove_item("k").unwrap()).await.unwrap();
228        assert_eq!(
229            Reflect::get(&storage, &"called".into())
230                .unwrap()
231                .as_string(),
232            Some("k".into())
233        );
234    }
235
236    #[wasm_bindgen_test]
237    fn remove_item_err() {
238        let _ = setup_cloud_storage();
239        assert!(remove_item("k").is_err());
240    }
241
242    #[wasm_bindgen_test(async)]
243    async fn get_items_ok() {
244        let storage = setup_cloud_storage();
245        let func = Function::new_with_args(
246            "keys",
247            "this.called = keys; return Promise.resolve({a: '1', b: '2'});"
248        );
249        let _ = Reflect::set(&storage, &"getItems".into(), &func);
250        let result = JsFuture::from(get_items(&["a", "b"]).unwrap())
251            .await
252            .unwrap();
253        let obj = result.dyn_into::<Object>().unwrap();
254        assert_eq!(
255            Reflect::get(&obj, &"a".into()).unwrap().as_string(),
256            Some("1".into())
257        );
258        assert_eq!(
259            Reflect::get(&obj, &"b".into()).unwrap().as_string(),
260            Some("2".into())
261        );
262        let called = Reflect::get(&storage, &"called".into()).unwrap();
263        let arr = Array::from(&called);
264        assert_eq!(arr.get(0).as_string(), Some("a".into()));
265        assert_eq!(arr.get(1).as_string(), Some("b".into()));
266    }
267
268    #[wasm_bindgen_test]
269    fn get_items_err() {
270        let _ = setup_cloud_storage();
271        assert!(get_items(&["a"]).is_err());
272    }
273
274    #[wasm_bindgen_test(async)]
275    async fn remove_items_ok() {
276        let storage = setup_cloud_storage();
277        let func =
278            Function::new_with_args("keys", "this.called = keys; return Promise.resolve();");
279        let _ = Reflect::set(&storage, &"removeItems".into(), &func);
280        JsFuture::from(remove_items(&["a", "b"]).unwrap())
281            .await
282            .unwrap();
283        let called = Reflect::get(&storage, &"called".into()).unwrap();
284        let arr = Array::from(&called);
285        assert_eq!(arr.get(0).as_string(), Some("a".into()));
286        assert_eq!(arr.get(1).as_string(), Some("b".into()));
287    }
288
289    #[wasm_bindgen_test]
290    fn remove_items_err() {
291        let _ = setup_cloud_storage();
292        assert!(remove_items(&["a"]).is_err());
293    }
294
295    #[wasm_bindgen_test(async)]
296    async fn get_keys_ok() {
297        let storage = setup_cloud_storage();
298        let func = Function::new_no_args("return Promise.resolve(['x', 'y']);");
299        let _ = Reflect::set(&storage, &"getKeys".into(), &func);
300        let result = JsFuture::from(get_keys().unwrap()).await.unwrap();
301        let arr = Array::from(&result);
302        assert_eq!(arr.get(0).as_string(), Some("x".into()));
303        assert_eq!(arr.get(1).as_string(), Some("y".into()));
304    }
305
306    #[wasm_bindgen_test]
307    fn get_keys_err() {
308        let _ = setup_cloud_storage();
309        assert!(get_keys().is_err());
310    }
311}