Improve login ux and session handling
This commit is contained in:
parent
a9c1d6ed01
commit
80f4ab6451
|
@ -1,6 +1,5 @@
|
||||||
use matrix_sdk::{
|
use matrix_sdk::{
|
||||||
identifiers::DeviceId, identifiers::UserId, reqwest::Url, Client, ClientConfig, Session,
|
identifiers::DeviceId, identifiers::UserId, reqwest::Url, Client, ClientConfig, SyncSettings,
|
||||||
SyncSettings,
|
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
@ -8,23 +7,14 @@ pub type Error = anyhow::Error;
|
||||||
|
|
||||||
// Needed to be able to serialize `Session`s. Should be done with serde remote.
|
// Needed to be able to serialize `Session`s. Should be done with serde remote.
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||||
pub struct SessionWrapper {
|
pub struct Session {
|
||||||
access_token: String,
|
access_token: String,
|
||||||
user_id: UserId,
|
user_id: UserId,
|
||||||
device_id: Box<DeviceId>,
|
device_id: Box<DeviceId>,
|
||||||
|
homeserver: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<SessionWrapper> for Session {
|
impl From<Session> for matrix_sdk::Session {
|
||||||
fn from(s: SessionWrapper) -> Self {
|
|
||||||
Self {
|
|
||||||
access_token: s.access_token,
|
|
||||||
user_id: s.user_id,
|
|
||||||
device_id: s.device_id,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Session> for SessionWrapper {
|
|
||||||
fn from(s: Session) -> Self {
|
fn from(s: Session) -> Self {
|
||||||
Self {
|
Self {
|
||||||
access_token: s.access_token,
|
access_token: s.access_token,
|
||||||
|
@ -34,38 +24,51 @@ impl From<Session> for SessionWrapper {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Login with credentials, creating a new authentication session
|
||||||
pub async fn login(
|
pub async fn login(
|
||||||
username: &str,
|
username: &str,
|
||||||
password: &str,
|
password: &str,
|
||||||
server: &str,
|
server: &str,
|
||||||
) -> Result<(Client, Session), Error> {
|
) -> Result<(Client, Session), Error> {
|
||||||
let url = Url::parse(server)?;
|
let url = Url::parse(server)?;
|
||||||
let config = ClientConfig::new().store_path(&dirs::config_dir().unwrap().join("retrix"));
|
let client = client(url)?;
|
||||||
let client = Client::new_with_config(url, config)?;
|
|
||||||
|
|
||||||
let session = match get_session()? {
|
let response = client
|
||||||
Some(session) => {
|
.login(
|
||||||
client.restore_login(session.clone()).await?;
|
username,
|
||||||
session
|
password,
|
||||||
}
|
None,
|
||||||
None => {
|
Some(&format!("retrix@{}", hostname::get()?.to_string_lossy())),
|
||||||
let response = client
|
)
|
||||||
.login(username, password, None, Some("retrix"))
|
.await?;
|
||||||
.await?;
|
let session = Session {
|
||||||
let session = Session {
|
access_token: response.access_token,
|
||||||
access_token: response.access_token,
|
user_id: response.user_id,
|
||||||
user_id: response.user_id,
|
device_id: response.device_id,
|
||||||
device_id: response.device_id,
|
homeserver: server.to_owned(),
|
||||||
};
|
|
||||||
write_session(session.clone())?;
|
|
||||||
session
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
write_session(&session)?;
|
||||||
client.sync_once(SyncSettings::new()).await?;
|
client.sync_once(SyncSettings::new()).await?;
|
||||||
|
|
||||||
Ok((client, session))
|
Ok((client, session))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn restore_login(session: Session) -> Result<(Client, Session), Error> {
|
||||||
|
let url = Url::parse(&session.homeserver)?;
|
||||||
|
let client = client(url)?;
|
||||||
|
|
||||||
|
client.restore_login(session.clone().into()).await?;
|
||||||
|
client.sync_once(SyncSettings::new()).await?;
|
||||||
|
|
||||||
|
Ok((client, session))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a matrix client handler with the desired configuration
|
||||||
|
fn client(url: Url) -> Result<Client, matrix_sdk::Error> {
|
||||||
|
let config = ClientConfig::new().store_path(&dirs::config_dir().unwrap().join("retrix"));
|
||||||
|
Client::new_with_config(url, config)
|
||||||
|
}
|
||||||
|
|
||||||
/// File path to store session data in
|
/// File path to store session data in
|
||||||
fn session_path() -> std::path::PathBuf {
|
fn session_path() -> std::path::PathBuf {
|
||||||
dirs::config_dir()
|
dirs::config_dir()
|
||||||
|
@ -75,22 +78,19 @@ fn session_path() -> std::path::PathBuf {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Read session data from config file
|
/// Read session data from config file
|
||||||
fn get_session() -> Result<Option<Session>, Error> {
|
pub fn get_session() -> Result<Option<Session>, Error> {
|
||||||
let path = session_path();
|
let path = session_path();
|
||||||
if !path.is_file() {
|
if !path.is_file() {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
let session: SessionWrapper = toml::from_slice(&std::fs::read(path)?)?;
|
let session: Session = toml::from_slice(&std::fs::read(path)?)?;
|
||||||
Ok(Some(session.into()))
|
Ok(Some(session))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Save session data to config file
|
/// Save session data to config file
|
||||||
fn write_session(session: Session) -> Result<(), Error> {
|
fn write_session(session: &Session) -> Result<(), Error> {
|
||||||
let session: SessionWrapper = session.into();
|
|
||||||
let path = session_path();
|
|
||||||
|
|
||||||
let serialized = toml::to_string(&session)?;
|
let serialized = toml::to_string(&session)?;
|
||||||
std::fs::write(path, serialized)?;
|
std::fs::write(session_path(), serialized)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
114
src/ui.rs
114
src/ui.rs
|
@ -1,6 +1,6 @@
|
||||||
use iced::{
|
use iced::{
|
||||||
text_input::{self, TextInput},
|
text_input::{self, TextInput},
|
||||||
Application, Button, Column, Command, Container, Element, Length, Row, Text,
|
Application, Button, Column, Command, Container, Element, Length, Scrollable, Text,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::matrix;
|
use crate::matrix;
|
||||||
|
@ -18,35 +18,19 @@ pub enum Retrix {
|
||||||
server: String,
|
server: String,
|
||||||
error: Option<String>,
|
error: Option<String>,
|
||||||
},
|
},
|
||||||
|
AwaitLogin,
|
||||||
LoggedIn {
|
LoggedIn {
|
||||||
client: matrix_sdk::Client,
|
client: matrix_sdk::Client,
|
||||||
session: matrix_sdk::Session,
|
session: matrix::Session,
|
||||||
|
|
||||||
rooms: Vec<matrix_sdk::Room>,
|
rooms: Vec<String>,
|
||||||
|
room_scroll: iced::scrollable::State,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
impl Retrix {
|
||||||
pub enum Message {
|
pub fn new_prompt() -> Retrix {
|
||||||
// Login form messages
|
Retrix::Prompt {
|
||||||
SetUser(String),
|
|
||||||
SetPassword(String),
|
|
||||||
SetServer(String),
|
|
||||||
Login,
|
|
||||||
LoggedIn(matrix_sdk::Client, matrix_sdk::Session),
|
|
||||||
SetError(String),
|
|
||||||
|
|
||||||
// Main state messages
|
|
||||||
ResetRooms(Vec<matrix_sdk::Room>),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Application for Retrix {
|
|
||||||
type Message = Message;
|
|
||||||
type Executor = iced::executor::Default;
|
|
||||||
type Flags = ();
|
|
||||||
|
|
||||||
fn new(_flags: ()) -> (Self, Command<Self::Message>) {
|
|
||||||
let app = Retrix::Prompt {
|
|
||||||
user_input: text_input::State::new(),
|
user_input: text_input::State::new(),
|
||||||
password_input: text_input::State::new(),
|
password_input: text_input::State::new(),
|
||||||
server_input: text_input::State::new(),
|
server_input: text_input::State::new(),
|
||||||
|
@ -56,8 +40,44 @@ impl Application for Retrix {
|
||||||
password: String::new(),
|
password: String::new(),
|
||||||
server: String::new(),
|
server: String::new(),
|
||||||
error: None,
|
error: None,
|
||||||
};
|
}
|
||||||
(app, Command::none())
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum Message {
|
||||||
|
// Login form messages
|
||||||
|
SetUser(String),
|
||||||
|
SetPassword(String),
|
||||||
|
SetServer(String),
|
||||||
|
Login,
|
||||||
|
LoggedIn(matrix_sdk::Client, matrix::Session),
|
||||||
|
LoginFailed(String),
|
||||||
|
|
||||||
|
// Main state messages
|
||||||
|
ResetRooms(Vec<String>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Application for Retrix {
|
||||||
|
type Message = Message;
|
||||||
|
type Executor = iced::executor::Default;
|
||||||
|
type Flags = ();
|
||||||
|
|
||||||
|
fn new(_flags: ()) -> (Self, Command<Self::Message>) {
|
||||||
|
// Skip login prompt if we have a session saved
|
||||||
|
match matrix::get_session().ok().flatten() {
|
||||||
|
Some(session) => {
|
||||||
|
let command = Command::perform(
|
||||||
|
async move { matrix::restore_login(session).await },
|
||||||
|
|result| match result {
|
||||||
|
Ok((s, c)) => Message::LoggedIn(s, c),
|
||||||
|
Err(e) => Message::LoginFailed(e.to_string()),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
(Retrix::AwaitLogin, command)
|
||||||
|
}
|
||||||
|
None => (Retrix::new_prompt(), Command::none()),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn title(&self) -> String {
|
fn title(&self) -> String {
|
||||||
|
@ -70,38 +90,47 @@ impl Application for Retrix {
|
||||||
ref mut user,
|
ref mut user,
|
||||||
ref mut password,
|
ref mut password,
|
||||||
ref mut server,
|
ref mut server,
|
||||||
ref mut error,
|
|
||||||
..
|
..
|
||||||
} => match message {
|
} => match message {
|
||||||
Message::SetUser(u) => *user = u,
|
Message::SetUser(u) => *user = u,
|
||||||
Message::SetPassword(p) => *password = p,
|
Message::SetPassword(p) => *password = p,
|
||||||
Message::SetServer(s) => *server = s,
|
Message::SetServer(s) => *server = s,
|
||||||
Message::SetError(e) => *error = Some(e),
|
|
||||||
Message::Login => {
|
Message::Login => {
|
||||||
let user = user.clone();
|
let user = user.clone();
|
||||||
let password = password.clone();
|
let password = password.clone();
|
||||||
let server = server.clone();
|
let server = server.clone();
|
||||||
|
*self = Retrix::AwaitLogin;
|
||||||
return Command::perform(
|
return Command::perform(
|
||||||
async move { matrix::login(&user, &password, &server).await },
|
async move { matrix::login(&user, &password, &server).await },
|
||||||
|result| match result {
|
|result| match result {
|
||||||
Ok((c, r)) => Message::LoggedIn(c, r),
|
Ok((c, r)) => Message::LoggedIn(c, r),
|
||||||
Err(e) => Message::SetError(e.to_string()),
|
Err(e) => Message::LoginFailed(e.to_string()),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
_ => (),
|
||||||
|
},
|
||||||
|
Retrix::AwaitLogin => match message {
|
||||||
|
Message::LoginFailed(e) => {
|
||||||
|
*self = Retrix::new_prompt();
|
||||||
|
if let Retrix::Prompt { ref mut error, .. } = *self {
|
||||||
|
*error = Some(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
Message::LoggedIn(client, session) => {
|
Message::LoggedIn(client, session) => {
|
||||||
*self = Retrix::LoggedIn {
|
*self = Retrix::LoggedIn {
|
||||||
client: client.clone(),
|
client: client.clone(),
|
||||||
session,
|
session,
|
||||||
rooms: Vec::new(),
|
rooms: Vec::new(),
|
||||||
|
room_scroll: Default::default(),
|
||||||
};
|
};
|
||||||
let client = client.clone();
|
let client = client.clone();
|
||||||
Command::perform(
|
return Command::perform(
|
||||||
async move {
|
async move {
|
||||||
let mut list = Vec::new();
|
let mut list = Vec::new();
|
||||||
for (id, room) in client.joined_rooms().read().await.iter() {
|
for (_, room) in client.joined_rooms().read().await.iter() {
|
||||||
let room = room.read().await;
|
let name = room.read().await.display_name();
|
||||||
list.push(room.clone());
|
list.push(name);
|
||||||
}
|
}
|
||||||
list
|
list
|
||||||
},
|
},
|
||||||
|
@ -165,11 +194,24 @@ impl Application for Retrix {
|
||||||
.height(iced::Length::Fill)
|
.height(iced::Length::Fill)
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
Retrix::LoggedIn { ref rooms, .. } => {
|
Retrix::AwaitLogin => Container::new(Text::new("Logging in..."))
|
||||||
|
.center_x()
|
||||||
|
.center_y()
|
||||||
|
.width(Length::Fill)
|
||||||
|
.height(Length::Fill)
|
||||||
|
.into(),
|
||||||
|
Retrix::LoggedIn {
|
||||||
|
ref rooms,
|
||||||
|
ref mut room_scroll,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
//let mut root_row = Row::new().width(Length::Fill).height(Length::Fill);
|
//let mut root_row = Row::new().width(Length::Fill).height(Length::Fill);
|
||||||
let mut room_col = Column::new().width(400.into()).height(Length::Fill);
|
let mut room_col = Scrollable::new(room_scroll)
|
||||||
|
.width(400.into())
|
||||||
|
.height(Length::Fill)
|
||||||
|
.spacing(15);
|
||||||
for room in rooms {
|
for room in rooms {
|
||||||
room_col = room_col.push(Text::new(room.display_name()));
|
room_col = room_col.push(Text::new(room));
|
||||||
}
|
}
|
||||||
room_col.into()
|
room_col.into()
|
||||||
//root_row = root_row.push(room_col);
|
//root_row = root_row.push(room_col);
|
||||||
|
|
Loading…
Reference in a new issue