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