1#![recursion_limit = "256"]
2use std::{path::PathBuf, sync::Arc};
3
4use futures::StreamExt;
5use serde::{Serialize, ser::Serializer};
6use tokio::{
7 runtime::Handle,
8 sync::{broadcast, mpsc::unbounded_channel},
9};
10use tracing::{error, info};
11use url::Url;
12
13use crate::{
14 init::{
15 FrontendAuthTypeResponse, check_homeserver_auth_type,
16 session::{setup_token_background_save, try_restore_session_to_state},
17 singletons::{
18 APP_DATA_DIR, CURRENT_USER_ID, EVENT_BRIDGE, REQUEST_SENDER,
19 VERIFICATION_RESPONSE_RECEIVER,
20 },
21 workers::{async_main_loop, async_worker},
22 },
23 models::{
24 event_bridge::EventBridge,
25 events::{
26 EmitEvent, MatrixLoginPayload, MatrixUpdateCurrentActiveRoom,
27 MatrixVerificationResponse, ToastNotificationRequest, ToastNotificationVariant,
28 },
29 state_updater::StateUpdater,
30 },
31 room::{
32 notifications::enqueue_toast_notification,
33 rooms_list::{RoomsCollectionStatus, RoomsListUpdate, enqueue_rooms_list_update},
34 },
35};
36
37pub(crate) mod account;
38pub mod commands;
39pub(crate) mod events;
40pub(crate) mod init;
41pub mod models;
42pub(crate) mod room;
43pub(crate) mod stores;
44pub(crate) mod user;
45pub(crate) mod utils;
46
47pub type Result<T> = std::result::Result<T, Error>;
48
49#[derive(Debug, thiserror::Error)]
51pub enum Error {
52 #[error(transparent)]
53 Io(#[from] std::io::Error),
54 #[error(transparent)]
55 Anyhow(#[from] anyhow::Error),
56 #[error(transparent)]
57 MatrixSdk(#[from] matrix_sdk::Error),
58}
59
60impl Serialize for Error {
61 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
62 where
63 S: Serializer,
64 {
65 serializer.serialize_str(self.to_string().as_ref())
66 }
67}
68
69pub struct EventReceivers {
71 verification_response_receiver: mpsc::Receiver<MatrixVerificationResponse>,
73 room_update_receiver: mpsc::Receiver<MatrixUpdateCurrentActiveRoom>,
74 matrix_login_receiver: mpsc::Receiver<MatrixLoginPayload>,
76 oauth_deeplink_receiver: mpsc::Receiver<Url>,
77}
78
79impl EventReceivers {
80 pub fn new(
81 verification_response_receiver: mpsc::Receiver<MatrixVerificationResponse>,
82 room_update_receiver: mpsc::Receiver<MatrixUpdateCurrentActiveRoom>,
83 matrix_login_receiver: mpsc::Receiver<MatrixLoginPayload>,
84 oauth_deeplink_receiver: mpsc::Receiver<Url>,
85 ) -> Self {
86 Self {
87 verification_response_receiver,
88 room_update_receiver,
89 matrix_login_receiver,
90 oauth_deeplink_receiver,
91 }
92 }
93}
94
95pub struct LibConfig {
97 updaters: Box<dyn StateUpdater>,
100 event_receivers: EventReceivers,
102 session_option: Option<String>,
104 app_data_dir: PathBuf,
106 oauth_client_uri: Url,
108 oauth_redirect_uri: Url,
110}
111
112impl LibConfig {
113 pub fn new(
114 updaters: Box<dyn StateUpdater>,
115 event_receivers: EventReceivers,
116 session_option: Option<String>,
117 app_data_dir: PathBuf,
118 oauth_client_uri: Url,
119 oauth_redirect_uri: Url,
120 ) -> Self {
121 Self {
122 updaters,
123 event_receivers,
124 session_option,
125 app_data_dir,
126 oauth_client_uri,
127 oauth_redirect_uri,
128 }
129 }
130}
131
132pub fn init(mut config: LibConfig) -> broadcast::Receiver<EmitEvent> {
135 APP_DATA_DIR
136 .set(config.app_data_dir)
137 .expect("Couldn't set app data dir");
138
139 let (event_bridge, broadcast_receiver) = EventBridge::new();
141 EVENT_BRIDGE
142 .set(event_bridge)
143 .expect("Couldn't set the event bridge");
144
145 let basic_init_handle = Handle::current().spawn(async move {
146 VERIFICATION_RESPONSE_RECEIVER
149 .set(tokio::sync::Mutex::new(
150 config.event_receivers.verification_response_receiver,
151 ))
152 .expect("Couldn't set the verification response receiver");
153
154 init::singletons::init_broadcaster(16).expect("Couldn't init the UI broadcaster");
156
157 let (matrix_request_sender, matrix_request_receiver) = unbounded_channel::<MatrixRequest>();
158 REQUEST_SENDER
159 .set(matrix_request_sender)
160 .expect("BUG: REQUEST_SENDER already set!");
161
162 matrix_request_receiver
163 });
164
165 let _monitor = Handle::current().spawn(async move {
166 let updaters_arc = Arc::new(config.updaters);
167 let inner_updaters = updaters_arc.clone();
168
169 setup_token_background_save(inner_updaters.clone());
171 let matrix_request_receiver = basic_init_handle.await.expect("couldn't do basic init");
172
173 let client_opt = match try_restore_session_to_state(config.session_option).await {
174 Ok(opt) => opt,
175 Err(e) => {
176 enqueue_toast_notification(ToastNotificationRequest::new(
177 format!("Failed to restore session, falling back on login. Error: {e}"),
178 None,
179 ToastNotificationVariant::Error,
180 ));
181 None
182 }
183 };
184
185 let (client, _has_been_restored) = match client_opt {
186 Some(restored) => {
187 if let Err(e) = &inner_updaters.update_login_state(
188 LoginState::Restored,
189 restored.user_id().map(|v| v.to_string()),
190 ) {
191 enqueue_toast_notification(ToastNotificationRequest::new(
192 format!("Cannot update login state. Error: {e}"),
193 None,
194 ToastNotificationVariant::Error,
195 ))
196 }
197 (restored, true)
198 }
199 None => {
200 LOGIN_STORE_READY.wait();
201 if let Err(e) =
202 &inner_updaters.update_login_state(LoginState::AwaitingForHomeserver, None)
203 {
204 enqueue_toast_notification(ToastNotificationRequest::new(
205 format!("Cannot update login state. Error: {e}"),
206 None,
207 ToastNotificationVariant::Error,
208 ))
209 }
210 info!("Waiting for homeserver selection...");
211
212 let client = if let Ok((auth_type, client)) = check_homeserver_auth_type().await {
213 let serialized_session = match auth_type {
214 FrontendAuthTypeResponse::Oauth => init::oauth::register_and_login_oauth(
215 &client,
216 config.event_receivers.oauth_deeplink_receiver,
217 &config.oauth_client_uri,
218 &config.oauth_redirect_uri,
219 )
220 .await
221 .expect("Failed to login with OAuth"),
222 FrontendAuthTypeResponse::Matrix => {
223 let login_payload = config
225 .event_receivers
226 .matrix_login_receiver
227 .recv()
228 .await
229 .expect("no login sender to listen to");
230 init::login::login_and_persist_matrix_session(
231 &client,
232 login_payload.username.clone(),
233 login_payload.password.clone(),
234 login_payload.client_name.clone(),
235 )
236 .await
237 .expect("Failed to login with Matrix Auth")
238 }
239 FrontendAuthTypeResponse::WrongUrl => {
240 enqueue_toast_notification(ToastNotificationRequest::new(
241 "The homeserver URL is incorrect".to_owned(),
242 Some("Please restart the app and try another one.".to_owned()),
243 ToastNotificationVariant::Error,
244 ));
245 "".to_owned()
246 }
247 };
248
249 if let Err(e) = &inner_updaters
250 .persist_login_session(serialized_session)
251 .await
252 {
253 enqueue_toast_notification(ToastNotificationRequest::new(
254 format!("Failed to persist login session. Error: {e}"),
255 None,
256 ToastNotificationVariant::Error,
257 ));
258 }
259
260 client
261 } else {
262 panic!("Unknown homeserver auth type")
263 };
264
265 (client, false)
266 }
267 };
268
269 CURRENT_USER_ID
270 .set(client.user_id().unwrap().to_owned())
271 .expect("Couldn't set CURRENT_USER_ID singleton");
272
273 let user_avatar = client.account().get_avatar_url().await.map_or(None, |a| a);
274
275 let user_display_name = client
276 .account()
277 .get_display_name()
278 .await
279 .map_or(None, |n| n);
280
281 let device_name = client
282 .encryption()
283 .get_own_device()
284 .await
285 .ok()
286 .flatten()
287 .and_then(|d| d.display_name().map(|s| s.to_owned()));
288
289 if let Err(e) = inner_updaters.update_current_user_info(
290 Some(CURRENT_USER_ID.get().unwrap().to_owned()),
291 user_avatar,
292 user_display_name,
293 device_name,
294 ) {
295 enqueue_toast_notification(ToastNotificationRequest::new(
296 format!("Cannot update current user info. Error: {e}"),
297 None,
298 ToastNotificationVariant::Error,
299 ))
300 }
301
302 if let Err(e) = &inner_updaters.update_login_state(
304 LoginState::LoggedIn,
305 CURRENT_USER_ID.get().map(|u| u.to_string()),
306 ) {
307 error!("Cannot update frontend login store. {e}");
308 enqueue_toast_notification(ToastNotificationRequest::new(
309 format!("Cannot update login state. Error: {e}"),
310 None,
311 ToastNotificationVariant::Error,
312 ))
313 }
314
315 let mut verification_subscriber = client.encryption().verification_state();
316
317 let verification_state_updaters = inner_updaters.clone();
318 tokio::task::spawn(async move {
319 while let Some(state) = verification_subscriber.next().await {
320 if let Err(e) = verification_state_updaters
321 .update_verification_state(FrontendVerificationState::new(state))
322 {
323 enqueue_toast_notification(ToastNotificationRequest::new(
324 format!("Cannot update verification store. Error: {e}"),
325 None,
326 ToastNotificationVariant::Error,
327 ))
328 }
329 }
330 });
331
332 let mut state_stream = client.encryption().recovery().state_stream();
333 let recovery_state_updaters = inner_updaters.clone();
334 tokio::task::spawn(async move {
335 while let Some(update) = state_stream.next().await {
336 recovery_state_updaters
337 .update_recovery_state(update)
338 .expect("couldn't update frontend recovery state")
339 }
340 });
341
342 let mut ui_event_receiver =
343 init::singletons::subscribe_to_events().expect("Couldn't get UI event receiver"); let mut worker_join_handle = Handle::current().spawn(async_worker(matrix_request_receiver));
347
348 let mut main_loop_join_handle = Handle::current().spawn(async_main_loop(
350 client,
351 updaters_arc,
352 config.event_receivers.room_update_receiver,
353 ));
354
355 LOGIN_STORE_READY.wait();
356
357 #[allow(clippy::never_loop)] loop {
359 tokio::select! {
360 result = &mut main_loop_join_handle => {
361 match result {
362 Ok(Ok(())) => {
363 error!("BUG: main async loop task ended unexpectedly!");
364 }
365 Ok(Err(e)) => {
366 error!("Error: main async loop task ended:\n\t{e:?}");
367 enqueue_rooms_list_update(RoomsListUpdate::Status {
368 status: RoomsCollectionStatus::Error(e.to_string()),
369 });
370 enqueue_toast_notification(ToastNotificationRequest::new(
371 format!("Rooms list update error: {e}"),
372 None,
373 ToastNotificationVariant::Error,
374 ));
375 },
376 Err(e) => {
377 error!("BUG: failed to join main async loop task: {e:?}");
378 }
379 }
380 break;
381 }
382 result = &mut worker_join_handle => {
383 match result {
384 Ok(Ok(())) => {
385 error!("BUG: async worker task ended unexpectedly!");
386 }
387 Ok(Err(e)) => {
388 error!("Error: async worker task ended:\n\t{e:?}");
389 enqueue_rooms_list_update(RoomsListUpdate::Status {
390 status: RoomsCollectionStatus::Error(e.to_string()),
391 });
392 enqueue_toast_notification(ToastNotificationRequest::new(
393 format!("Rooms list update error: {e}"),
394 None,
395 ToastNotificationVariant::Error,
396 ));
397 },
398 Err(e) => {
399 error!("BUG: failed to join async worker task: {e:?}");
400 }
401 }
402 break;
403 }
404 _ = ui_event_receiver.recv() => {
405 #[cfg(debug_assertions)]
406 tracing::trace!("Received UI update event");
407 }
408 }
409 }
410 });
411
412 broadcast_receiver
414}
415
416pub use init::session::FullMatrixSession;
419pub use init::singletons::{CLIENT, LOGIN_STORE_READY};
420pub use models::async_requests::*;
421pub use room::frontend_events::events_dto::FrontendTimelineItem;
422pub use room::room_screen::RoomScreen;
423pub use room::rooms_list::RoomsList;
424pub use stores::login_store::{FrontendSyncServiceState, FrontendVerificationState, LoginState};
425pub use user::user_profile::UserProfile;
426pub use matrix_sdk::AuthSession;
428pub use matrix_sdk::attachment::{
429 AttachmentInfo, BaseAudioInfo, BaseFileInfo, BaseImageInfo, BaseVideoInfo, Thumbnail,
430};
431pub use matrix_sdk::encryption::recovery::RecoveryState;
432pub use matrix_sdk::media::{MediaFormat, MediaRequestParameters, MediaThumbnailSettings};
433pub use matrix_sdk::ruma::serde::base64::{Base64, Standard, UrlSafe};
434pub use matrix_sdk::ruma::{
435 OwnedDeviceId, OwnedMxcUri, OwnedRoomId, OwnedUserId, UInt,
436 events::room::{
437 EncryptedFile, EncryptedFileHashes, EncryptedFileInfo, MediaSource, V2EncryptedFileInfo,
438 },
439 media::Method,
440};
441pub use tokio::sync::mpsc;
442pub use tokio::sync::oneshot;