[fractal] login: Handle matrix client creation in Login
- From: Julian Sparber <jsparber src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [fractal] login: Handle matrix client creation in Login
- Date: Tue, 11 Oct 2022 13:50:04 +0000 (UTC)
commit ba430b50ccb9916815435c14d6fbf857cb4d13f1
Author: Kévin Commaille <zecakeh tedomum fr>
Date: Thu Oct 6 12:56:36 2022 +0200
login: Handle matrix client creation in Login
src/login/mod.rs | 530 ++++++++++++++++++++++++++++++++++++-----------------
src/session/mod.rs | 292 +++--------------------------
src/window.rs | 35 ++--
3 files changed, 405 insertions(+), 452 deletions(-)
---
diff --git a/src/login/mod.rs b/src/login/mod.rs
index 8b3714223..df64452cc 100644
--- a/src/login/mod.rs
+++ b/src/login/mod.rs
@@ -1,10 +1,18 @@
+use std::{fs, path::PathBuf};
+
use adw::{prelude::*, subclass::prelude::BinImpl};
use gettextrs::gettext;
-use gtk::{self, gio, glib, glib::clone, subclass::prelude::*, CompositeTemplate};
-use log::{debug, warn};
+use gtk::{self, gdk, gio, glib, glib::clone, subclass::prelude::*, CompositeTemplate};
+use log::{error, warn};
use matrix_sdk::{
- config::RequestConfig, ruma::api::client::session::get_login_types::v3::LoginType, Client,
+ config::{RequestConfig, StoreConfig},
+ ruma::api::client::session::get_login_types::v3::LoginType,
+ store::{MigrationConflictStrategy, OpenStoreError, SledStateStore},
+ Client, ClientBuildError, StoreError,
};
+use rand::{distributions::Alphanumeric, thread_rng, Rng};
+use ruma::OwnedServerName;
+use thiserror::Error;
use url::Url;
mod advanced_dialog;
@@ -18,8 +26,11 @@ use self::{
method_page::LoginMethodPage, sso_page::LoginSsoPage,
};
use crate::{
- components::SpinnerButton, spawn, spawn_tokio, toast, user_facing_error::UserFacingError,
- Session,
+ components::SpinnerButton,
+ secret::{self, Secret, StoredSession},
+ spawn, spawn_tokio, toast,
+ user_facing_error::UserFacingError,
+ Session, Window,
};
mod imp {
@@ -36,7 +47,8 @@ mod imp {
#[derive(Debug, Default, CompositeTemplate)]
#[template(resource = "/org/gnome/Fractal/login.ui")]
pub struct Login {
- pub current_session: RefCell<Option<Session>>,
+ /// The current created Matrix client and its configuration.
+ pub created_client: RefCell<Option<CreatedClient>>,
#[template_child]
pub back_button: TemplateChild<gtk::Button>,
#[template_child]
@@ -81,7 +93,9 @@ mod imp {
klass.install_action("login.prev", None, move |widget, _, _| widget.go_previous());
klass.install_action("login.sso", Some("ms"), move |widget, _, variant| {
let idp_id = variant.and_then(|v| v.get::<Option<String>>()).flatten();
- widget.login_with_sso(idp_id);
+ spawn!(clone!(@weak widget => async move {
+ widget.login_with_sso(idp_id).await;
+ }));
});
klass.install_action("login.open-advanced", None, move |widget, _, _| {
spawn!(clone!(@weak widget => async move {
@@ -172,6 +186,10 @@ mod imp {
obj.update_network_state();
}
+
+ fn dispose(&self, obj: &Self::Type) {
+ obj.prune_created_client(true);
+ }
}
impl WidgetImpl for Login {}
@@ -190,6 +208,45 @@ impl Login {
glib::Object::new(&[]).expect("Failed to create Login")
}
+ fn parent_window(&self) -> Window {
+ self.root()
+ .and_then(|root| root.downcast().ok())
+ .expect("Login needs to have a parent window")
+ }
+
+ pub fn created_client(&self) -> Option<CreatedClient> {
+ self.imp().created_client.borrow().clone()
+ }
+
+ pub fn client(&self) -> Option<Client> {
+ self.imp()
+ .created_client
+ .borrow()
+ .as_ref()
+ .map(|c| c.client.clone())
+ }
+
+ pub fn prune_created_client(&self, clean: bool) {
+ if let Some(created_client) = self.imp().created_client.take() {
+ if clean {
+ if let Err(error) = fs::remove_dir_all(created_client.path) {
+ error!("Failed to remove newly-created database: {}", error);
+ }
+ }
+ }
+ }
+
+ async fn set_created_client(&self, created_client: Option<CreatedClient>) {
+ let homeserver = if let Some(c) = &created_client {
+ Some(c.client.homeserver().await)
+ } else {
+ None
+ };
+
+ self.set_homeserver(homeserver);
+ self.imp().created_client.replace(created_client);
+ }
+
pub fn homeserver(&self) -> Option<Url> {
self.imp().homeserver.borrow().clone()
}
@@ -203,7 +260,7 @@ impl Login {
}
pub fn set_homeserver(&self, homeserver: Option<Url>) {
- let priv_ = imp::Login::from_instance(self);
+ let priv_ = self.imp();
if self.homeserver() == homeserver {
return;
@@ -228,17 +285,20 @@ impl Login {
}
fn visible_child(&self) -> String {
- let priv_ = imp::Login::from_instance(self);
- priv_.main_stack.visible_child_name().unwrap().into()
+ self.imp().main_stack.visible_child_name().unwrap().into()
}
fn set_visible_child(&self, visible_child: &str) {
- let priv_ = imp::Login::from_instance(self);
- priv_.main_stack.set_visible_child_name(visible_child);
+ // Clean up the created client when we come back to the homeserver selection.
+ if visible_child == "homeserver" {
+ self.prune_created_client(true);
+ }
+
+ self.imp().main_stack.set_visible_child_name(visible_child);
}
fn update_next_state(&self) {
- let priv_ = imp::Login::from_instance(self);
+ let priv_ = self.imp();
match self.visible_child().as_ref() {
"homeserver" => {
self.enable_next_action(priv_.homeserver_page.can_go_next());
@@ -262,22 +322,28 @@ impl Login {
}
fn go_next(&self) {
- match self.visible_child().as_ref() {
- "homeserver" => {
- if self.autodiscovery() {
- self.try_autodiscovery();
- } else {
- self.check_homeserver();
+ self.freeze();
+
+ spawn!(
+ glib::PRIORITY_DEFAULT_IDLE,
+ clone!(@weak self as obj => async move {
+ match obj.visible_child().as_ref() {
+ "homeserver" => obj.get_homeserver().await,
+ "method" => obj.login_with_password().await,
+ _ => {}
}
- }
- "method" => self.login_with_password(),
- _ => {}
- }
+
+ obj.unfreeze();
+ })
+ );
}
fn go_previous(&self) {
match self.visible_child().as_ref() {
- "method" => self.set_visible_child("homeserver"),
+ "method" => {
+ self.set_visible_child("homeserver");
+ self.imp().method_page.clean();
+ }
"sso" => {
self.set_visible_child(if self.imp().supports_password.get() {
"method"
@@ -292,74 +358,48 @@ impl Login {
}
async fn open_advanced_dialog(&self) {
- let dialog =
- LoginAdvancedDialog::new(self.root().unwrap().downcast_ref::<gtk::Window>().unwrap());
+ let dialog = LoginAdvancedDialog::new(self.parent_window().upcast_ref());
self.bind_property("autodiscovery", &dialog, "autodiscovery")
.flags(glib::BindingFlags::SYNC_CREATE | glib::BindingFlags::BIDIRECTIONAL)
.build();
dialog.run_future().await;
}
- fn try_autodiscovery(&self) {
- let server = self.imp().homeserver_page.server_name().unwrap();
-
- self.freeze();
-
- let handle =
- spawn_tokio!(async move { Client::builder().server_name(&server).build().await });
-
- spawn!(
- glib::PRIORITY_DEFAULT_IDLE,
- clone!(@weak self as obj => async move {
- match handle.await.unwrap() {
- Ok(client) => {
- let homeserver = client.homeserver().await;
- obj.set_homeserver(Some(homeserver));
- obj.check_login_types(client).await;
- }
- Err(error) => {
- warn!("Failed to discover homeserver: {}", error);
- toast!(obj, error.to_user_facing());
- }
- };
- obj.unfreeze();
- })
- );
- }
-
- fn check_homeserver(&self) {
- let homeserver = self.imp().homeserver_page.homeserver_url().unwrap();
- let homeserver_clone = homeserver.clone();
+ async fn get_homeserver(&self) {
+ let autodiscovery = self.autodiscovery();
+ let homeserver_page = &self.imp().homeserver_page;
- self.freeze();
+ let homeserver = if autodiscovery {
+ HomeserverOrServerName::ServerName(homeserver_page.server_name().unwrap())
+ } else {
+ HomeserverOrServerName::Homeserver(homeserver_page.homeserver_url().unwrap())
+ };
let handle = spawn_tokio!(async move {
- Client::builder()
- .homeserver_url(homeserver_clone)
- .request_config(RequestConfig::new().disable_retry())
- .build()
- .await
+ CreatedClient::new(&homeserver, autodiscovery, None, None).await
});
- spawn!(
- glib::PRIORITY_DEFAULT_IDLE,
- clone!(@weak self as obj => async move {
- match handle.await.unwrap() {
- Ok(client) => {
- obj.set_homeserver(Some(homeserver));
- obj.check_login_types(client).await;
- }
- Err(error) => {
- warn!("Failed to check homeserver: {}", error);
- toast!(obj, error.to_user_facing());
- }
- };
- obj.unfreeze();
- })
- );
+ match handle.await.unwrap() {
+ Ok(created_client) => {
+ self.set_created_client(Some(created_client)).await;
+ self.check_login_types().await;
+ }
+ Err(error) => {
+ if autodiscovery {
+ warn!("Failed to discover homeserver: {}", error);
+ } else {
+ warn!("Failed to check homeserver: {}", error);
+ }
+ toast!(self, error.to_user_facing());
+
+ // Clean up the created client because it's bound to the homeserver.
+ self.prune_created_client(true);
+ }
+ };
}
- async fn check_login_types(&self, client: Client) {
+ async fn check_login_types(&self) {
+ let client = self.client().unwrap();
let handle = spawn_tokio!(async move { client.get_login_types().await });
let login_types = match handle.await.unwrap() {
@@ -384,13 +424,14 @@ impl Login {
.iter()
.any(|flow| matches!(flow, LoginType::Password(_)));
- self.imp().supports_password.replace(has_password);
+ let priv_ = self.imp();
+ priv_.supports_password.replace(has_password);
if has_password {
- self.imp().method_page.update_sso(sso);
+ priv_.method_page.update_sso(sso);
self.show_login_methods();
} else {
- self.login_with_sso(None);
+ self.login_with_sso(None).await;
}
}
@@ -407,53 +448,163 @@ impl Login {
self.set_visible_child("method");
}
- fn login_with_password(&self) {
+ async fn login_with_password(&self) {
let priv_ = self.imp();
- let homeserver = self.homeserver().unwrap();
let username = priv_.method_page.username();
let password = priv_.method_page.password();
- let autodiscovery = self.autodiscovery();
-
- self.freeze();
+ let CreatedClient {
+ client,
+ path,
+ passphrase,
+ } = self.created_client().unwrap();
- let session = Session::new();
- self.set_handler_for_prepared_session(&session);
+ let handle = spawn_tokio!(async move {
+ client
+ .login_username(&username, &password)
+ .initial_device_display_name("Fractal")
+ .send()
+ .await
+ });
- spawn!(
- glib::PRIORITY_DEFAULT_IDLE,
- clone!(@weak session => async move {
- session.login_with_password(homeserver, username, password, autodiscovery).await;
- })
- );
- priv_.current_session.replace(Some(session));
+ match handle.await.unwrap() {
+ Ok(response) => {
+ let session_info = StoredSession {
+ homeserver: self.homeserver().unwrap(),
+ user_id: response.user_id,
+ device_id: response.device_id,
+ path,
+ secret: Secret {
+ access_token: response.access_token,
+ passphrase,
+ },
+ };
+ self.create_session(session_info, true).await;
+ }
+ Err(error) => {
+ warn!("Failed to log in: {error}");
+ toast!(self, error.to_user_facing());
+ }
+ }
}
- fn login_with_sso(&self, idp_id: Option<String>) {
- let priv_ = imp::Login::from_instance(self);
- let homeserver = self.homeserver().unwrap();
+ async fn login_with_sso(&self, idp_id: Option<String>) {
+ let CreatedClient {
+ client,
+ path,
+ passphrase,
+ } = self.created_client().unwrap();
+
self.set_visible_child("sso");
+ let handle = spawn_tokio!(async move {
+ let mut login = client
+ .login_sso(|sso_url| async move {
+ let ctx = glib::MainContext::default();
+ ctx.spawn(async move {
+ gtk::show_uri(gtk::Window::NONE, &sso_url, gdk::CURRENT_TIME);
+ });
+ Ok(())
+ })
+ .initial_device_display_name("Fractal");
+
+ if let Some(idp_id) = idp_id.as_deref() {
+ login = login.identity_provider_id(idp_id);
+ }
+
+ login.send().await
+ });
+
+ match handle.await.unwrap() {
+ Ok(response) => {
+ let session_info = StoredSession {
+ homeserver: self.homeserver().unwrap(),
+ user_id: response.user_id,
+ device_id: response.device_id,
+ path,
+ secret: Secret {
+ access_token: response.access_token,
+ passphrase,
+ },
+ };
+ self.create_session(session_info, true).await;
+ }
+ Err(error) => {
+ warn!("Failed to log in: {error}");
+ toast!(self, error.to_user_facing());
+ self.go_previous();
+ }
+ }
+ }
+
+ pub async fn restore_previous_session(&self, session: StoredSession) {
+ let handle = spawn_tokio!(async move {
+ let created_client = CreatedClient::new(
+ &HomeserverOrServerName::Homeserver(session.homeserver.clone()),
+ false,
+ Some(session.path.clone()),
+ Some(session.secret.passphrase.clone()),
+ )
+ .await?;
+
+ created_client
+ .client
+ .restore_login(matrix_sdk::Session {
+ user_id: session.user_id.clone(),
+ device_id: session.device_id.clone(),
+ access_token: session.secret.access_token.clone(),
+ refresh_token: None,
+ })
+ .await
+ .map(|_| (created_client, session))
+ .map_err(ClientSetupError::from)
+ });
+
+ match handle.await.unwrap() {
+ Ok((created_client, session_info)) => {
+ self.set_created_client(Some(created_client)).await;
+ self.create_session(session_info, false).await;
+ }
+ Err(error) => {
+ warn!("Failed to restore previous login: {error}");
+ toast!(self, error.to_user_facing());
+ }
+ }
+ }
+
+ pub async fn create_session(&self, session_info: StoredSession, is_new: bool) {
+ let client = self.client().unwrap();
let session = Session::new();
- self.set_handler_for_prepared_session(&session);
- spawn!(
- glib::PRIORITY_DEFAULT_IDLE,
- clone!(@weak session, @weak self as s => async move {
- session.login_with_sso(homeserver, idp_id).await;
- s.set_visible_child("homeserver");
- })
- );
- priv_.current_session.replace(Some(session));
+
+ if is_new {
+ if let Err(error) = secret::store_session(&session_info).await {
+ error!("Couldn't store session: {:?}", error);
+
+ self.parent_window().switch_to_error_page(
+ &format!("{}\n\n{}", gettext("Unable to store session"), error),
+ error,
+ );
+ return;
+ }
+ };
+
+ session.prepare(client, session_info).await;
+ self.emit_by_name::<()>("new-session", &[&session]);
}
pub fn clean(&self) {
let priv_ = self.imp();
+
+ // Clean pages.
priv_.homeserver_page.clean();
priv_.method_page.clean();
+
+ // Clean data.
+ self.prune_created_client(false);
self.set_autodiscovery(true);
- priv_.homeserver.take();
+
+ // Reinitialize UI.
priv_.main_stack.set_visible_child_name("homeserver");
self.unfreeze();
- self.drop_session_reference();
}
fn freeze(&self) {
@@ -486,22 +637,6 @@ impl Login {
})
}
- fn drop_session_reference(&self) {
- let priv_ = self.imp();
-
- if let Some(session) = priv_.current_session.take() {
- if let Some(id) = priv_.prepared_source_id.take() {
- session.disconnect(id);
- }
- if let Some(id) = priv_.logged_out_source_id.take() {
- session.disconnect(id);
- }
- if let Some(id) = priv_.ready_source_id.take() {
- session.disconnect(id);
- }
- }
- }
-
pub fn default_widget(&self) -> gtk::Widget {
self.imp().next_button.get().upcast()
}
@@ -520,48 +655,6 @@ impl Login {
}
}
- fn set_handler_for_prepared_session(&self, session: &Session) {
- let priv_ = self.imp();
- priv_
- .prepared_source_id
- .replace(Some(session.connect_prepared(
- clone!(@weak self as login => move |session, error| {
- match error {
- Some(e) => {
- toast!(login, e);
- login.unfreeze();
- },
- None => {
- debug!("A new session was prepared");
- login.emit_by_name::<()>("new-session", &[&session]);
- }
- }
- }),
- )));
-
- priv_.ready_source_id.replace(Some(session.connect_ready(
- clone!(@weak self as login => move |_| {
- login.clean();
- }),
- )));
-
- priv_
- .logged_out_source_id
- .replace(Some(session.connect_logged_out(
- clone!(@weak self as login => move |_| {
- login.parent_window().switch_to_login_page();
- login.drop_session_reference();
- login.unfreeze();
- }),
- )));
- }
-
- fn parent_window(&self) -> crate::Window {
- self.root()
- .and_then(|root| root.downcast().ok())
- .expect("Login needs to have a parent window")
- }
-
fn update_network_state(&self) {
let priv_ = self.imp();
let monitor = gio::NetworkMonitor::default();
@@ -592,3 +685,104 @@ impl Default for Login {
Self::new()
}
}
+
+/// A homeserver URL or a server name.
+pub enum HomeserverOrServerName {
+ /// A homeserver URL.
+ Homeserver(Url),
+
+ /// A server name.
+ ServerName(OwnedServerName),
+}
+
+/// All errors that can occur when setting up the Matrix client.
+#[derive(Error, Debug)]
+pub enum ClientSetupError {
+ #[error(transparent)]
+ Store(#[from] OpenStoreError),
+ #[error(transparent)]
+ Client(#[from] ClientBuildError),
+ #[error(transparent)]
+ Sdk(#[from] matrix_sdk::Error),
+}
+
+impl UserFacingError for ClientSetupError {
+ fn to_user_facing(self) -> String {
+ match self {
+ ClientSetupError::Store(err) => err.to_user_facing(),
+ ClientSetupError::Client(err) => err.to_user_facing(),
+ ClientSetupError::Sdk(err) => err.to_user_facing(),
+ }
+ }
+}
+
+/// A newly created Matrix client and its configuration.
+#[derive(Debug, Clone)]
+pub struct CreatedClient {
+ /// The Matrix client.
+ pub client: Client,
+
+ /// The path where the store is located.
+ pub path: PathBuf,
+
+ /// The passphrase to decrypt the store.
+ pub passphrase: String,
+}
+
+impl CreatedClient {
+ /// Create a Matrix `Client` for the given homeserver.
+ async fn new(
+ homeserver: &HomeserverOrServerName,
+ use_discovery: bool,
+ path: Option<PathBuf>,
+ passphrase: Option<String>,
+ ) -> Result<CreatedClient, ClientSetupError> {
+ let path = path.unwrap_or_else(|| {
+ let mut path = glib::user_data_dir();
+ path.push(glib::uuid_string_random().as_str());
+ path
+ });
+
+ let passphrase = passphrase.unwrap_or_else(|| {
+ thread_rng()
+ .sample_iter(Alphanumeric)
+ .take(30)
+ .map(char::from)
+ .collect()
+ });
+
+ let state_store = SledStateStore::builder()
+ .path(path.clone())
+ .passphrase(passphrase.clone())
+ .migration_conflict_strategy(MigrationConflictStrategy::Drop)
+ .build()
+ .map_err(|err| OpenStoreError::from(StoreError::backend(err)))?;
+ let crypto_store = state_store.open_crypto_store()?;
+ let store_config = StoreConfig::new()
+ .state_store(state_store)
+ .crypto_store(crypto_store);
+
+ let builder = match homeserver {
+ HomeserverOrServerName::Homeserver(url) => Client::builder().homeserver_url(url),
+ HomeserverOrServerName::ServerName(server_name) => {
+ Client::builder().server_name(server_name)
+ }
+ };
+
+ let client = builder
+ .store_config(store_config)
+ // force_auth option to solve an issue with some servers configuration to require
+ // auth for profiles:
+ // https://gitlab.gnome.org/GNOME/fractal/-/issues/934
+ .request_config(RequestConfig::new().retry_limit(2).force_auth())
+ .respect_login_well_known(use_discovery)
+ .build()
+ .await?;
+
+ Ok(Self {
+ client,
+ path,
+ passphrase,
+ })
+ }
+}
diff --git a/src/session/mod.rs b/src/session/mod.rs
index 780469688..b37be46e8 100644
--- a/src/session/mod.rs
+++ b/src/session/mod.rs
@@ -11,7 +11,7 @@ mod sidebar;
mod user;
pub mod verification;
-use std::{collections::HashSet, fs, path::PathBuf, time::Duration};
+use std::{collections::HashSet, fs, time::Duration};
use adw::{prelude::*, subclass::prelude::*};
use futures::StreamExt;
@@ -23,7 +23,7 @@ use gtk::{
};
use log::{debug, error, warn};
use matrix_sdk::{
- config::{RequestConfig, StoreConfig, SyncSettings},
+ config::SyncSettings,
deserialized_responses::SyncResponse,
room::Room as MatrixRoom,
ruma::{
@@ -43,13 +43,9 @@ use matrix_sdk::{
matrix_uri::MatrixId,
MatrixUri, OwnedRoomOrAliasId, OwnedServerName, RoomId, RoomOrAliasId,
},
- store::{MigrationConflictStrategy, OpenStoreError, SledStateStore},
- Client, ClientBuildError, Error, HttpError, RumaApiError, StoreError,
+ Client, HttpError, RumaApiError,
};
-use rand::{distributions::Alphanumeric, thread_rng, Rng};
-use thiserror::Error;
use tokio::task::JoinHandle;
-use url::Url;
use self::{
account_settings::AccountSettings,
@@ -67,34 +63,13 @@ pub use self::{
user::{User, UserActions, UserExt},
};
use crate::{
- secret,
- secret::{Secret, StoredSession},
+ secret::{self, StoredSession},
session::sidebar::ItemList,
spawn, spawn_tokio, toast,
utils::{check_if_reachable, parse_matrix_to_uri},
- UserFacingError, Window,
+ Window,
};
-#[derive(Error, Debug)]
-pub enum ClientSetupError {
- #[error(transparent)]
- Store(#[from] OpenStoreError),
- #[error(transparent)]
- Client(#[from] ClientBuildError),
- #[error(transparent)]
- Sdk(#[from] Error),
-}
-
-impl UserFacingError for ClientSetupError {
- fn to_user_facing(self) -> String {
- match self {
- ClientSetupError::Store(err) => err.to_user_facing(),
- ClientSetupError::Client(err) => err.to_user_facing(),
- ClientSetupError::Sdk(err) => err.to_user_facing(),
- }
- }
-}
-
mod imp {
use std::cell::{Cell, RefCell};
@@ -116,7 +91,7 @@ mod imp {
pub content: TemplateChild<Content>,
#[template_child]
pub media_viewer: TemplateChild<MediaViewer>,
- pub client: RefCell<Option<Client>>,
+ pub client: OnceCell<Client>,
pub item_list: OnceCell<ItemList>,
pub user: OnceCell<User>,
pub is_ready: Cell<bool>,
@@ -252,12 +227,6 @@ mod imp {
fn signals() -> &'static [Signal] {
static SIGNALS: Lazy<Vec<Signal>> = Lazy::new(|| {
vec![
- Signal::builder(
- "prepared",
- &[Option::<String>::static_type().into()],
- <()>::static_type().into(),
- )
- .build(),
Signal::builder("ready", &[], <()>::static_type().into()).build(),
Signal::builder("logged-out", &[], <()>::static_type().into()).build(),
]
@@ -347,198 +316,33 @@ impl Session {
room_search.set_search_mode(!room_search.is_search_mode());
}
- pub async fn login_with_password(
- &self,
- homeserver: Url,
- username: String,
- password: String,
- use_discovery: bool,
- ) {
- self.imp().logout_on_dispose.set(true);
-
- let mut path = glib::user_data_dir();
- path.push(glib::uuid_string_random().as_str());
-
- let passphrase: String = {
- let mut rng = thread_rng();
- (&mut rng)
- .sample_iter(Alphanumeric)
- .take(30)
- .map(char::from)
- .collect()
- };
-
- let handle = spawn_tokio!(async move {
- let client =
- create_client(&homeserver, path.clone(), passphrase.clone(), use_discovery).await?;
-
- let response = client
- .login_username(&username, &password)
- .initial_device_display_name("Fractal")
- .send()
- .await;
- match response {
- Ok(response) => Ok((
- client,
- StoredSession {
- homeserver,
- path,
- user_id: response.user_id,
- device_id: response.device_id,
- secret: Secret {
- passphrase,
- access_token: response.access_token,
- },
- },
- )),
- Err(error) => {
- // Remove the store created by Client::new()
- fs::remove_dir_all(path).unwrap();
- Err(error.into())
- }
- }
- });
-
- self.handle_login_result(handle.await.unwrap(), true).await;
- }
-
- pub async fn login_with_sso(&self, homeserver: Url, idp_id: Option<String>) {
- self.imp().logout_on_dispose.set(true);
- let mut path = glib::user_data_dir();
- path.push(glib::uuid_string_random().as_str());
- let passphrase: String = {
- let mut rng = thread_rng();
- (&mut rng)
- .sample_iter(Alphanumeric)
- .take(30)
- .map(char::from)
- .collect()
- };
- let handle = spawn_tokio!(async move {
- let client = create_client(&homeserver, path.clone(), passphrase.clone(), true).await?;
-
- let mut login = client
- .login_sso(|sso_url| async move {
- let ctx = glib::MainContext::default();
- ctx.spawn(async move {
- gtk::show_uri(gtk::Window::NONE, &sso_url, gdk::CURRENT_TIME);
- });
- Ok(())
- })
- .initial_device_display_name("Fractal");
-
- if let Some(idp_id) = idp_id.as_deref() {
- login = login.identity_provider_id(idp_id);
- }
-
- let response = login.send().await;
- match response {
- Ok(response) => Ok((
- client,
- StoredSession {
- homeserver,
- path,
- user_id: response.user_id,
- device_id: response.device_id,
- secret: Secret {
- passphrase,
- access_token: response.access_token,
- },
- },
- )),
- Err(error) => {
- // Remove the store created by Client::new()
- fs::remove_dir_all(path).unwrap();
- Err(error.into())
- }
- }
- });
-
- self.handle_login_result(handle.await.unwrap(), true).await;
- }
-
- pub async fn login_with_previous_session(&self, session: StoredSession) {
- let handle = spawn_tokio!(async move {
- let client = create_client(
- &session.homeserver,
- session.path.clone(),
- session.secret.passphrase.clone(),
- false,
- )
- .await?;
-
- client
- .restore_login(matrix_sdk::Session {
- user_id: session.user_id.clone(),
- device_id: session.device_id.clone(),
- access_token: session.secret.access_token.clone(),
- refresh_token: None,
- })
- .await
- .map(|_| (client, session))
- .map_err(Into::into)
- });
-
- self.handle_login_result(handle.await.unwrap(), false).await;
- }
-
- async fn handle_login_result(
- &self,
- result: Result<(Client, StoredSession), ClientSetupError>,
- store_session: bool,
- ) {
+ pub async fn prepare(&self, client: Client, session: StoredSession) {
let priv_ = self.imp();
- let error = match result {
- Ok((client, session)) => {
- priv_.client.replace(Some(client));
- let user = User::new(self, &session.user_id);
- priv_.user.set(user).unwrap();
- self.notify("user");
-
- self.update_user_profile();
-
- if store_session {
- if let Err(error) = secret::store_session(&session).await {
- warn!("Couldn't store session: {:?}", error);
- if let Some(window) = self.parent_window() {
- window.switch_to_error_page(
- &format!("{}\n\n{}", gettext("Unable to store session"), error),
- error,
- );
- }
- self.logout(false).await;
- fs::remove_dir_all(session.path).unwrap();
- return;
- }
- };
- priv_
- .settings
- .set(SessionSettings::new(session.id()))
- .unwrap();
+ priv_.client.set(client).unwrap();
- priv_.info.set(session).unwrap();
- self.update_offline().await;
+ let user = User::new(self, &session.user_id);
+ priv_.user.set(user).unwrap();
+ self.notify("user");
- self.room_list().load();
- self.setup_direct_room_handler();
- self.setup_room_encrypted_changes();
+ self.update_user_profile();
- self.set_is_prepared(true);
- self.sync();
+ priv_
+ .settings
+ .set(SessionSettings::new(session.id()))
+ .unwrap();
- None
- }
- Err(error) => {
- error!("Failed to prepare the session: {:?}", error);
+ priv_.info.set(session).unwrap();
+ self.update_offline().await;
- priv_.logout_on_dispose.set(false);
+ self.room_list().load();
+ self.setup_direct_room_handler();
+ self.setup_room_encrypted_changes();
- Some(error.to_user_facing())
- }
- };
+ self.set_is_prepared(true);
+ self.sync();
- self.emit_by_name::<()>("prepared", &[&error]);
+ debug!("A new session was prepared");
}
fn sync(&self) {
@@ -703,9 +507,9 @@ impl Session {
pub fn client(&self) -> Client {
self.imp()
.client
- .borrow()
+ .get()
+ .expect("The session wasn't prepared")
.clone()
- .expect("The session isn't ready")
}
pub fn is_offline(&self) -> bool {
@@ -748,21 +552,6 @@ impl Session {
self.notify("offline");
}
- /// Connects the prepared signals to the function f given in input
- pub fn connect_prepared<F: Fn(&Self, Option<String>) + 'static>(
- &self,
- f: F,
- ) -> glib::SignalHandlerId {
- self.connect_local("prepared", true, move |values| {
- let obj = values[0].get::<Self>().unwrap();
- let err = values[1].get::<Option<String>>().unwrap();
-
- f(&obj, err);
-
- None
- })
- }
-
pub fn connect_logged_out<F: Fn(&Self) + 'static>(&self, f: F) -> glib::SignalHandlerId {
self.connect_local("logged-out", true, move |values| {
let obj = values[0].get::<Self>().unwrap();
@@ -1036,35 +825,6 @@ impl Default for Session {
}
}
-async fn create_client(
- homeserver: &Url,
- path: PathBuf,
- passphrase: String,
- use_discovery: bool,
-) -> Result<Client, ClientSetupError> {
- let state_store = SledStateStore::builder()
- .path(path)
- .passphrase(passphrase)
- .migration_conflict_strategy(MigrationConflictStrategy::Drop)
- .build()
- .map_err(|err| OpenStoreError::from(StoreError::backend(err)))?;
- let crypto_store = state_store.open_crypto_store()?;
- let store_config = StoreConfig::new()
- .state_store(state_store)
- .crypto_store(crypto_store);
- Client::builder()
- .homeserver_url(homeserver)
- .store_config(store_config)
- // force_auth option to solve an issue with some servers configuration to require
- // auth for profiles:
- // https://gitlab.gnome.org/GNOME/fractal/-/issues/934
- .request_config(RequestConfig::new().retry_limit(2).force_auth())
- .respect_login_well_known(use_discovery)
- .build()
- .await
- .map_err(Into::into)
-}
-
fn parse_room(room: &str) -> Option<(OwnedRoomOrAliasId, Vec<OwnedServerName>)> {
MatrixUri::parse(room)
.ok()
diff --git a/src/window.rs b/src/window.rs
index 0388aac44..62485ae34 100644
--- a/src/window.rs
+++ b/src/window.rs
@@ -126,6 +126,18 @@ mod imp {
}));
obj.add_action(&fullscreen);
+ self.login
+ .connect_new_session(clone!(@weak obj => move |_login, session| {
+ obj.add_session(&session);
+ obj.switch_to_loading_page();
+ }));
+
+ self.main_stack.connect_visible_child_notify(
+ clone!(@weak obj => move |_| obj.set_default_by_child()),
+ );
+
+ obj.set_default_by_child();
+
spawn!(clone!(@weak obj => async move {
obj.restore_sessions().await;
}));
@@ -176,6 +188,9 @@ impl Window {
// We need to grab the focus so that keyboard shortcuts work
session.grab_focus();
+ session.connect_ready(clone!(@weak self as obj => move |_| {
+ obj.imp().login.clean();
+ }));
session.connect_logged_out(clone!(@weak self as obj => move |session| {
obj.remove_session(session)
}));
@@ -213,30 +228,14 @@ impl Window {
info!("Database path: {path}");
}
- let session = Session::new();
spawn!(
glib::PRIORITY_DEFAULT_IDLE,
- clone!(@weak session => async move {
- session.login_with_previous_session(stored_session).await;
+ clone!(@weak self as obj => async move {
+ obj.imp().login.restore_previous_session(stored_session).await;
})
);
- self.add_session(&session);
}
}
-
- let priv_ = self.imp();
- priv_.login.connect_new_session(
- clone!(@weak self as obj => move |_login, session| {
- obj.add_session(&session);
- obj.switch_to_loading_page();
- }),
- );
-
- priv_.main_stack.connect_visible_child_notify(
- clone!(@weak self as obj => move |_| obj.set_default_by_child()),
- );
-
- self.set_default_by_child();
}
Err(error) => {
warn!("Failed to restore previous sessions: {:?}", error);
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]