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::{de::DeserializeOwned, Serialize};
41use wasm_bindgen::{prelude::wasm_bindgen, JsValue};
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_repository {
99 ($name:ident, $ty:ty, $typescript_ty:literal) => {
100 #[wasm_bindgen]
101 extern "C" {
102 #[wasm_bindgen(js_name = $name, typescript_type = $typescript_ty)]
103 pub type $name;
104
105 #[wasm_bindgen(method, catch)]
106 async fn get(
107 this: &$name,
108 id: String,
109 ) -> Result<::wasm_bindgen::JsValue, ::wasm_bindgen::JsValue>;
110 #[wasm_bindgen(method, catch)]
111 async fn list(this: &$name)
112 -> Result<::wasm_bindgen::JsValue, ::wasm_bindgen::JsValue>;
113 #[wasm_bindgen(method, catch)]
114 async fn set(
115 this: &$name,
116 id: String,
117 value: $ty,
118 ) -> Result<::wasm_bindgen::JsValue, ::wasm_bindgen::JsValue>;
119 #[wasm_bindgen(method, catch)]
120 async fn remove(
121 this: &$name,
122 id: String,
123 ) -> Result<::wasm_bindgen::JsValue, ::wasm_bindgen::JsValue>;
124 }
125
126 impl $crate::platform::repository::WasmRepository<$ty> for $name {
127 async fn get(
128 &self,
129 id: String,
130 ) -> Result<::wasm_bindgen::JsValue, ::wasm_bindgen::JsValue> {
131 self.get(id).await
132 }
133 async fn list(&self) -> Result<::wasm_bindgen::JsValue, ::wasm_bindgen::JsValue> {
134 self.list().await
135 }
136 async fn set(
137 &self,
138 id: String,
139 value: $ty,
140 ) -> Result<::wasm_bindgen::JsValue, ::wasm_bindgen::JsValue> {
141 self.set(id, value).await
142 }
143 async fn remove(
144 &self,
145 id: String,
146 ) -> Result<::wasm_bindgen::JsValue, ::wasm_bindgen::JsValue> {
147 self.remove(id).await
148 }
149 }
150
151 impl $name {
152 pub fn into_channel_impl(
153 self,
154 ) -> ::std::sync::Arc<impl bitwarden_state::repository::Repository<$ty>> {
155 use $crate::platform::repository::WasmRepositoryChannel;
156 ::std::sync::Arc::new(WasmRepositoryChannel::new(self))
157 }
158 }
159 };
160}
161pub(crate) use create_wasm_repository;
162
163const UNIT: Result<JsValue, JsValue> = Ok(JsValue::UNDEFINED);
164
165async fn run_convert<T: 'static, Func, Fut, Ret>(
168 runner: &::bitwarden_threading::ThreadBoundRunner<T>,
169 f: Func,
170) -> Result<Ret, RepositoryError>
171where
172 Func: FnOnce(Rc<T>) -> Fut + Send + 'static,
173 Fut: Future<Output = Result<JsValue, JsValue>>,
174 Ret: serde::de::DeserializeOwned + Send + Sync + 'static,
175{
176 runner
177 .run_in_thread(|state| async move { convert_result(f(state).await) })
178 .await
179 .expect("Task should not panic")
180}
181
182fn convert_result<T: serde::de::DeserializeOwned>(
185 result: Result<JsValue, JsValue>,
186) -> Result<T, RepositoryError> {
187 result
188 .map_err(|e| RepositoryError::Internal(format!("{e:?}")))
189 .and_then(|value| {
190 ::tsify_next::serde_wasm_bindgen::from_value(value)
191 .map_err(|e| RepositoryError::Internal(e.to_string()))
192 })
193}