1use crate::error::Error;
2use cfg_if::cfg_if;
3use chrome_sys::storage;
4use js_sys::{Array, Object};
5use wasm_bindgen::prelude::*;
6use workflow_core::task::call_async_no_send;
7
8pub struct LocalStorage;
10
11impl LocalStorage {
12 pub async fn set_item(key: &str, value: &str) -> Result<JsValue, JsValue> {
14 let key = key.to_string();
15 let value = value.to_string();
16 call_async_no_send!(async move {
17 let data = Object::new();
18 js_sys::Reflect::set(&data, &key.into(), &value.into())?;
19 storage::set(data.into()).await
20 })
21 }
22
23 pub async fn get_item(key: &str) -> Result<Option<String>, JsValue> {
25 let _key = key.to_string();
26 let obj = call_async_no_send!(storage::get(_key).await)?;
27 Ok(js_sys::Reflect::get(&obj, &key.into())?.as_string())
28 }
29
30 pub async fn get_items(keys: Vec<&str>) -> Result<StorageData, JsValue> {
32 let keys = keys.iter().map(|k| k.to_string()).collect::<Vec<_>>();
33 Ok(call_async_no_send!(async move {
34 let query = Array::new();
35 for key in keys {
36 query.push(&key.into());
37 }
38 storage::get_items(query).await
39 })?
40 .try_into()?)
41 }
42
43 pub async fn get_all() -> Result<StorageData, JsValue> {
45 Ok(call_async_no_send!(storage::get_all().await)?.try_into()?)
46 }
47
48 pub async fn keys() -> Result<Vec<String>, JsValue> {
50 Ok(Self::get_all().await?.keys())
51 }
52
53 pub async fn remove_item(key: &str) -> Result<(), JsValue> {
55 let key = key.to_string();
56 call_async_no_send!(storage::remove(key).await)
57 }
58
59 pub async fn rename_item(from_key: &str, to_key: &str) -> Result<(), Error> {
63 let from_key = from_key.to_string();
64 let to_key = to_key.to_string();
65
66 if Self::get_item(&to_key).await?.is_some() {
67 return Err(Error::KeyExists(to_key));
68 }
69 if let Some(existing) = Self::get_item(&from_key).await? {
70 Self::set_item(&to_key, &existing).await?;
71 Self::remove_item(&from_key).await?;
72 Ok(())
73 } else {
74 Err(Error::MissingKey(from_key))
75 }
76 }
77
78 pub async fn remove_items(keys: Vec<&str>) -> Result<(), JsValue> {
80 let keys = keys.iter().map(|k| k.to_string()).collect::<Vec<_>>();
81 call_async_no_send!(async move {
82 let query = Array::new();
83 for key in keys {
84 query.push(&key.into());
85 }
86 storage::remove_items(query).await
87 })
88 }
89
90 pub async fn clear() -> Result<(), JsValue> {
92 call_async_no_send!(storage::clear().await)
93 }
94
95 #[cfg(debug_assertions)]
99 pub async fn unit_tests() -> Result<(), String> {
100 use workflow_core::sendable::Sendable;
101
102 let old_data = Sendable(Self::get_all().await.unwrap());
103 let error = Sendable(test_impl().await.err());
104 let old_data_clone = old_data.clone();
105 call_async_no_send!(storage::set(old_data.unwrap().inner.into()).await).unwrap();
106
107 let new_data = Self::get_all().await.unwrap();
108 for key in new_data.keys() {
109 let new_value = new_data.get(&key).unwrap();
110 let old_value = old_data_clone.get(&key).unwrap();
111 if new_value != old_value {
112 return Err(format!(
113 "[WARNING] Data restore failed: {key} => {old_value:?} != {new_value:?}"
114 ));
115 }
116 }
117
118 if let Some(err) = error.unwrap() {
119 return Err(err.as_string().unwrap_or(format!("{err:?}")));
120 }
121 Ok(())
122 }
123}
124
125#[derive(Debug, Clone)]
128pub struct StorageData {
129 pub inner: Object,
131}
132
133impl TryFrom<JsValue> for StorageData {
134 type Error = JsError;
135 fn try_from(inner: JsValue) -> Result<Self, Self::Error> {
136 if !inner.is_object() {
137 return Err(JsError::new(&format!(
138 "Invalid JsValue: cant convert JsValue ({inner:?}) to StorageData."
139 )));
140 }
141 let inner = Object::from(inner);
142 Ok(Self { inner })
143 }
144}
145
146impl StorageData {
147 pub fn keys(&self) -> Vec<String> {
149 let mut keys = vec![];
150 for key in Object::keys(&self.inner) {
151 keys.push(key.as_string().unwrap());
152 }
153
154 keys
155 }
156
157 pub fn has(&self, key: &str) -> bool {
159 js_sys::Object::has_own(&self.inner, &key.into())
160 }
161
162 pub fn get_value(&self, key: &str) -> Result<Option<JsValue>, JsValue> {
165 let value = js_sys::Reflect::get(&self.inner, &key.into())?;
166 if value.eq(&JsValue::UNDEFINED) {
167 Ok(None)
168 } else {
169 Ok(Some(value))
170 }
171 }
172
173 pub fn get(&self, key: &str) -> Result<Option<String>, JsValue> {
176 let value = js_sys::Reflect::get(&self.inner, &key.into())?;
177 if value.eq(&JsValue::UNDEFINED) {
178 Ok(None)
179 } else {
180 Ok(value.as_string())
181 }
182 }
183}
184
185#[cfg(debug_assertions)]
186macro_rules! assert_test {
187 ($name:literal, $cond1:expr_2021, $cond2:expr_2021) => {{
188 if $cond1 != $cond2 {
189 return Result::<(), JsValue>::Err(
190 format!(
191 "{} => {}, {:?} != {:?}",
192 $name,
193 stringify!($cond1),
194 $cond1,
195 $cond2
196 )
197 .into(),
198 );
199 }
200 }};
201}
202
203#[cfg(debug_assertions)]
204async fn test_impl() -> Result<(), JsValue> {
205 LocalStorage::clear().await?;
206 {
207 let empty_data = LocalStorage::get_all().await?;
208 assert_test!("Key length should be 0", empty_data.keys().len(), 0);
209 }
210 {
211 LocalStorage::set_item("key1", "value-1").await?;
212 let data = LocalStorage::get_all().await?;
213 assert_test!("Key length should be 1", data.keys().len(), 1);
214 assert_test!("'key1' key should be there", data.has("key1"), true);
215 assert_test!(
216 "value for 'key1' key should be 'value-1'",
217 data.get("key1")?.unwrap(),
218 "value-1"
219 );
220 }
221 {
222 let item = LocalStorage::get_item("key1").await?.unwrap();
223 assert_test!("value for 'key1' key should be 'value-1'", item, "value-1");
224 }
225 {
226 LocalStorage::set_item("key2", "value-2").await?;
227 let data = LocalStorage::get_all().await?;
228 assert_test!("Key length should be 2", data.keys().len(), 2);
229 assert_test!("'key2' key should be there", data.has("key2"), true);
230 assert_test!(
231 "value for 'key2' key should be 'value-2'",
232 data.get("key2")?.unwrap(),
233 "value-2"
234 );
235 }
236 {
237 let item = LocalStorage::get_item("key2").await?.unwrap();
238 assert_test!("value for 'key2' key should be 'value-2'", item, "value-2");
239 }
240 {
241 LocalStorage::set_item("key3", "value-3").await?;
242 let data = LocalStorage::get_all().await?;
243 assert_test!("Key length should be 3", data.keys().len(), 3);
244 assert_test!("'key3' key should be there", data.has("key3"), true);
245 assert_test!(
246 "value for 'key3' key should be 'value-3'",
247 data.get("key3")?.unwrap(),
248 "value-3"
249 );
250 }
251 {
252 let data = LocalStorage::get_items(vec!["key2", "key3"]).await?;
253 assert_test!("Key length should be 2", data.keys().len(), 2);
254 assert_test!("'key2' key should be there", data.has("key2"), true);
255 assert_test!("'key3' key should be there", data.has("key3"), true);
256 assert_test!(
257 "value for 'key2' key should be 'value-2'",
258 data.get("key2")?.unwrap(),
259 "value-2"
260 );
261 assert_test!(
262 "value for 'key3' key should be 'value-3'",
263 data.get("key3")?.unwrap(),
264 "value-3"
265 );
266 }
267 {
268 LocalStorage::remove_item("key2").await?;
269 let data = LocalStorage::get_all().await?;
270 assert_test!(
271 "After remove_item, Key length should be 2",
272 data.keys().len(),
273 2
274 );
275 assert_test!("'key2' key should not be there", data.has("key2"), false);
276 assert_test!(
277 "value for 'key2' key should be None",
278 data.get("key2")?,
279 Option::<String>::None
280 );
281 }
282 {
283 LocalStorage::clear().await?;
284 let data = LocalStorage::get_all().await?;
285 assert_test!("After clear, Key length should be 0", data.keys().len(), 0);
286 assert_test!("'key1' key should not be there", data.has("key1"), false);
287 assert_test!(
288 "value for 'key2' key should be None",
289 data.get("key2")?,
290 Option::<String>::None
291 );
292 }
293
294 Ok(())
295}
296
297pub async fn __chrome_storage_unit_test() {
301 cfg_if! {
302 if #[cfg(all(target_arch = "wasm32", debug_assertions))] {
303 if !workflow_core::runtime::is_chrome_extension() {
304 workflow_log::log_info!("ChromeStorage::test() FAILED: these are unit tests for chrome extension storage api.");
305 return
306 }
307 use LocalStorage as ChromeStorage;
308 match ChromeStorage::unit_tests().await{
309 Ok(_)=>workflow_log::log_info!("ChromeStorage::test() PASSED"),
310 Err(err)=>workflow_log::log_error!("ChromeStorage::test() FAILED: {err:?}")
311 };
312 }
313 }
314}