Skip to content

Commit ff6fec4

Browse files
committed
Add docs
1 parent f5f06fa commit ff6fec4

File tree

2 files changed

+176
-2
lines changed

2 files changed

+176
-2
lines changed

crates/bitwarden-state/README.md

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,175 @@
11
# bitwarden-state
2+
3+
This crate contains the core state handling code of the Bitwarden SDK. Its primary feature is a
4+
namespaced key-value store, accessible via the typed [Repository](crate::repository::Repository)
5+
trait.
6+
7+
To make use of the `Repository` trait, the first thing to do is to ensure the data to be used with
8+
it is registered to do so:
9+
10+
```rust
11+
struct Cipher {
12+
// Cipher fields
13+
};
14+
15+
// Register `Cipher` for use with a `Repository`.
16+
// This should be done in the crate where `Cipher` is defined.
17+
bitwarden_state::register_repository_item!(Cipher, "Cipher");
18+
```
19+
20+
With the registration complete, the next important decision is to select where will the data be
21+
stored:
22+
23+
- If the application using the SDK is responsible for storing the data, it must provide its own
24+
implementation of the `Repository` trait. We call this approach `Client-Managed State` or
25+
`Application-Managed State`. See the next section for details on how to implement this.
26+
27+
- If the SDK itself will handle data storage, we call that approach `SDK-Managed State`. The
28+
implementation of this is will a work in progress.
29+
30+
## Client-Managed State
31+
32+
With `Client-Managed State` the application and SDK will both access the same data pool, which
33+
simplifies the initial migration and development. Using this approach requires manual setup, as we
34+
need to define some functions in `bitwarden-wasm-internal` and `bitwarden-uniffi` to allow the
35+
applications to provide their `Repository` implementations. The implementations themselves will be
36+
very simple as we provide macros that take care of the brunt of the work.
37+
38+
### Client-Managed State in WASM
39+
40+
For WASM, we need to define a new `Repository` for our type and provide a function that will accept
41+
it. This is done in the file `crates/bitwarden-wasm-internal/src/platform/mod.rs`, you can check the
42+
provided example:
43+
44+
```rust,ignore
45+
repository::create_wasm_repository!(CipherRepository, Cipher, "Repository<Cipher>");
46+
47+
#[wasm_bindgen]
48+
impl StateClient {
49+
pub fn register_cipher_repository(&self, store: CipherRepository) {
50+
let store = store.into_channel_impl();
51+
self.0.platform().state().register_client_managed(store)
52+
}
53+
}
54+
```
55+
56+
#### How to use it on web clients
57+
58+
Once we have the function defined in `bitwarden-wasm-internal`, we can use it from the web clients.
59+
For that, the first thing we need to do is create a mapper between the client and SDK types. This
60+
mapper will also contain the `UserKeyDefinition` for the `StateProvider` API and should be created
61+
in the folder of the team that owns the model:
62+
63+
```typescript
64+
export class CipherRecordMapper implements SdkRecordMapper<CipherData, SdkCipher> {
65+
userKeyDefinition(): UserKeyDefinition<Record<string, CipherData>> {
66+
return ENCRYPTED_CIPHERS;
67+
}
68+
69+
toSdk(value: CipherData): SdkCipher {
70+
return new Cipher(value).toSdkCipher();
71+
}
72+
73+
fromSdk(value: SdkCipher): CipherData {
74+
throw new Error("Cipher.fromSdk is not implemented yet");
75+
}
76+
}
77+
```
78+
79+
Once that is done, we should be able to register the mapper in the
80+
`libs/common/src/platform/services/sdk/client-managed-state.ts` file, inside the `initializeState`
81+
function:
82+
83+
```typescript
84+
export async function initializeState(
85+
userId: UserId,
86+
stateClient: StateClient,
87+
stateProvider: StateProvider,
88+
): Promise<void> {
89+
await stateClient.register_cipher_repository(
90+
new RepositoryRecord(userId, stateProvider, new CipherRecordMapper()),
91+
);
92+
}
93+
```
94+
95+
### Client-Managed State in UniFFI
96+
97+
For UniFFI, we need to define a new `Repository` for our type and provide a function that will
98+
accept it. This is done in the file `crates/bitwarden-uniffi/src/platform/mod.rs`, you can check the
99+
provided example:
100+
101+
```rust,ignore
102+
repository::create_uniffi_repository!(CipherRepository, Cipher);
103+
104+
#[uniffi::export]
105+
impl StateClient {
106+
pub fn register_cipher_repository(&self, store: Arc<dyn CipherRepository>) {
107+
let store_internal = UniffiRepositoryBridge::new(store);
108+
self.0
109+
.platform()
110+
.state()
111+
.register_client_managed(store_internal)
112+
}
113+
}
114+
```
115+
116+
#### How to use it on iOS
117+
118+
Once we have the function defined in `bitwarden-uniffi`, we can use it from the iOS application:
119+
120+
```swift
121+
class CipherStoreImpl: CipherStore {
122+
private var cipherDataStore: CipherDataStore
123+
private var userId: String
124+
125+
init(cipherDataStore: CipherDataStore, userId: String) {
126+
self.cipherDataStore = cipherDataStore
127+
self.userId = userId
128+
}
129+
130+
func get(id: String) async -> Cipher? {
131+
return try await cipherDataStore.fetchCipher(withId: id, userId: userId)
132+
}
133+
134+
func list() async -> [Cipher] {
135+
return try await cipherDataStore.fetchAllCiphers(userId: userId)
136+
}
137+
138+
func set(id: String, value: Cipher) async { }
139+
140+
func remove(id: String) async { }
141+
}
142+
143+
let store = CipherStoreImpl(cipherDataStore: self.cipherDataStore, userId: userId);
144+
try await self.clientService.platform().store().registerCipherStore(store: store);
145+
```
146+
147+
### How to use it on Android
148+
149+
Once we have the function defined in `bitwarden-uniffi`, we can use it from the Android application:
150+
151+
```kotlin
152+
val vaultDiskSource: VaultDiskSource ;
153+
154+
class CipherStoreImpl: CipherStore {
155+
override suspend fun get(id: String): Cipher? {
156+
return vaultDiskSource.getCiphers(userId).firstOrNull()
157+
.orEmpty().firstOrNull { it.id == id }?.toEncryptedSdkCipher()
158+
}
159+
160+
override suspend fun list(): List<Cipher> {
161+
return vaultDiskSource.getCiphers(userId).firstOrNull()
162+
.orEmpty().map { it.toEncryptedSdkCipher() }
163+
}
164+
165+
override suspend fun set(id: String, value: Cipher) {
166+
TODO("Not yet implemented")
167+
}
168+
169+
override suspend fun remove(id: String) {
170+
TODO("Not yet implemented")
171+
}
172+
}
173+
174+
getClient(userId = userId).platform().store().registerCipherStore(CipherStoreImpl());
175+
```

crates/bitwarden-state/src/repository.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ pub trait Repository<V: RepositoryItem>: Send + Sync {
2424

2525
/// This trait is used to mark types that can be stored in a repository.
2626
/// It should not be implemented manually; instead, users should
27-
/// use the [register_repository_item] macro to register their item types.
27+
/// use the [crate::register_repository_item] macro to register their item types.
2828
pub trait RepositoryItem: Internal + Send + Sync + 'static {
2929
/// The name of the type implementing this trait.
3030
const NAME: &'static str;
@@ -49,7 +49,7 @@ macro_rules! register_repository_item {
4949
}
5050

5151
/// This code is not meant to be used directly, users of this crate should use the
52-
/// [register_repository_item] macro to register their types.
52+
/// [crate::register_repository_item] macro to register their types.
5353
#[doc(hidden)]
5454
pub mod ___internal {
5555

0 commit comments

Comments
 (0)