bitwarden_wasm_internal/platform/
repository.rs1use std::{future::Future, marker::PhantomData, rc::Rc};
37
38use bitwarden_state::repository::{Repository, RepositoryError, RepositoryItem};
39use bitwarden_threading::ThreadBoundRunner;
40use wasm_bindgen::{JsValue, prelude::wasm_bindgen};
41
42pub(crate) trait WasmRepository<T> {
46 async fn get(&self, id: String) -> Result<JsValue, JsValue>;
47 async fn list(&self) -> Result<JsValue, JsValue>;
48 async fn set(&self, id: String, value: T) -> Result<JsValue, JsValue>;
49 async fn set_bulk(&self, values: Vec<(String, T)>) -> Result<JsValue, JsValue>;
50 async fn remove(&self, id: String) -> Result<JsValue, JsValue>;
51 async fn remove_bulk(&self, keys: Vec<String>) -> Result<JsValue, JsValue>;
52 async fn remove_all(&self) -> Result<JsValue, JsValue>;
53}
54
55pub(crate) struct WasmRepositoryChannel<T, R: WasmRepository<T> + 'static>(
59 ThreadBoundRunner<R>,
60 PhantomData<T>,
61);
62
63impl<T, R: WasmRepository<T> + 'static> WasmRepositoryChannel<T, R> {
64 pub(crate) fn new(repository: R) -> Self {
65 Self(ThreadBoundRunner::new(repository), PhantomData)
66 }
67}
68
69#[async_trait::async_trait]
70impl<T: RepositoryItem, R: WasmRepository<T> + 'static> Repository<T>
71 for WasmRepositoryChannel<T, R>
72{
73 async fn get(&self, key: T::Key) -> Result<Option<T>, RepositoryError> {
74 let key = key.to_string();
75 run_convert(&self.0, |s| async move { s.get(key).await }).await
76 }
77 async fn list(&self) -> Result<Vec<T>, RepositoryError> {
78 run_convert(&self.0, |s| async move { s.list().await }).await
79 }
80 async fn set(&self, key: T::Key, value: T) -> Result<(), RepositoryError> {
81 let key = key.to_string();
82 run_convert(
83 &self.0,
84 |s| async move { s.set(key, value).await.and(UNIT) },
85 )
86 .await
87 }
88 async fn set_bulk(&self, values: Vec<(T::Key, T)>) -> Result<(), RepositoryError> {
89 let values: Vec<(String, T)> = values
90 .into_iter()
91 .map(|(k, v)| (k.to_string(), v))
92 .collect();
93 run_convert(
94 &self.0,
95 |s| async move { s.set_bulk(values).await.and(UNIT) },
96 )
97 .await
98 }
99 async fn remove(&self, key: T::Key) -> Result<(), RepositoryError> {
100 let key = key.to_string();
101 run_convert(&self.0, |s| async move { s.remove(key).await.and(UNIT) }).await
102 }
103 async fn remove_bulk(&self, keys: Vec<T::Key>) -> Result<(), RepositoryError> {
104 let keys: Vec<String> = keys.into_iter().map(|k| k.to_string()).collect();
105 run_convert(
106 &self.0,
107 |s| async move { s.remove_bulk(keys).await.and(UNIT) },
108 )
109 .await
110 }
111 async fn remove_all(&self) -> Result<(), RepositoryError> {
112 run_convert(&self.0, |s| async move { s.remove_all().await.and(UNIT) }).await
113 }
114}
115
116#[wasm_bindgen(typescript_custom_section)]
117const REPOSITORY_CUSTOM_TS_TYPE: &'static str = r#"
118export interface Repository<T> {
119 get(id: string): Promise<T | null>;
120 list(): Promise<T[]>;
121 set(id: string, value: T): Promise<void>;
122 setBulk(values: [string, T][]): Promise<void>;
123 remove(id: string): Promise<void>;
124 removeBulk(keys: string[]): Promise<void>;
125 removeAll(): Promise<void>;
126}
127"#;
128
129macro_rules! create_wasm_repositories {
133 ( $container_name:ident ; $( $qualified_type_name:ty, $type_name:ident, $field_name:ident, $repo_name:ident );+ $(;)? ) => {
134
135 const _: () = {
136 #[wasm_bindgen(typescript_custom_section)]
137 const REPOSITORIES_CUSTOM_TS_TYPE: &'static str = concat!(
138 "export interface ",
139 stringify!($container_name),
140 "{\n",
141 $( stringify!($field_name), ": Repository<", stringify!($type_name), "> | null;\n", )+
142 "}\n"
143 );
144 };
145
146 #[wasm_bindgen]
147 extern "C" {
148 #[wasm_bindgen(typescript_type = $container_name)]
149 pub type $container_name;
150
151 $(
152 #[wasm_bindgen(method, getter)]
153 pub fn $field_name(this: &$container_name) -> Option<$repo_name>;
154 )+
155 }
156
157 impl $container_name {
158 pub fn register_all(self, client: &bitwarden_core::platform::StateClient) {
159 $(
160 if let Some(repo) = self.$field_name() {
161 let repo = repo.into_channel_impl();
162 client.register_client_managed(repo);
163 }
164 )+
165 }
166 }
167
168 $(
169 #[wasm_bindgen]
170 extern "C" {
171 #[wasm_bindgen]
172 pub type $repo_name;
173
174 #[wasm_bindgen(method, catch)]
175 async fn get(
176 this: &$repo_name,
177 id: String,
178 ) -> Result<::wasm_bindgen::JsValue, ::wasm_bindgen::JsValue>;
179 #[wasm_bindgen(method, catch)]
180 async fn list(this: &$repo_name)
181 -> Result<::wasm_bindgen::JsValue, ::wasm_bindgen::JsValue>;
182 #[wasm_bindgen(method, catch)]
183 async fn set(
184 this: &$repo_name,
185 id: String,
186 value: $qualified_type_name,
187 ) -> Result<::wasm_bindgen::JsValue, ::wasm_bindgen::JsValue>;
188 #[wasm_bindgen(method, catch, js_name = "setBulk")]
189 async fn set_bulk(
190 this: &$repo_name,
191 values: ::wasm_bindgen::JsValue,
192 ) -> Result<::wasm_bindgen::JsValue, ::wasm_bindgen::JsValue>;
193 #[wasm_bindgen(method, catch)]
194 async fn remove(
195 this: &$repo_name,
196 id: String,
197 ) -> Result<::wasm_bindgen::JsValue, ::wasm_bindgen::JsValue>;
198 #[wasm_bindgen(method, catch, js_name = "removeBulk")]
199 async fn remove_bulk(
200 this: &$repo_name,
201 keys: ::wasm_bindgen::JsValue,
202 ) -> Result<::wasm_bindgen::JsValue, ::wasm_bindgen::JsValue>;
203 #[wasm_bindgen(method, catch, js_name = "removeAll")]
204 async fn remove_all(
205 this: &$repo_name,
206 ) -> Result<::wasm_bindgen::JsValue, ::wasm_bindgen::JsValue>;
207 }
208
209 impl $crate::platform::repository::WasmRepository<$qualified_type_name> for $repo_name {
210 async fn get(
211 &self,
212 id: String,
213 ) -> Result<::wasm_bindgen::JsValue, ::wasm_bindgen::JsValue> {
214 self.get(id).await
215 }
216 async fn list(&self) -> Result<::wasm_bindgen::JsValue, ::wasm_bindgen::JsValue> {
217 self.list().await
218 }
219 async fn set(
220 &self,
221 id: String,
222 value: $qualified_type_name,
223 ) -> Result<::wasm_bindgen::JsValue, ::wasm_bindgen::JsValue> {
224 self.set(id, value).await
225 }
226 async fn set_bulk(
227 &self,
228 values: Vec<(String, $qualified_type_name)>,
229 ) -> Result<::wasm_bindgen::JsValue, ::wasm_bindgen::JsValue> {
230 let js_val = ::tsify::serde_wasm_bindgen::to_value(&values)
231 .map_err(|e| ::wasm_bindgen::JsValue::from_str(&e.to_string()))?;
232 self.set_bulk(js_val).await
233 }
234 async fn remove(
235 &self,
236 id: String,
237 ) -> Result<::wasm_bindgen::JsValue, ::wasm_bindgen::JsValue> {
238 self.remove(id).await
239 }
240 async fn remove_bulk(
241 &self,
242 keys: Vec<String>,
243 ) -> Result<::wasm_bindgen::JsValue, ::wasm_bindgen::JsValue> {
244 let js_val = ::tsify::serde_wasm_bindgen::to_value(&keys)
245 .map_err(|e| ::wasm_bindgen::JsValue::from_str(&e.to_string()))?;
246 self.remove_bulk(js_val).await
247 }
248 async fn remove_all(&self) -> Result<::wasm_bindgen::JsValue, ::wasm_bindgen::JsValue> {
249 self.remove_all().await
250 }
251 }
252
253 impl $repo_name {
254 pub fn into_channel_impl(
255 self,
256 ) -> ::std::sync::Arc<impl bitwarden_state::repository::Repository<$qualified_type_name>> {
257 use $crate::platform::repository::WasmRepositoryChannel;
258 ::std::sync::Arc::new(WasmRepositoryChannel::new(self))
259 }
260 }
261 )+
262 };
263}
264pub(crate) use create_wasm_repositories;
265
266const UNIT: Result<JsValue, JsValue> = Ok(JsValue::UNDEFINED);
267
268async fn run_convert<T: 'static, Func, Fut, Ret>(
271 runner: &::bitwarden_threading::ThreadBoundRunner<T>,
272 f: Func,
273) -> Result<Ret, RepositoryError>
274where
275 Func: FnOnce(Rc<T>) -> Fut + Send + 'static,
276 Fut: Future<Output = Result<JsValue, JsValue>>,
277 Ret: serde::de::DeserializeOwned + Send + Sync + 'static,
278{
279 runner
280 .run_in_thread(|state| async move { convert_result(f(state).await) })
281 .await
282 .expect("Task should not panic")
283}
284
285fn convert_result<T: serde::de::DeserializeOwned>(
288 result: Result<JsValue, JsValue>,
289) -> Result<T, RepositoryError> {
290 result
291 .map_err(|e| RepositoryError::Internal(format!("{e:?}")))
292 .and_then(|value| {
293 ::tsify::serde_wasm_bindgen::from_value(value)
294 .map_err(|e| RepositoryError::Internal(e.to_string()))
295 })
296}