telegram_webapp_sdk/api/
secure_storage.rs1use js_sys::{Function, Promise, Reflect};
5use wasm_bindgen::{JsCast, JsValue};
6use wasm_bindgen_futures::JsFuture;
7use web_sys::window;
8
9pub async fn set(key: &str, value: &str) -> Result<(), JsValue> {
26 let storage = secure_storage_object()?;
27 let func = Reflect::get(&storage, &JsValue::from_str("set"))?.dyn_into::<Function>()?;
28 let promise = func
29 .call2(&storage, &JsValue::from_str(key), &JsValue::from_str(value))?
30 .dyn_into::<Promise>()?;
31 JsFuture::from(promise).await?;
32 Ok(())
33}
34
35pub async fn get(key: &str) -> Result<Option<String>, JsValue> {
51 let storage = secure_storage_object()?;
52 let func = Reflect::get(&storage, &JsValue::from_str("get"))?.dyn_into::<Function>()?;
53 let promise = func
54 .call1(&storage, &JsValue::from_str(key))?
55 .dyn_into::<Promise>()?;
56 let value = JsFuture::from(promise).await?;
57 Ok(value.as_string())
58}
59
60pub async fn restore(key: &str) -> Result<Option<String>, JsValue> {
76 let storage = secure_storage_object()?;
77 let func = Reflect::get(&storage, &JsValue::from_str("restore"))?.dyn_into::<Function>()?;
78 let promise = func
79 .call1(&storage, &JsValue::from_str(key))?
80 .dyn_into::<Promise>()?;
81 let value = JsFuture::from(promise).await?;
82 Ok(value.as_string())
83}
84
85pub async fn remove(key: &str) -> Result<(), JsValue> {
100 let storage = secure_storage_object()?;
101 let func = Reflect::get(&storage, &JsValue::from_str("remove"))?.dyn_into::<Function>()?;
102 let promise = func
103 .call1(&storage, &JsValue::from_str(key))?
104 .dyn_into::<Promise>()?;
105 JsFuture::from(promise).await?;
106 Ok(())
107}
108
109pub async fn clear() -> Result<(), JsValue> {
124 let storage = secure_storage_object()?;
125 let func = Reflect::get(&storage, &JsValue::from_str("clear"))?.dyn_into::<Function>()?;
126 let promise = func.call0(&storage)?.dyn_into::<Promise>()?;
127 JsFuture::from(promise).await?;
128 Ok(())
129}
130
131fn secure_storage_object() -> Result<JsValue, JsValue> {
132 let window = window().ok_or_else(|| JsValue::from_str("no window"))?;
133 let tg = Reflect::get(&window, &JsValue::from_str("Telegram"))?;
134 let webapp = Reflect::get(&tg, &JsValue::from_str("WebApp"))?;
135 Reflect::get(&webapp, &JsValue::from_str("secureStorage"))
136}
137
138#[cfg(test)]
139mod tests {
140 use js_sys::{Function, Object, Reflect};
141 use wasm_bindgen::prelude::*;
142 use wasm_bindgen_test::{wasm_bindgen_test, wasm_bindgen_test_configure};
143 use web_sys::window;
144
145 use super::*;
146
147 wasm_bindgen_test_configure!(run_in_browser);
148
149 #[allow(dead_code)]
150 fn setup_secure_storage() -> Object {
151 let win = window().unwrap();
152 let telegram = Object::new();
153 let webapp = Object::new();
154 let storage = Object::new();
155 let _ = Reflect::set(&win, &"Telegram".into(), &telegram);
156 let _ = Reflect::set(&telegram, &"WebApp".into(), &webapp);
157 let _ = Reflect::set(&webapp, &"secureStorage".into(), &storage);
158 storage
159 }
160
161 #[wasm_bindgen_test(async)]
162 #[allow(dead_code)]
163 async fn set_calls_js() {
164 let storage = setup_secure_storage();
165 let func = Function::new_with_args("k,v", "this[k] = v; return Promise.resolve();");
166 let _ = Reflect::set(&storage, &"set".into(), &func);
167 assert!(set("a", "b").await.is_ok());
168 let val = Reflect::get(&storage, &"a".into()).unwrap();
169 assert_eq!(val.as_string().as_deref(), Some("b"));
170 }
171
172 #[wasm_bindgen_test(async)]
173 #[allow(dead_code)]
174 async fn set_err() {
175 assert!(set("a", "b").await.is_err());
176 }
177
178 #[wasm_bindgen_test(async)]
179 #[allow(dead_code)]
180 async fn get_calls_js() {
181 let storage = setup_secure_storage();
182 let func = Function::new_with_args("k", "return this[k];");
183 let _ = Reflect::set(&storage, &"get".into(), &func);
184 let _ = Reflect::set(&storage, &"a".into(), &JsValue::from_str("b"));
185 let value = get("a").await.unwrap();
186 assert_eq!(value.as_deref(), Some("b"));
187 }
188
189 #[wasm_bindgen_test(async)]
190 #[allow(dead_code)]
191 async fn get_err() {
192 assert!(get("a").await.is_err());
193 }
194
195 #[wasm_bindgen_test(async)]
196 #[allow(dead_code)]
197 async fn restore_calls_js() {
198 let storage = setup_secure_storage();
199 let func = Function::new_with_args("k", "return this[k];");
200 let _ = Reflect::set(&storage, &"restore".into(), &func);
201 let _ = Reflect::set(&storage, &"a".into(), &JsValue::from_str("b"));
202 let value = restore("a").await.unwrap();
203 assert_eq!(value.as_deref(), Some("b"));
204 }
205
206 #[wasm_bindgen_test(async)]
207 #[allow(dead_code)]
208 async fn restore_err() {
209 assert!(restore("a").await.is_err());
210 }
211
212 #[wasm_bindgen_test(async)]
213 #[allow(dead_code)]
214 async fn remove_calls_js() {
215 let storage = setup_secure_storage();
216 let func = Function::new_with_args("k", "delete this[k]; return Promise.resolve();");
217 let _ = Reflect::set(&storage, &"remove".into(), &func);
218 let _ = Reflect::set(&storage, &"a".into(), &JsValue::from_str("b"));
219 assert!(remove("a").await.is_ok());
220 let has = Reflect::has(&storage, &"a".into()).unwrap();
221 assert!(!has);
222 }
223
224 #[wasm_bindgen_test(async)]
225 #[allow(dead_code)]
226 async fn remove_err() {
227 assert!(remove("a").await.is_err());
228 }
229
230 #[wasm_bindgen_test(async)]
231 #[allow(dead_code)]
232 async fn clear_calls_js() {
233 let storage = setup_secure_storage();
234 let func = Function::new_no_args(
235 "Object.keys(this).forEach(k => delete this[k]); return Promise.resolve();"
236 );
237 let _ = Reflect::set(&storage, &"clear".into(), &func);
238 let _ = Reflect::set(&storage, &"a".into(), &JsValue::from_str("b"));
239 assert!(clear().await.is_ok());
240 let has = Reflect::has(&storage, &"a".into()).unwrap();
241 assert!(!has);
242 }
243
244 #[wasm_bindgen_test(async)]
245 #[allow(dead_code)]
246 async fn clear_err() {
247 assert!(clear().await.is_err());
248 }
249}