Skip to content

Commit 51d5ec7

Browse files
committed
Implement SDK managed state
# Conflicts: # Cargo.lock # crates/bitwarden-state/Cargo.toml # crates/bitwarden-state/src/registry.rs # crates/bitwarden-state/src/repository.rs # crates/bitwarden-vault/src/cipher/cipher.rs # Conflicts: # crates/bitwarden-vault/src/vault_client.rs
1 parent ff6fec4 commit 51d5ec7

File tree

15 files changed

+588
-17
lines changed

15 files changed

+588
-17
lines changed

Cargo.lock

Lines changed: 93 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/bitwarden-core/src/platform/state_client.rs

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
use std::sync::Arc;
22

3-
use bitwarden_state::repository::{Repository, RepositoryItem};
3+
use bitwarden_state::{
4+
registry::StateRegistryError,
5+
repository::{Repository, RepositoryItem, RepositoryItemData},
6+
};
47

58
use crate::Client;
69

@@ -25,4 +28,25 @@ impl StateClient {
2528
pub fn get_client_managed<T: RepositoryItem>(&self) -> Option<Arc<dyn Repository<T>>> {
2629
self.client.internal.repository_map.get_client_managed()
2730
}
31+
32+
/// Initialize the database for SDK managed repositories.
33+
pub async fn initialize_database(
34+
&self,
35+
repositories: Vec<RepositoryItemData>,
36+
) -> Result<(), StateRegistryError> {
37+
self.client
38+
.internal
39+
.repository_map
40+
.initialize_database(repositories)
41+
.await
42+
}
43+
44+
/// Get a SDK managed state repository for a specific type, if it exists.
45+
pub fn get_sdk_managed<
46+
T: RepositoryItem + serde::ser::Serialize + serde::de::DeserializeOwned,
47+
>(
48+
&self,
49+
) -> Result<impl Repository<T>, StateRegistryError> {
50+
self.client.internal.repository_map.get_sdk_managed()
51+
}
2852
}

crates/bitwarden-state/Cargo.toml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,18 @@ wasm = []
1616
[dependencies]
1717
async-trait = { workspace = true }
1818
bitwarden-error = { workspace = true }
19+
bitwarden-threading = { workspace = true }
1920
log = { workspace = true }
21+
serde = { workspace = true }
22+
serde_json = { workspace = true }
2023
thiserror = { workspace = true }
24+
tokio = { workspace = true }
25+
26+
[target.'cfg(target_arch="wasm32")'.dependencies]
27+
js-sys = { workspace = true }
28+
indexed-db = ">=0.4.2, <0.5"
29+
[target.'cfg(not(target_arch="wasm32"))'.dependencies]
30+
rusqlite = ">=0.36.0, <0.37"
2131

2232
[dev-dependencies]
2333
tokio = { workspace = true, features = ["rt"] }

crates/bitwarden-state/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,5 @@ pub mod repository;
55

66
/// This module provides a registry for managing repositories of different types.
77
pub mod registry;
8+
9+
pub(crate) mod sdk_managed;

crates/bitwarden-state/src/registry.rs

Lines changed: 71 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,25 @@
11
use std::{
22
any::{Any, TypeId},
33
collections::HashMap,
4-
sync::{Arc, RwLock},
4+
sync::{Arc, OnceLock, RwLock},
55
};
66

7-
use crate::repository::{Repository, RepositoryItem};
7+
use bitwarden_error::bitwarden_error;
8+
use serde::{de::DeserializeOwned, Serialize};
9+
use thiserror::Error;
10+
11+
use crate::{
12+
repository::{Repository, RepositoryItem, RepositoryItemData},
13+
sdk_managed::{Database, SystemDatabase},
14+
};
815

916
/// A registry that contains repositories for different types of items.
1017
/// These repositories can be either managed by the client or by the SDK itself.
1118
pub struct StateRegistry {
19+
sdk_managed: RwLock<Vec<RepositoryItemData>>,
1220
client_managed: RwLock<HashMap<TypeId, Box<dyn Any + Send + Sync>>>,
21+
22+
database: OnceLock<SystemDatabase>,
1323
}
1424

1525
impl std::fmt::Debug for StateRegistry {
@@ -18,13 +28,58 @@ impl std::fmt::Debug for StateRegistry {
1828
}
1929
}
2030

31+
#[allow(missing_docs)]
32+
#[bitwarden_error(flat)]
33+
#[derive(Debug, Error)]
34+
pub enum StateRegistryError {
35+
#[error("Database is already initialized")]
36+
DatabaseAlreadyInitialized,
37+
#[error("Database is not initialized")]
38+
DatabaseNotInitialized,
39+
40+
#[error(transparent)]
41+
Database(#[from] crate::sdk_managed::DatabaseError),
42+
}
43+
2144
impl StateRegistry {
2245
/// Creates a new empty `StateRegistry`.
2346
#[allow(clippy::new_without_default)]
2447
pub fn new() -> Self {
2548
StateRegistry {
2649
client_managed: RwLock::new(HashMap::new()),
50+
database: OnceLock::new(),
51+
sdk_managed: RwLock::new(Vec::new()),
52+
}
53+
}
54+
55+
// TODO: Ideally we'd do this in new, but that would mean making the client initialization
56+
// async.
57+
// TODO: This function needs to be provided some configuration to know where to open the
58+
// database. For Sqlite:
59+
// - A folder path where the files will be stored.
60+
// - A user ID to create a unique database file per user?
61+
//
62+
// For WASM indexedDB:
63+
// - A database name to use for the indexedDB (Some prefix to avoid conflicts + user ID?)
64+
65+
/// Initializes the database used for sdk-managed repositories.
66+
pub async fn initialize_database(
67+
&self,
68+
repositories: Vec<RepositoryItemData>,
69+
) -> Result<(), StateRegistryError> {
70+
if self.database.get().is_some() {
71+
return Err(StateRegistryError::DatabaseAlreadyInitialized);
2772
}
73+
let _ = self
74+
.database
75+
.set(SystemDatabase::initialize(&repositories).await?);
76+
77+
*self
78+
.sdk_managed
79+
.write()
80+
.expect("RwLock should not be poisoned") = repositories.clone();
81+
82+
Ok(())
2883
}
2984

3085
/// Registers a client-managed repository into the map, associating it with its type.
@@ -44,6 +99,17 @@ impl StateRegistry {
4499
.and_then(|boxed| boxed.downcast_ref::<Arc<dyn Repository<T>>>())
45100
.map(Arc::clone)
46101
}
102+
103+
/// Retrieves a SDK-managed repository from the database.
104+
pub fn get_sdk_managed<T: RepositoryItem + Serialize + DeserializeOwned>(
105+
&self,
106+
) -> Result<impl Repository<T>, StateRegistryError> {
107+
if let Some(db) = self.database.get() {
108+
Ok(db.get_repository::<T>()?)
109+
} else {
110+
Err(StateRegistryError::DatabaseNotInitialized)
111+
}
112+
}
47113
}
48114

49115
#[cfg(test)]
@@ -83,9 +149,9 @@ mod tests {
83149
#[derive(PartialEq, Eq, Debug)]
84150
struct TestItem<T>(T);
85151

86-
register_repository_item!(TestItem<usize>, "TestItem<usize>");
87-
register_repository_item!(TestItem<String>, "TestItem<String>");
88-
register_repository_item!(TestItem<Vec<u8>>, "TestItem<Vec<u8>>");
152+
register_repository_item!(TestItem<usize>, "TestItem<usize>", version: 1);
153+
register_repository_item!(TestItem<String>, "TestItem<String>", version: 1);
154+
register_repository_item!(TestItem<Vec<u8>>, "TestItem<Vec<u8>>", version: 1);
89155

90156
impl_repository!(TestA, TestItem<usize>);
91157
impl_repository!(TestB, TestItem<String>);

0 commit comments

Comments
 (0)