1use std::sync::Arc;
2
3use bitwarden_api_api::models::SyncResponseModel;
4use bitwarden_core::{
5 Client,
6 client::{ApiConfigurations, FromClientPart},
7};
8use bitwarden_state::Setting;
9use chrono::{DateTime, Utc};
10use serde::{Deserialize, Serialize};
11use thiserror::Error;
12use tokio::sync::Mutex;
13
14use crate::{
15 SyncErrorHandler, SyncHandler, SyncHandlerError, registry::HandlerRegistry, state::LAST_SYNC,
16};
17
18#[allow(missing_docs)]
19#[derive(Debug, Error)]
20pub enum SyncError {
21 #[error(transparent)]
22 Api(#[from] bitwarden_core::ApiError),
23
24 #[error("Sync event handler failed: {0}")]
25 HandlerFailed(#[source] SyncHandlerError),
26
27 #[error("Account has been deleted on the server.")]
28 AccountDeleted,
29}
30
31#[allow(missing_docs)]
32#[derive(Serialize, Deserialize, Debug, Clone)]
33#[serde(rename_all = "camelCase", deny_unknown_fields)]
34pub struct SyncRequest {
35 #[serde(default)]
37 pub force: bool,
38 pub exclude_subdomains: Option<bool>,
40}
41
42pub struct SyncClient {
47 api_configurations: Arc<ApiConfigurations>,
48 sync_handlers: HandlerRegistry<dyn SyncHandler>,
49 error_handlers: HandlerRegistry<dyn SyncErrorHandler>,
50 sync_lock: Mutex<()>,
51 last_sync: Option<Setting<DateTime<Utc>>>,
52}
53
54impl SyncClient {
55 pub fn new(client: Client) -> Self {
57 Self {
58 api_configurations: client.get_part(),
59 sync_handlers: HandlerRegistry::new(),
60 error_handlers: HandlerRegistry::new(),
61 sync_lock: Mutex::new(()),
62 last_sync: client.platform().state().setting(LAST_SYNC).ok(),
63 }
64 }
65
66 pub async fn last_sync(&self) -> Option<DateTime<Utc>> {
68 match self.last_sync.as_ref()?.get().await {
69 Ok(value) => value,
70 Err(e) => {
71 tracing::warn!("Failed to read last sync timestamp: {e}");
72 None
73 }
74 }
75 }
76
77 pub fn register_sync_handler(&self, handler: Arc<dyn SyncHandler>) {
82 self.sync_handlers.register(handler);
83 }
84
85 pub fn register_error_handler(&self, handler: Arc<dyn SyncErrorHandler>) {
91 self.error_handlers.register(handler);
92 }
93
94 pub async fn sync(&self, request: SyncRequest) -> Result<SyncResponseModel, SyncError> {
108 let _guard = self.sync_lock.lock().await;
110
111 let result = async {
112 let response = self.perform_sync(&request).await?;
113 self.run_handlers(&response).await?;
114 Ok(response)
115 }
116 .await;
117
118 if let Err(ref error) = result {
119 self.run_error_handlers(error).await;
120 }
121
122 result
123 }
124
125 async fn run_handlers(&self, response: &SyncResponseModel) -> Result<(), SyncError> {
133 let handlers = self.sync_handlers.handlers();
134
135 for handler in &handlers {
136 handler
137 .on_sync(response)
138 .await
139 .map_err(SyncError::HandlerFailed)?;
140 }
141
142 for handler in &handlers {
143 handler.on_sync_complete().await;
144 }
145
146 Ok(())
147 }
148
149 async fn run_error_handlers(&self, error: &SyncError) {
153 for handler in &self.error_handlers.handlers() {
154 handler.on_error(error).await;
155 }
156 }
157
158 async fn perform_sync(&self, input: &SyncRequest) -> Result<SyncResponseModel, SyncError> {
160 let sync = self
161 .api_configurations
162 .api_client
163 .sync_api()
164 .get(input.exclude_subdomains)
165 .await
166 .map_err(|e| SyncError::Api(e.into()))?;
167
168 Ok(sync)
169 }
170}
171
172pub trait SyncClientExt {
177 fn sync(&self) -> SyncClient;
179}
180
181impl SyncClientExt for Client {
182 fn sync(&self) -> SyncClient {
183 SyncClient::new(self.clone())
184 }
185}
186
187#[cfg(test)]
188mod tests {
189 use std::sync::{Arc, Mutex};
190
191 use super::*;
192
193 struct TestHandler {
194 name: String,
195 execution_log: Arc<Mutex<Vec<String>>>,
196 should_fail: bool,
197 }
198
199 #[async_trait::async_trait]
200 impl SyncHandler for TestHandler {
201 async fn on_sync(&self, _response: &SyncResponseModel) -> Result<(), SyncHandlerError> {
202 self.execution_log.lock().unwrap().push(self.name.clone());
203 if self.should_fail {
204 Err("Handler failed".into())
205 } else {
206 Ok(())
207 }
208 }
209 }
210
211 struct TestErrorHandler {
212 name: String,
213 error_log: Arc<Mutex<Vec<String>>>,
214 }
215
216 #[async_trait::async_trait]
217 impl SyncErrorHandler for TestErrorHandler {
218 async fn on_error(&self, _error: &SyncError) {
219 self.error_log.lock().unwrap().push(self.name.clone());
220 }
221 }
222
223 fn test_client(api_client: bitwarden_api_api::apis::ApiClient) -> SyncClient {
225 let dummy_config = bitwarden_api_api::Configuration::new(String::new());
226 SyncClient {
227 api_configurations: Arc::new(ApiConfigurations {
228 api_client,
229 identity_client: bitwarden_api_identity::apis::ApiClient::new_mocked(|_| {}),
230 api_config: dummy_config.clone(),
231 identity_config: dummy_config,
232 device_type: bitwarden_core::client::DeviceType::SDK,
233 }),
234 sync_handlers: HandlerRegistry::new(),
235 error_handlers: HandlerRegistry::new(),
236 sync_lock: tokio::sync::Mutex::new(()),
237 last_sync: None,
238 }
239 }
240
241 #[tokio::test]
242 async fn test_handlers_execute_in_registration_order() {
243 let client = test_client(bitwarden_api_api::apis::ApiClient::new_mocked(|_| {}));
244 let log = Arc::new(Mutex::new(Vec::new()));
245
246 client.register_sync_handler(Arc::new(TestHandler {
247 name: "first".to_string(),
248 execution_log: log.clone(),
249 should_fail: false,
250 }));
251 client.register_sync_handler(Arc::new(TestHandler {
252 name: "second".to_string(),
253 execution_log: log.clone(),
254 should_fail: false,
255 }));
256 client.register_sync_handler(Arc::new(TestHandler {
257 name: "third".to_string(),
258 execution_log: log.clone(),
259 should_fail: false,
260 }));
261
262 let response = SyncResponseModel::default();
263 client.run_handlers(&response).await.unwrap();
264
265 assert_eq!(
266 *log.lock().unwrap(),
267 vec!["first", "second", "third"],
268 "Handlers should execute in registration order"
269 );
270 }
271
272 #[tokio::test]
273 async fn test_handler_error_stops_subsequent_handlers() {
274 let client = test_client(bitwarden_api_api::apis::ApiClient::new_mocked(|_| {}));
275 let log = Arc::new(Mutex::new(Vec::new()));
276
277 client.register_sync_handler(Arc::new(TestHandler {
278 name: "first".to_string(),
279 execution_log: log.clone(),
280 should_fail: false,
281 }));
282 client.register_sync_handler(Arc::new(TestHandler {
283 name: "second".to_string(),
284 execution_log: log.clone(),
285 should_fail: true,
286 }));
287 client.register_sync_handler(Arc::new(TestHandler {
288 name: "third".to_string(),
289 execution_log: log.clone(),
290 should_fail: false,
291 }));
292
293 let response = SyncResponseModel::default();
294 let result = client.run_handlers(&response).await;
295
296 assert!(result.is_err(), "Should return error when handler fails");
297 assert_eq!(
298 *log.lock().unwrap(),
299 vec!["first", "second"],
300 "Third handler should not execute after second handler fails"
301 );
302 }
303
304 #[tokio::test]
305 async fn test_sync_success_calls_handlers_and_returns_response() {
306 let client = test_client(bitwarden_api_api::apis::ApiClient::new_mocked(|mock| {
307 mock.sync_api
308 .expect_get()
309 .returning(|_| Ok(SyncResponseModel::default()));
310 }));
311 let sync_log = Arc::new(Mutex::new(Vec::new()));
312 let error_log = Arc::new(Mutex::new(Vec::new()));
313
314 client.register_sync_handler(Arc::new(TestHandler {
315 name: "handler".to_string(),
316 execution_log: sync_log.clone(),
317 should_fail: false,
318 }));
319 client.register_error_handler(Arc::new(TestErrorHandler {
320 name: "error_handler".to_string(),
321 error_log: error_log.clone(),
322 }));
323
324 let result = client
325 .sync(SyncRequest {
326 force: false,
327 exclude_subdomains: None,
328 })
329 .await;
330
331 assert!(result.is_ok(), "Sync should succeed");
332 assert_eq!(
333 *sync_log.lock().unwrap(),
334 vec!["handler"],
335 "Sync handler should be called on success"
336 );
337 assert!(
338 error_log.lock().unwrap().is_empty(),
339 "Error handlers should not be called on success"
340 );
341 }
342
343 #[tokio::test]
344 async fn test_sync_error_notifies_error_handlers() {
345 let client = test_client(bitwarden_api_api::apis::ApiClient::new_mocked(|mock| {
346 mock.sync_api
347 .expect_get()
348 .returning(|_| Err(std::io::Error::other("test error").into()));
349 }));
350 let error_log = Arc::new(Mutex::new(Vec::new()));
351
352 client.register_error_handler(Arc::new(TestErrorHandler {
353 name: "first".to_string(),
354 error_log: error_log.clone(),
355 }));
356 client.register_error_handler(Arc::new(TestErrorHandler {
357 name: "second".to_string(),
358 error_log: error_log.clone(),
359 }));
360
361 let result = client
363 .sync(SyncRequest {
364 force: false,
365 exclude_subdomains: None,
366 })
367 .await;
368
369 assert!(result.is_err());
370 assert_eq!(
371 *error_log.lock().unwrap(),
372 vec!["first", "second"],
373 "All error handlers should be called on sync failure"
374 );
375 }
376}