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