Skip to main content

bitwarden_state/sdk_managed/
mod.rs

1use std::sync::Arc;
2
3use bitwarden_error::bitwarden_error;
4use thiserror::Error;
5
6use crate::{
7    persist::Persist,
8    repository::{Repository, RepositoryError, RepositoryItem, RepositoryMigrations},
9    settings::{SettingItem, SettingTrait, SettingsError},
10};
11
12mod configuration;
13pub use configuration::DatabaseConfiguration;
14
15#[cfg(target_arch = "wasm32")]
16mod indexed_db;
17
18#[cfg(not(target_arch = "wasm32"))]
19mod sqlite;
20
21mod memory;
22pub(super) use memory::MemoryDatabase;
23
24/// Errors that can occur when interacting with the SDK-managed database.
25#[bitwarden_error(flat)]
26#[derive(Debug, Error)]
27pub enum DatabaseError {
28    /// The requested database configuration is not supported on the current platform.
29    #[error("Database not supported on this platform: {0:?}")]
30    UnsupportedConfiguration(DatabaseConfiguration),
31
32    /// A call dispatched through the thread-bound runner failed.
33    #[error(transparent)]
34    ThreadBoundRunner(#[from] bitwarden_threading::CallError),
35
36    /// Failed to serialize or deserialize a stored value.
37    #[error("Serialization error: {0}")]
38    Serialization(#[from] serde_json::Error),
39
40    /// A JavaScript error was raised by the IndexedDB backend.
41    #[error("JS error: {0}")]
42    JS(String),
43
44    /// An unexpected internal error occurred in the database backend.
45    #[error("Internal error: {0}")]
46    Internal(String),
47
48    /// The database has been closed (e.g. by [`crate::registry::StateRegistry::wipe`]) and
49    /// can no longer service operations.
50    #[error("Database is closed")]
51    Closed,
52}
53
54#[cfg(target_arch = "wasm32")]
55impl From<::indexed_db::Error<indexed_db::IndexedDbInternalError>> for DatabaseError {
56    fn from(e: ::indexed_db::Error<indexed_db::IndexedDbInternalError>) -> Self {
57        DatabaseError::Internal(e.to_string())
58    }
59}
60
61#[cfg(not(target_arch = "wasm32"))]
62impl From<rusqlite::Error> for DatabaseError {
63    fn from(e: rusqlite::Error) -> Self {
64        DatabaseError::Internal(e.to_string())
65    }
66}
67
68pub trait Database {
69    async fn initialize(
70        configuration: DatabaseConfiguration,
71        registrations: RepositoryMigrations,
72    ) -> Result<Self, DatabaseError>
73    where
74        Self: Sized;
75
76    async fn get<T: RepositoryItem>(&self, key: &str) -> Result<Option<T>, DatabaseError>;
77
78    async fn list<T: RepositoryItem>(&self) -> Result<Vec<T>, DatabaseError>;
79
80    async fn set<T: RepositoryItem>(&self, key: &str, value: T) -> Result<(), DatabaseError>;
81
82    async fn set_bulk<T: RepositoryItem>(
83        &self,
84        values: Vec<(String, T)>,
85    ) -> Result<(), DatabaseError>;
86
87    async fn remove<T: RepositoryItem>(&self, key: &str) -> Result<(), DatabaseError>;
88
89    async fn remove_bulk<T: RepositoryItem>(&self, keys: Vec<String>) -> Result<(), DatabaseError>;
90
91    async fn remove_all<T: RepositoryItem>(&self) -> Result<(), DatabaseError>;
92
93    async fn wipe(&self) -> Result<(), DatabaseError>;
94}
95
96#[derive(Clone)]
97pub(super) enum SystemDatabase {
98    #[cfg(not(target_arch = "wasm32"))]
99    Sqlite(sqlite::SqliteDatabase),
100    #[cfg(target_arch = "wasm32")]
101    IndexedDb(indexed_db::IndexedDbDatabase),
102    Memory(MemoryDatabase),
103}
104
105impl Database for SystemDatabase {
106    async fn initialize(
107        configuration: DatabaseConfiguration,
108        migrations: RepositoryMigrations,
109    ) -> Result<Self, DatabaseError> {
110        match configuration {
111            #[cfg(not(target_arch = "wasm32"))]
112            DatabaseConfiguration::Sqlite { .. } => Ok(SystemDatabase::Sqlite(
113                sqlite::SqliteDatabase::initialize(configuration, migrations).await?,
114            )),
115            #[cfg(target_arch = "wasm32")]
116            DatabaseConfiguration::IndexedDb { .. } => Ok(SystemDatabase::IndexedDb(
117                indexed_db::IndexedDbDatabase::initialize(configuration, migrations).await?,
118            )),
119            DatabaseConfiguration::Memory => Ok(SystemDatabase::Memory(MemoryDatabase::new())),
120            #[allow(unreachable_patterns)]
121            other => Err(DatabaseError::UnsupportedConfiguration(other)),
122        }
123    }
124
125    async fn get<T: RepositoryItem>(&self, key: &str) -> Result<Option<T>, DatabaseError> {
126        match self {
127            #[cfg(not(target_arch = "wasm32"))]
128            SystemDatabase::Sqlite(db) => db.get(key).await,
129            #[cfg(target_arch = "wasm32")]
130            SystemDatabase::IndexedDb(db) => db.get(key).await,
131            SystemDatabase::Memory(db) => db.get(key).await,
132        }
133    }
134
135    async fn list<T: RepositoryItem>(&self) -> Result<Vec<T>, DatabaseError> {
136        match self {
137            #[cfg(not(target_arch = "wasm32"))]
138            SystemDatabase::Sqlite(db) => db.list().await,
139            #[cfg(target_arch = "wasm32")]
140            SystemDatabase::IndexedDb(db) => db.list().await,
141            SystemDatabase::Memory(db) => db.list().await,
142        }
143    }
144
145    async fn set<T: RepositoryItem>(&self, key: &str, value: T) -> Result<(), DatabaseError> {
146        match self {
147            #[cfg(not(target_arch = "wasm32"))]
148            SystemDatabase::Sqlite(db) => db.set(key, value).await,
149            #[cfg(target_arch = "wasm32")]
150            SystemDatabase::IndexedDb(db) => db.set(key, value).await,
151            SystemDatabase::Memory(db) => db.set(key, value).await,
152        }
153    }
154
155    async fn set_bulk<T: RepositoryItem>(
156        &self,
157        values: Vec<(String, T)>,
158    ) -> Result<(), DatabaseError> {
159        match self {
160            #[cfg(not(target_arch = "wasm32"))]
161            SystemDatabase::Sqlite(db) => db.set_bulk(values).await,
162            #[cfg(target_arch = "wasm32")]
163            SystemDatabase::IndexedDb(db) => db.set_bulk(values).await,
164            SystemDatabase::Memory(db) => db.set_bulk(values).await,
165        }
166    }
167
168    async fn remove<T: RepositoryItem>(&self, key: &str) -> Result<(), DatabaseError> {
169        match self {
170            #[cfg(not(target_arch = "wasm32"))]
171            SystemDatabase::Sqlite(db) => db.remove::<T>(key).await,
172            #[cfg(target_arch = "wasm32")]
173            SystemDatabase::IndexedDb(db) => db.remove::<T>(key).await,
174            SystemDatabase::Memory(db) => db.remove::<T>(key).await,
175        }
176    }
177
178    async fn remove_bulk<T: RepositoryItem>(&self, keys: Vec<String>) -> Result<(), DatabaseError> {
179        match self {
180            #[cfg(not(target_arch = "wasm32"))]
181            SystemDatabase::Sqlite(db) => db.remove_bulk::<T>(keys).await,
182            #[cfg(target_arch = "wasm32")]
183            SystemDatabase::IndexedDb(db) => db.remove_bulk::<T>(keys).await,
184            SystemDatabase::Memory(db) => db.remove_bulk::<T>(keys).await,
185        }
186    }
187
188    async fn remove_all<T: RepositoryItem>(&self) -> Result<(), DatabaseError> {
189        match self {
190            #[cfg(not(target_arch = "wasm32"))]
191            SystemDatabase::Sqlite(db) => db.remove_all::<T>().await,
192            #[cfg(target_arch = "wasm32")]
193            SystemDatabase::IndexedDb(db) => db.remove_all::<T>().await,
194            SystemDatabase::Memory(db) => db.remove_all::<T>().await,
195        }
196    }
197
198    async fn wipe(&self) -> Result<(), DatabaseError> {
199        match self {
200            #[cfg(not(target_arch = "wasm32"))]
201            SystemDatabase::Sqlite(db) => db.wipe().await,
202            #[cfg(target_arch = "wasm32")]
203            SystemDatabase::IndexedDb(db) => db.wipe().await,
204            SystemDatabase::Memory(db) => db.wipe().await,
205        }
206    }
207}
208
209struct DBRepository<T: RepositoryItem> {
210    database: SystemDatabase,
211    _marker: std::marker::PhantomData<T>,
212}
213
214#[async_trait::async_trait]
215impl<V: RepositoryItem> Repository<V> for DBRepository<V> {
216    async fn get(&self, key: V::Key) -> Result<Option<V>, RepositoryError> {
217        let key = key.to_string();
218        let value = self.database.get::<V>(&key).await?;
219        Ok(value)
220    }
221    async fn list(&self) -> Result<Vec<V>, RepositoryError> {
222        let values = self.database.list::<V>().await?;
223        Ok(values)
224    }
225    async fn set(&self, key: V::Key, value: V) -> Result<(), RepositoryError> {
226        let key = key.to_string();
227        Ok(self.database.set::<V>(&key, value).await?)
228    }
229    async fn set_bulk(&self, values: Vec<(V::Key, V)>) -> Result<(), RepositoryError> {
230        let values = values
231            .into_iter()
232            .map(|(k, v)| (k.to_string(), v))
233            .collect();
234        Ok(self.database.set_bulk::<V>(values).await?)
235    }
236    async fn remove(&self, key: V::Key) -> Result<(), RepositoryError> {
237        let key = key.to_string();
238        Ok(self.database.remove::<V>(&key).await?)
239    }
240    async fn remove_bulk(&self, keys: Vec<V::Key>) -> Result<(), RepositoryError> {
241        let keys = keys.into_iter().map(|k| k.to_string()).collect();
242        Ok(self.database.remove_bulk::<V>(keys).await?)
243    }
244    async fn remove_all(&self) -> Result<(), RepositoryError> {
245        Ok(self.database.remove_all::<V>().await?)
246    }
247}
248
249/// Stores a single setting in the `Setting` table, keyed by `name`, as the bare serialized value.
250struct DBSetting<T> {
251    database: SystemDatabase,
252    name: &'static str,
253    _marker: std::marker::PhantomData<T>,
254}
255
256#[async_trait::async_trait]
257impl<T: Persist> SettingTrait<T> for DBSetting<T> {
258    async fn get(&self) -> Result<Option<T>, SettingsError> {
259        match self.database.get::<SettingItem>(self.name).await? {
260            Some(item) => Ok(Some(serde_json::from_value::<T>(item.0)?)),
261            None => Ok(None),
262        }
263    }
264
265    async fn set(&self, value: T) -> Result<(), SettingsError> {
266        let item = SettingItem(serde_json::to_value(&value)?);
267        Ok(self.database.set::<SettingItem>(self.name, item).await?)
268    }
269
270    async fn remove(&self) -> Result<(), SettingsError> {
271        Ok(self.database.remove::<SettingItem>(self.name).await?)
272    }
273}
274
275impl SystemDatabase {
276    pub(super) fn get_repository<V: RepositoryItem>(&self) -> Arc<dyn Repository<V>> {
277        Arc::new(DBRepository {
278            database: self.clone(),
279            _marker: std::marker::PhantomData,
280        })
281    }
282
283    pub(super) fn get_setting<T: Persist>(&self, name: &'static str) -> Arc<dyn SettingTrait<T>> {
284        Arc::new(DBSetting {
285            database: self.clone(),
286            name,
287            _marker: std::marker::PhantomData,
288        })
289    }
290}