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 remove(&self, id: String) -> Result<JsValue, JsValue>;
50}
51
52pub(crate) struct WasmRepositoryChannel<T, R: WasmRepository<T> + 'static>(
56 ThreadBoundRunner<R>,
57 PhantomData<T>,
58);
59
60impl<T, R: WasmRepository<T> + 'static> WasmRepositoryChannel<T, R> {
61 pub(crate) fn new(repository: R) -> Self {
62 Self(ThreadBoundRunner::new(repository), PhantomData)
63 }
64}
65
66#[async_trait::async_trait]
67impl<T: RepositoryItem, R: WasmRepository<T> + 'static> Repository<T>
68 for WasmRepositoryChannel<T, R>
69{
70 async fn get(&self, key: T::Key) -> Result<Option<T>, RepositoryError> {
71 let key = key.to_string();
72 run_convert(&self.0, |s| async move { s.get(key).await }).await
73 }
74 async fn list(&self) -> Result<Vec<T>, RepositoryError> {
75 run_convert(&self.0, |s| async move { s.list().await }).await
76 }
77 async fn set(&self, key: T::Key, value: T) -> Result<(), RepositoryError> {
78 let key = key.to_string();
79 run_convert(
80 &self.0,
81 |s| async move { s.set(key, value).await.and(UNIT) },
82 )
83 .await
84 }
85 async fn remove(&self, key: T::Key) -> Result<(), RepositoryError> {
86 let key = key.to_string();
87 run_convert(&self.0, |s| async move { s.remove(key).await.and(UNIT) }).await
88 }
89}
90
91#[wasm_bindgen(typescript_custom_section)]
92const REPOSITORY_CUSTOM_TS_TYPE: &'static str = r#"
93export interface Repository<T> {
94 get(id: string): Promise<T | null>;
95 list(): Promise<T[]>;
96 set(id: string, value: T): Promise<void>;
97 remove(id: string): Promise<void>;
98}
99"#;
100
101macro_rules! create_wasm_repositories {
105 ( $container_name:ident ; $( $qualified_type_name:ty, $type_name:ident, $field_name:ident, $repo_name:ident );+ $(;)? ) => {
106
107 const _: () = {
108 #[wasm_bindgen(typescript_custom_section)]
109 const REPOSITORIES_CUSTOM_TS_TYPE: &'static str = concat!(
110 "export interface ",
111 stringify!($container_name),
112 "{\n",
113 $( stringify!($field_name), ": Repository<", stringify!($type_name), "> | null;\n", )+
114 "}\n"
115 );
116 };
117
118 #[wasm_bindgen]
119 extern "C" {
120 #[wasm_bindgen(typescript_type = $container_name)]
121 pub type $container_name;
122
123 $(
124 #[wasm_bindgen(method, getter)]
125 pub fn $field_name(this: &$container_name) -> Option<$repo_name>;
126 )+
127 }
128
129 impl $container_name {
130 pub fn register_all(self, client: &bitwarden_core::platform::StateClient) {
131 $(
132 if let Some(repo) = self.$field_name() {
133 let repo = repo.into_channel_impl();
134 client.register_client_managed(repo);
135 }
136 )+
137 }
138 }
139
140 $(
141 #[wasm_bindgen]
142 extern "C" {
143 #[wasm_bindgen]
144 pub type $repo_name;
145
146 #[wasm_bindgen(method, catch)]
147 async fn get(
148 this: &$repo_name,
149 id: String,
150 ) -> Result<::wasm_bindgen::JsValue, ::wasm_bindgen::JsValue>;
151 #[wasm_bindgen(method, catch)]
152 async fn list(this: &$repo_name)
153 -> Result<::wasm_bindgen::JsValue, ::wasm_bindgen::JsValue>;
154 #[wasm_bindgen(method, catch)]
155 async fn set(
156 this: &$repo_name,
157 id: String,
158 value: $qualified_type_name,
159 ) -> Result<::wasm_bindgen::JsValue, ::wasm_bindgen::JsValue>;
160 #[wasm_bindgen(method, catch)]
161 async fn remove(
162 this: &$repo_name,
163 id: String,
164 ) -> Result<::wasm_bindgen::JsValue, ::wasm_bindgen::JsValue>;
165 }
166
167 impl $crate::platform::repository::WasmRepository<$qualified_type_name> for $repo_name {
168 async fn get(
169 &self,
170 id: String,
171 ) -> Result<::wasm_bindgen::JsValue, ::wasm_bindgen::JsValue> {
172 self.get(id).await
173 }
174 async fn list(&self) -> Result<::wasm_bindgen::JsValue, ::wasm_bindgen::JsValue> {
175 self.list().await
176 }
177 async fn set(
178 &self,
179 id: String,
180 value: $qualified_type_name,
181 ) -> Result<::wasm_bindgen::JsValue, ::wasm_bindgen::JsValue> {
182 self.set(id, value).await
183 }
184 async fn remove(
185 &self,
186 id: String,
187 ) -> Result<::wasm_bindgen::JsValue, ::wasm_bindgen::JsValue> {
188 self.remove(id).await
189 }
190 }
191
192 impl $repo_name {
193 pub fn into_channel_impl(
194 self,
195 ) -> ::std::sync::Arc<impl bitwarden_state::repository::Repository<$qualified_type_name>> {
196 use $crate::platform::repository::WasmRepositoryChannel;
197 ::std::sync::Arc::new(WasmRepositoryChannel::new(self))
198 }
199 }
200 )+
201 };
202}
203pub(crate) use create_wasm_repositories;
204
205const UNIT: Result<JsValue, JsValue> = Ok(JsValue::UNDEFINED);
206
207async fn run_convert<T: 'static, Func, Fut, Ret>(
210 runner: &::bitwarden_threading::ThreadBoundRunner<T>,
211 f: Func,
212) -> Result<Ret, RepositoryError>
213where
214 Func: FnOnce(Rc<T>) -> Fut + Send + 'static,
215 Fut: Future<Output = Result<JsValue, JsValue>>,
216 Ret: serde::de::DeserializeOwned + Send + Sync + 'static,
217{
218 runner
219 .run_in_thread(|state| async move { convert_result(f(state).await) })
220 .await
221 .expect("Task should not panic")
222}
223
224fn convert_result<T: serde::de::DeserializeOwned>(
227 result: Result<JsValue, JsValue>,
228) -> Result<T, RepositoryError> {
229 result
230 .map_err(|e| RepositoryError::Internal(format!("{e:?}")))
231 .and_then(|value| {
232 ::tsify::serde_wasm_bindgen::from_value(value)
233 .map_err(|e| RepositoryError::Internal(e.to_string()))
234 })
235}