telegram_webapp_sdk/api/
secure_storage.rs1use js_sys::{Function, Promise, Reflect};
2use wasm_bindgen::{JsCast, JsValue};
3use wasm_bindgen_futures::JsFuture;
4use web_sys::window;
5
6pub 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
32pub 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
57pub 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
82pub 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
106pub 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}