zoe_client/client/
builder.rs1use super::ClientSecret;
2use crate::error::Result;
3use crate::services::MultiRelayMessageManager;
4use crate::services::blob_store::MultiRelayBlobService;
5use crate::{Client, ClientError, FileStorage, SessionManager};
6use async_broadcast;
7use eyeball::SharedObservable;
8use rand::Rng;
9use std::collections::BTreeMap;
10use std::net::SocketAddr;
11use std::path::PathBuf;
12use std::sync::Arc;
13use tokio::sync::RwLock;
14use zoe_app_primitives::{CompressionConfig, RelayAddress};
15use zoe_client_storage::{SqliteMessageStorage, StorageConfig};
16use zoe_wire_protocol::{KeyPair, VerifyingKey};
17
18#[cfg(feature = "frb-api")]
19use flutter_rust_bridge::frb;
20
21#[derive(Default)]
22#[cfg_attr(feature = "frb-api", frb(opaque))]
23pub struct ClientBuilder {
24 media_storage_dir: Option<PathBuf>,
25 inner_keypair: Option<Arc<KeyPair>>,
26 servers: Option<Vec<RelayAddress>>,
27 db_storage_dir: Option<PathBuf>,
28 encryption_key: Option<[u8; 32]>,
29 autoconnect: bool,
30}
31
32impl Client {
33 #[cfg_attr(feature = "frb-api", frb)]
35 pub fn builder() -> ClientBuilder {
36 ClientBuilder::default()
37 }
38}
39
40#[cfg_attr(feature = "frb-api", frb)]
41impl ClientBuilder {
42 pub fn client_secret(&mut self, secret: ClientSecret) {
43 self.servers.get_or_insert_default().extend(secret.servers);
44 self.inner_keypair = Some(secret.inner_keypair);
45 self.encryption_key = Some(secret.encryption_key);
46 }
47
48 pub fn media_storage_dir(&mut self, media_storage_dir: String) {
49 self.media_storage_dir_pathbuf(PathBuf::from(media_storage_dir));
50 }
51
52 pub fn servers(&mut self, servers: Vec<RelayAddress>) {
53 self.servers = Some(servers);
54 }
55
56 pub fn server_info(&mut self, server_public_key: VerifyingKey, server_addr: SocketAddr) {
57 self.servers.get_or_insert_default().push(
58 RelayAddress::new(server_public_key)
59 .with_address(server_addr.into())
60 .with_name("Legacy Server".to_string()),
61 );
62 }
63
64 pub fn db_storage_dir(&mut self, path: String) {
66 self.db_storage_dir_pathbuf(PathBuf::from(path));
67 }
68
69 pub fn encryption_key(&mut self, key: [u8; 32]) {
71 self.encryption_key = Some(key);
72 }
73
74 pub fn autoconnect(&mut self, autoconnect: bool) {
81 self.autoconnect = autoconnect;
82 }
83
84 pub async fn build(self) -> Result<Client> {
85 let Some(media_storage_dir) = self.media_storage_dir else {
86 return Err(ClientError::BuildError(
87 "Media storage dir is required".to_string(),
88 ));
89 };
90
91 let Some(db_storage_dir) = self.db_storage_dir else {
92 return Err(ClientError::BuildError(
93 "DB storage dir is required".to_string(),
94 ));
95 };
96
97 if self.autoconnect {
99 if self.servers.is_none() || self.servers.as_ref().unwrap().is_empty() {
100 return Err(ClientError::BuildError(
101 "At least one server is required when autoconnect is enabled".to_string(),
102 ));
103 }
104 }
105
106 let encryption_key = match self.encryption_key {
107 Some(encryption_key) => encryption_key,
108 None => rand::rngs::OsRng::default().r#gen(),
109 };
110
111 let key_pair = if let Some(key_pair) = self.inner_keypair {
112 key_pair
113 } else {
114 Arc::new(KeyPair::generate(&mut rand::rngs::OsRng))
115 };
116
117 let user_id = key_pair.id();
118 let user_id_hex = hex::encode(user_id);
119
120 let fs_path = media_storage_dir.to_path_buf().join(&user_id_hex);
122 let storage_config = StorageConfig {
123 database_path: db_storage_dir.join(&user_id_hex).join("db.sqlite"),
124 max_query_limit: None,
125 enable_wal_mode: true, cache_size_kb: Some(32 * 1024), };
128
129 let storage = Arc::new(
130 SqliteMessageStorage::new(storage_config, &encryption_key)
131 .await
132 .map_err(|e| ClientError::BuildError(format!("Failed to create storage: {}", e)))?,
133 );
134
135 let (relay_status_sender, relay_status_rx) = async_broadcast::broadcast(1000);
137 let relay_status_keeper = relay_status_rx.deactivate();
138 let message_manager = Arc::new(MultiRelayMessageManager::new(Arc::clone(&storage)));
140 let blob_service = Arc::new(MultiRelayBlobService::new(Arc::clone(&storage)));
141 let session_manager = Arc::new(
142 SessionManager::builder(Arc::clone(&storage), message_manager.clone())
143 .client_keypair(key_pair.clone())
144 .build()
145 .await
146 .map_err(|e| {
147 ClientError::BuildError(format!("Failed to create session manager: {}", e))
148 })?,
149 );
150
151 let fs =
152 FileStorage::new(&fs_path, blob_service.clone(), CompressionConfig::default()).await?;
153
154 let servers = self.servers.clone().unwrap_or_default();
155 let client_secret = ClientSecret {
156 inner_keypair: key_pair,
157 servers: servers.clone(),
158 encryption_key: encryption_key,
159 };
160
161 let client_secret_observable = SharedObservable::new(client_secret.clone());
163
164 let client = Client {
165 client_secret: Arc::new(client_secret),
166 fs: Arc::new(fs),
167 storage: Arc::clone(&storage),
168 message_manager,
169 blob_service,
170 relay_connections: Arc::new(RwLock::new(BTreeMap::new())),
171 relay_info: Arc::new(RwLock::new(BTreeMap::new())),
172 encryption_key,
173 client_secret_observable,
174 relay_status_sender: Arc::new(relay_status_sender),
175 _relay_status_keeper: Arc::new(relay_status_keeper),
176 connection_monitors: Arc::new(RwLock::new(BTreeMap::new())),
177 session_manager,
178 };
179
180 if self.autoconnect {
182 for server in servers {
183 let relay_address = server.clone();
185 let _handle = client.add_relay_background(relay_address);
186 }
187 }
188
189 Ok(client)
190 }
191}
192
193#[cfg_attr(feature = "frb-api", frb(ignore))]
194impl ClientBuilder {
196 pub fn media_storage_dir_pathbuf(&mut self, media_storage_dir: PathBuf) {
197 self.media_storage_dir = Some(PathBuf::from(media_storage_dir));
198 }
199
200 pub fn inner_keypair(&mut self, inner_keypair: KeyPair) {
201 self.inner_keypair = Some(Arc::new(inner_keypair));
202 }
203
204 pub fn db_storage_dir_pathbuf(&mut self, path: PathBuf) {
205 self.db_storage_dir = Some(path);
206 }
207}