Expand description
§bitwarden-state
This crate contains the core state handling code of the Bitwarden SDK. Its primary feature is a namespaced key-value store, accessible via the typed Repository trait.
To make use of the Repository
trait, the first thing to do is to ensure the data to be used with
it is registered to do so:
struct Cipher {
// Cipher fields
};
// Register `Cipher` for use with a `Repository`.
// This should be done in the crate where `Cipher` is defined.
bitwarden_state::register_repository_item!(Cipher, "Cipher");
With the registration complete, the next important decision is to select where will the data be stored:
-
If the application using the SDK is responsible for storing the data, it must provide its own implementation of the
Repository
trait. We call this approachClient-Managed State
orApplication-Managed State
. See the next section for details on how to implement this. -
If the SDK itself will handle data storage, we call that approach
SDK-Managed State
. The implementation of this is will a work in progress.
Note that these approaches aren’t mutually exclusive: a repository item can use both client and SDK managed state at the same time. However, this mixed approach is only recommended during migration scenarios to avoid potential confusion.
§Client-Managed State
With Client-Managed State
the application and SDK will both access the same data pool, which
simplifies the initial migration and development. Using this approach requires manual setup, as we
need to define some functions in bitwarden-wasm-internal
and bitwarden-uniffi
to allow the
applications to provide their Repository
implementations. The implementations themselves will be
very simple as we provide macros that take care of the brunt of the work.
§Client-Managed State in WASM
For WASM, we need to define a new Repository
for our type and provide a function that will accept
it. This is done in the file crates/bitwarden-wasm-internal/src/platform/mod.rs
, you can check the
provided example:
repository::create_wasm_repository!(CipherRepository, Cipher, "Repository<Cipher>");
#[wasm_bindgen]
impl StateClient {
pub fn register_cipher_repository(&self, store: CipherRepository) {
let store = store.into_channel_impl();
self.0.platform().state().register_client_managed(store)
}
}
§How to initialize Client-Managed State on the web clients
Once we have the function defined in bitwarden-wasm-internal
, we can use it from the web clients.
For that, the first thing we need to do is create a mapper between the client and SDK types. This
mapper will also contain the UserKeyDefinition
for the StateProvider
API and should be created
in the folder of the team that owns the model:
export class CipherRecordMapper implements SdkRecordMapper<CipherData, SdkCipher> {
userKeyDefinition(): UserKeyDefinition<Record<string, CipherData>> {
return ENCRYPTED_CIPHERS;
}
toSdk(value: CipherData): SdkCipher {
return new Cipher(value).toSdkCipher();
}
fromSdk(value: SdkCipher): CipherData {
throw new Error("Cipher.fromSdk is not implemented yet");
}
}
Once that is done, we should be able to register the mapper in the
libs/common/src/platform/services/sdk/client-managed-state.ts
file, inside the initializeState
function:
export async function initializeState(
userId: UserId,
stateClient: StateClient,
stateProvider: StateProvider,
): Promise<void> {
await stateClient.register_cipher_repository(
new RepositoryRecord(userId, stateProvider, new CipherRecordMapper()),
);
}
§Client-Managed State in UniFFI
For UniFFI, we need to define a new Repository
for our type and provide a function that will
accept it. This is done in the file crates/bitwarden-uniffi/src/platform/mod.rs
, you can check the
provided example:
repository::create_uniffi_repository!(CipherRepository, Cipher);
#[uniffi::export]
impl StateClient {
pub fn register_cipher_repository(&self, store: Arc<dyn CipherRepository>) {
let store_internal = UniffiRepositoryBridge::new(store);
self.0
.platform()
.state()
.register_client_managed(store_internal)
}
}
§How to initialize Client-Managed State on iOS
Once we have the function defined in bitwarden-uniffi
, we can use it from the iOS application:
class CipherStoreImpl: CipherStore {
private var cipherDataStore: CipherDataStore
private var userId: String
init(cipherDataStore: CipherDataStore, userId: String) {
self.cipherDataStore = cipherDataStore
self.userId = userId
}
func get(id: String) async -> Cipher? {
return try await cipherDataStore.fetchCipher(withId: id, userId: userId)
}
func list() async -> [Cipher] {
return try await cipherDataStore.fetchAllCiphers(userId: userId)
}
func set(id: String, value: Cipher) async { }
func remove(id: String) async { }
}
let store = CipherStoreImpl(cipherDataStore: self.cipherDataStore, userId: userId);
try await self.clientService.platform().store().registerCipherStore(store: store);
§How to initialize Client-Managed State on Android
Once we have the function defined in bitwarden-uniffi
, we can use it from the Android application:
val vaultDiskSource: VaultDiskSource ;
class CipherStoreImpl: CipherStore {
override suspend fun get(id: String): Cipher? {
return vaultDiskSource.getCiphers(userId).firstOrNull()
.orEmpty().firstOrNull { it.id == id }?.toEncryptedSdkCipher()
}
override suspend fun list(): List<Cipher> {
return vaultDiskSource.getCiphers(userId).firstOrNull()
.orEmpty().map { it.toEncryptedSdkCipher() }
}
override suspend fun set(id: String, value: Cipher) {
TODO("Not yet implemented")
}
override suspend fun remove(id: String) {
TODO("Not yet implemented")
}
}
getClient(userId = userId).platform().store().registerCipherStore(CipherStoreImpl());
§SDK-Managed State
With SDK-Managed State
, the SDK will be exclusively responsible for the data storage. This means
that the clients don’t need to make any changes themselves, as the implementation is internal to the
SDK. To add support for an SDK managed Repository
, a new migration step needs to be added to the
bitwarden-state-migrations
crate.
§How to initialize SDK-Managed State
Go to crates/bitwarden-state-migrations/src/lib.rs
and add a line with your type, as shown below.
In this example we’re registering Cipher
and Folder
as SDK managed.
/// Returns a list of all SDK-managed repository migrations.
pub fn get_sdk_managed_migrations() -> RepositoryMigrations {
use RepositoryMigrationStep::*;
RepositoryMigrations::new(vec![
// Add any new migrations here. Note that order matters, and that removing a repository
// requires a separate migration step using `Remove(...)`.
Add(Cipher::data()),
Add(Folder::data()),
])
}
Modules§
- registry
- This module provides a registry for managing repositories of different types.
- repository
- This module provides a generic repository interface for storing and retrieving items.
- sdk_
managed 🔒
Macros§
- register_
repository_ item - Register a type for use in a repository. The type must only be registered once in the crate where it’s defined. The provided name must be unique and not be changed.
Enums§
- Database
Configuration - Configuration for the database used by the SDK.
Constants§
- UNIFFI_
META_ 🔒CONST_ NAMESPACE_ BITWARDEN_ STATE - Export namespace metadata.