tauri_plugin_matrix_svelte/
lib.rs

1use matrix::{
2    requests::MatrixRequest,
3    room::rooms_list::{enqueue_rooms_list_update, RoomsListUpdate},
4    singletons::{CLIENT, LOGIN_STORE_READY, REQUEST_SENDER},
5    stores::login_store::{update_login_state, LoginState},
6    try_restore_session_to_state,
7    workers::{async_main_loop, async_worker},
8};
9use serde::Deserialize;
10use stronghold::init_stronghold_client;
11use tauri::{
12    plugin::{Builder, TauriPlugin},
13    Manager, Runtime,
14};
15
16pub use models::*;
17
18#[cfg(desktop)]
19mod desktop;
20#[cfg(mobile)]
21mod mobile;
22
23mod commands;
24mod error;
25
26pub mod matrix;
27pub mod models;
28pub mod stronghold;
29pub mod utils;
30
31pub use error::{Error, Result};
32
33#[cfg(desktop)]
34use desktop::MatrixSvelte;
35#[cfg(mobile)]
36use mobile::MatrixSvelte;
37
38use crate::matrix::room::rooms_list::RoomsCollectionStatus;
39
40// Plugin config
41#[derive(Deserialize)]
42pub struct PluginConfig {
43    stronghold_password: String,
44}
45
46/// Extensions to [`tauri::App`], [`tauri::AppHandle`] and [`tauri::Window`] to access the Matrix Svelte APIs.
47pub trait MatrixSvelteExt<R: Runtime> {
48    fn matrix_svelte(&self) -> &MatrixSvelte<R>;
49}
50
51impl<R: Runtime, T: Manager<R>> crate::MatrixSvelteExt<R> for T {
52    fn matrix_svelte(&self) -> &MatrixSvelte<R> {
53        self.state::<MatrixSvelte<R>>().inner()
54    }
55}
56
57/// Initializes the plugin.
58pub fn init<R: Runtime>() -> TauriPlugin<R, PluginConfig> {
59    Builder::<R, PluginConfig>::new("matrix-svelte")
60        .invoke_handler(tauri::generate_handler![
61            commands::ping,
62            commands::login_and_create_new_session,
63            commands::submit_async_request
64        ])
65        .setup(|app, api| {
66            // Create a channel to be used between UI thread(s) and the async worker thread.
67            crate::matrix::singletons::init_broadcaster(16)
68                .expect("Couldn't init the UI broadcaster"); // TODO: adapt capacity if needed
69
70            let (sender, receiver) = tokio::sync::mpsc::unbounded_channel::<MatrixRequest>();
71            REQUEST_SENDER
72                .set(sender)
73                .expect("BUG: REQUEST_SENDER already set!");
74
75            let init_app_handle = app.app_handle().clone();
76            let stronghold_app_handle = app.app_handle().clone();
77            let main_loop_app_handle = app.app_handle().clone();
78
79            let stronghold_handle = tauri::async_runtime::spawn(async move {
80                init_stronghold_client(&stronghold_app_handle)
81                    .expect("Couldn't init stronghold client")
82            });
83
84            let _monitor = tauri::async_runtime::spawn(async move {
85                stronghold_handle
86                    .await
87                    .expect("Couldn't init stronghold client");
88                let client = try_restore_session_to_state(&init_app_handle)
89                    .await
90                    .expect("Couldn't try to restore session");
91
92                LOGIN_STORE_READY.wait();
93                let client = match client {
94                    Some(new_login) => {
95                        // Should check that the login store is available before. With the on_load store hook ?
96                        update_login_state(&init_app_handle, LoginState::Restored)
97                            .expect("Couldn't update login state");
98                        new_login
99                    }
100                    None => {
101                        println!("Waiting for login request...");
102                        update_login_state(&init_app_handle, LoginState::AwaitingForLogin)
103                            .expect("Couldn't update login state");
104                        // We await frontend to call the login command and set the client
105                        // loop until client is set
106                        CLIENT.wait();
107                        update_login_state(&init_app_handle, LoginState::LoggedIn)
108                            .expect("Couldn't update login state");
109                        CLIENT.get().unwrap().clone()
110                    }
111                };
112
113                let mut ui_event_receiver = crate::matrix::singletons::subscribe_to_events()
114                    .expect("Couldn't get UI event receiver"); // subscribe to events so the sender(s) never fail
115
116                // Spawn the actual async worker thread.
117                let mut worker_join_handle = tauri::async_runtime::spawn(async_worker(receiver));
118
119                // // Start the main loop that drives the Matrix client SDK.
120                let mut main_loop_join_handle =
121                    tauri::async_runtime::spawn(async_main_loop(main_loop_app_handle, client));
122
123                #[allow(clippy::never_loop)] // unsure if needed, just following tokio's examples.
124                loop {
125                    tokio::select! {
126                        result = &mut main_loop_join_handle => {
127                            match result {
128                                Ok(Ok(())) => {
129                                    eprintln!("BUG: main async loop task ended unexpectedly!");
130                                }
131                                Ok(Err(e)) => {
132                                    eprintln!("Error: main async loop task ended:\n\t{e:?}");
133                                    enqueue_rooms_list_update(RoomsListUpdate::Status {
134                                        status: RoomsCollectionStatus::Error(e.to_string()),
135                                    });
136                                    // enqueue_popup_notification(format!("Rooms list update error: {e}"));
137                                },
138                                Err(e) => {
139                                    eprintln!("BUG: failed to join main async loop task: {e:?}");
140                                }
141                            }
142                            break;
143                        }
144                        result = &mut worker_join_handle => {
145                            match result {
146                                Ok(Ok(())) => {
147                                    eprintln!("BUG: async worker task ended unexpectedly!");
148                                }
149                                Ok(Err(e)) => {
150                                    eprintln!("Error: async worker task ended:\n\t{e:?}");
151                                    enqueue_rooms_list_update(RoomsListUpdate::Status {
152                                        status: RoomsCollectionStatus::Error(e.to_string()),
153                                    });
154                                    // enqueue_popup_notification(format!("Rooms list update error: {e}"));
155                                },
156                                Err(e) => {
157                                    eprintln!("BUG: failed to join async worker task: {e:?}");
158                                }
159                            }
160                            break;
161                        }
162                        _ = ui_event_receiver.recv() => {
163                            #[cfg(debug_assertions)]
164                            println!("Received UI update event");
165                        }
166                    }
167                }
168            });
169            #[cfg(mobile)]
170            let matrix_svelte = mobile::init(app, api)?;
171            #[cfg(desktop)]
172            let matrix_svelte = desktop::init(app, api)?;
173            app.manage(matrix_svelte);
174            Ok(())
175        })
176        .build()
177}