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