telegram_webapp_sdk/api/
secure_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 secure storage.
7///
8/// Values are stored in an encrypted form and can be restored after the user
9/// reinstalls the application.
10///
11/// # Errors
12/// Returns `Err(JsValue)` if the JavaScript call fails or `secureStorage` is
13/// missing.
14///
15/// # Examples
16/// ```
17/// use telegram_webapp_sdk::api::secure_storage::set;
18/// # async fn run() -> Result<(), wasm_bindgen::JsValue> {
19/// set("token", "123").await?;
20/// # Ok(()) }
21/// ```
22pub async fn set(key: &str, value: &str) -> Result<(), JsValue> {
23    let storage = secure_storage_object()?;
24    let func = Reflect::get(&storage, &JsValue::from_str("set"))?.dyn_into::<Function>()?;
25    let promise = func
26        .call2(&storage, &JsValue::from_str(key), &JsValue::from_str(value))?
27        .dyn_into::<Promise>()?;
28    JsFuture::from(promise).await?;
29    Ok(())
30}
31
32/// Retrieves a value from Telegram's secure storage.
33///
34/// # Errors
35/// Returns `Err(JsValue)` if the JavaScript call fails or `secureStorage` is
36/// missing.
37///
38/// # Examples
39/// ```
40/// use telegram_webapp_sdk::api::secure_storage::{get, set};
41/// # async fn run() -> Result<(), wasm_bindgen::JsValue> {
42/// set("token", "123").await?;
43/// let value = get("token").await?;
44/// assert_eq!(value.as_deref(), Some("123"));
45/// # Ok(()) }
46/// ```
47pub async fn get(key: &str) -> Result<Option<String>, JsValue> {
48    let storage = secure_storage_object()?;
49    let func = Reflect::get(&storage, &JsValue::from_str("get"))?.dyn_into::<Function>()?;
50    let promise = func
51        .call1(&storage, &JsValue::from_str(key))?
52        .dyn_into::<Promise>()?;
53    let value = JsFuture::from(promise).await?;
54    Ok(value.as_string())
55}
56
57/// Restores a previously removed value from Telegram's secure storage.
58///
59/// # Errors
60/// Returns `Err(JsValue)` if the JavaScript call fails or `secureStorage` is
61/// missing.
62///
63/// # Examples
64/// ```
65/// use telegram_webapp_sdk::api::secure_storage::{remove, restore, set};
66/// # async fn run() -> Result<(), wasm_bindgen::JsValue> {
67/// set("token", "123").await?;
68/// remove("token").await?;
69/// let _ = restore("token").await?;
70/// # Ok(()) }
71/// ```
72pub async fn restore(key: &str) -> Result<Option<String>, JsValue> {
73    let storage = secure_storage_object()?;
74    let func = Reflect::get(&storage, &JsValue::from_str("restore"))?.dyn_into::<Function>()?;
75    let promise = func
76        .call1(&storage, &JsValue::from_str(key))?
77        .dyn_into::<Promise>()?;
78    let value = JsFuture::from(promise).await?;
79    Ok(value.as_string())
80}
81
82/// Removes a value from Telegram's secure storage.
83///
84/// # Errors
85/// Returns `Err(JsValue)` if the JavaScript call fails or `secureStorage` is
86/// missing.
87///
88/// # Examples
89/// ```
90/// use telegram_webapp_sdk::api::secure_storage::{remove, set};
91/// # async fn run() -> Result<(), wasm_bindgen::JsValue> {
92/// set("token", "123").await?;
93/// remove("token").await?;
94/// # Ok(()) }
95/// ```
96pub async fn remove(key: &str) -> Result<(), JsValue> {
97    let storage = secure_storage_object()?;
98    let func = Reflect::get(&storage, &JsValue::from_str("remove"))?.dyn_into::<Function>()?;
99    let promise = func
100        .call1(&storage, &JsValue::from_str(key))?
101        .dyn_into::<Promise>()?;
102    JsFuture::from(promise).await?;
103    Ok(())
104}
105
106/// Clears all entries from Telegram's secure storage.
107///
108/// # Errors
109/// Returns `Err(JsValue)` if the JavaScript call fails or `secureStorage` is
110/// missing.
111///
112/// # Examples
113/// ```
114/// use telegram_webapp_sdk::api::secure_storage::{clear, set};
115/// # async fn run() -> Result<(), wasm_bindgen::JsValue> {
116/// set("token", "123").await?;
117/// clear().await?;
118/// # Ok(()) }
119/// ```
120pub async fn clear() -> Result<(), JsValue> {
121    let storage = secure_storage_object()?;
122    let func = Reflect::get(&storage, &JsValue::from_str("clear"))?.dyn_into::<Function>()?;
123    let promise = func.call0(&storage)?.dyn_into::<Promise>()?;
124    JsFuture::from(promise).await?;
125    Ok(())
126}
127
128fn secure_storage_object() -> Result<JsValue, JsValue> {
129    let window = window().ok_or_else(|| JsValue::from_str("no window"))?;
130    let tg = Reflect::get(&window, &JsValue::from_str("Telegram"))?;
131    let webapp = Reflect::get(&tg, &JsValue::from_str("WebApp"))?;
132    Reflect::get(&webapp, &JsValue::from_str("secureStorage"))
133}
134
135#[cfg(test)]
136mod tests {
137    use js_sys::{Function, Object, Reflect};
138    use wasm_bindgen::prelude::*;
139    use wasm_bindgen_test::{wasm_bindgen_test, wasm_bindgen_test_configure};
140    use web_sys::window;
141
142    use super::*;
143
144    wasm_bindgen_test_configure!(run_in_browser);
145
146    #[allow(dead_code)]
147    fn setup_secure_storage() -> Object {
148        let win = window().unwrap();
149        let telegram = Object::new();
150        let webapp = Object::new();
151        let storage = Object::new();
152        let _ = Reflect::set(&win, &"Telegram".into(), &telegram);
153        let _ = Reflect::set(&telegram, &"WebApp".into(), &webapp);
154        let _ = Reflect::set(&webapp, &"secureStorage".into(), &storage);
155        storage
156    }
157
158    #[wasm_bindgen_test(async)]
159    #[allow(dead_code)]
160    async fn set_calls_js() {
161        let storage = setup_secure_storage();
162        let func = Function::new_with_args("k,v", "this[k] = v; return Promise.resolve();");
163        let _ = Reflect::set(&storage, &"set".into(), &func);
164        assert!(set("a", "b").await.is_ok());
165        let val = Reflect::get(&storage, &"a".into()).unwrap();
166        assert_eq!(val.as_string().as_deref(), Some("b"));
167    }
168
169    #[wasm_bindgen_test(async)]
170    #[allow(dead_code)]
171    async fn set_err() {
172        assert!(set("a", "b").await.is_err());
173    }
174
175    #[wasm_bindgen_test(async)]
176    #[allow(dead_code)]
177    async fn get_calls_js() {
178        let storage = setup_secure_storage();
179        let func = Function::new_with_args("k", "return this[k];");
180        let _ = Reflect::set(&storage, &"get".into(), &func);
181        let _ = Reflect::set(&storage, &"a".into(), &JsValue::from_str("b"));
182        let value = get("a").await.unwrap();
183        assert_eq!(value.as_deref(), Some("b"));
184    }
185
186    #[wasm_bindgen_test(async)]
187    #[allow(dead_code)]
188    async fn get_err() {
189        assert!(get("a").await.is_err());
190    }
191
192    #[wasm_bindgen_test(async)]
193    #[allow(dead_code)]
194    async fn restore_calls_js() {
195        let storage = setup_secure_storage();
196        let func = Function::new_with_args("k", "return this[k];");
197        let _ = Reflect::set(&storage, &"restore".into(), &func);
198        let _ = Reflect::set(&storage, &"a".into(), &JsValue::from_str("b"));
199        let value = restore("a").await.unwrap();
200        assert_eq!(value.as_deref(), Some("b"));
201    }
202
203    #[wasm_bindgen_test(async)]
204    #[allow(dead_code)]
205    async fn restore_err() {
206        assert!(restore("a").await.is_err());
207    }
208
209    #[wasm_bindgen_test(async)]
210    #[allow(dead_code)]
211    async fn remove_calls_js() {
212        let storage = setup_secure_storage();
213        let func = Function::new_with_args("k", "delete this[k]; return Promise.resolve();");
214        let _ = Reflect::set(&storage, &"remove".into(), &func);
215        let _ = Reflect::set(&storage, &"a".into(), &JsValue::from_str("b"));
216        assert!(remove("a").await.is_ok());
217        let has = Reflect::has(&storage, &"a".into()).unwrap();
218        assert!(!has);
219    }
220
221    #[wasm_bindgen_test(async)]
222    #[allow(dead_code)]
223    async fn remove_err() {
224        assert!(remove("a").await.is_err());
225    }
226
227    #[wasm_bindgen_test(async)]
228    #[allow(dead_code)]
229    async fn clear_calls_js() {
230        let storage = setup_secure_storage();
231        let func = Function::new_no_args(
232            "Object.keys(this).forEach(k => delete this[k]); return Promise.resolve();"
233        );
234        let _ = Reflect::set(&storage, &"clear".into(), &func);
235        let _ = Reflect::set(&storage, &"a".into(), &JsValue::from_str("b"));
236        assert!(clear().await.is_ok());
237        let has = Reflect::has(&storage, &"a".into()).unwrap();
238        assert!(!has);
239    }
240
241    #[wasm_bindgen_test(async)]
242    #[allow(dead_code)]
243    async fn clear_err() {
244        assert!(clear().await.is_err());
245    }
246}