commit f9049009d7ad736ac234ec5ea6425b12fccb911c
parent 20c615e0afcb9aff2630d330e6034d7fdc668183
Author: Marcel <mtrnord1@gmail.com>
Date: Sat, 6 Jun 2020 22:19:59 +0200
Display error on missing field, Handle timeouts and do 10 tries before failing (with 5 seconds timeout in between each try)
Took 1 hour 3 minutes
Diffstat:
5 files changed, 342 insertions(+), 129 deletions(-)
diff --git a/src/app/matrix/mod.rs b/src/app/matrix/mod.rs
@@ -3,7 +3,10 @@ use matrix_sdk::{
api::r0::{filter::RoomEventFilter, message::get_message_events::Direction},
events::{
collections::all::RoomEvent,
- room::message::{MessageEvent, MessageEventContent, TextMessageEventContent, FormattedBody, MessageFormat},
+ room::message::{
+ FormattedBody, MessageEvent, MessageEventContent, MessageFormat,
+ TextMessageEventContent,
+ },
EventJson,
},
identifiers::RoomId,
@@ -23,7 +26,7 @@ use yew::worker::*;
use crate::app::matrix::types::{get_media_download_url, get_video_media_download_url};
use crate::constants::AUTH_KEY;
-use crate::errors::MatrixError;
+use crate::errors::{Field, MatrixError};
use pulldown_cmark::{html, Options, Parser};
mod sync;
@@ -60,7 +63,7 @@ pub enum Request {
SetHomeserver(String),
SetUsername(String),
SetPassword(String),
- Login(),
+ Login,
GetLoggedIn,
GetUserdata,
GetOldMessages((RoomId, Option<String>)),
@@ -140,7 +143,8 @@ impl Agent for MatrixAgent {
Request::SetPassword(password) => {
self.matrix_state.password = Some(password);
}
- Request::Login() => {
+ Request::Login => {
+ info!("Starting Login");
let login_client = self.login();
if login_client.is_none() {
for sub in self.subscribers.iter() {
@@ -169,23 +173,62 @@ impl Agent for MatrixAgent {
client.restore_login(session).await;
} else {
// FIXME gracefully handle login errors
- let login_response: matrix_sdk::api::r0::session::login::Response = client
+ let login_response = client
.login(username, password, None, Some("Daydream".to_string()))
- .await
- .unwrap();
- let session_store = SessionStore {
- access_token: login_response.access_token,
- user_id: login_response.user_id.to_string(),
- device_id: login_response.device_id,
- homeserver_url: client.homeserver().to_string(),
- };
- let mut storage = agent.storage.lock().unwrap();
- storage.store(AUTH_KEY, Json(&session_store));
- }
-
- for sub in subscribers.iter() {
- let resp = Response::LoggedIn(true);
- agent.link.respond(*sub, resp);
+ .await;
+ match login_response {
+ Ok(login_response) => {
+ let session_store = SessionStore {
+ access_token: login_response.access_token,
+ user_id: login_response.user_id.to_string(),
+ device_id: login_response.device_id,
+ homeserver_url: client.homeserver().to_string(),
+ };
+ let mut storage = agent.storage.lock().unwrap();
+ storage.store(AUTH_KEY, Json(&session_store));
+
+ for sub in subscribers.iter() {
+ let resp = Response::LoggedIn(true);
+ agent.link.respond(*sub, resp);
+ }
+ }
+ Err(e) => {
+ if let matrix_sdk::Error::Reqwest(e) = e {
+ match e.status() {
+ None => {
+ for sub in subscribers.iter() {
+ let resp = Response::Error(MatrixError::SDKError(
+ e.to_string(),
+ ));
+ agent.link.respond(*sub, resp);
+ }
+ }
+ Some(v) => {
+ if v.is_server_error() {
+ for sub in subscribers.iter() {
+ let resp =
+ Response::Error(MatrixError::LoginTimeout);
+ agent.link.respond(*sub, resp);
+ }
+ } else {
+ for sub in subscribers.iter() {
+ let resp = Response::Error(
+ MatrixError::SDKError(e.to_string()),
+ );
+ agent.link.respond(*sub, resp);
+ }
+ }
+ }
+ }
+ } else {
+ for sub in subscribers.iter() {
+ let resp =
+ Response::Error(MatrixError::SDKError(e.to_string()));
+ agent.link.respond(*sub, resp);
+ }
+ }
+ }
+ }
}
});
}
@@ -292,7 +335,9 @@ impl Agent for MatrixAgent {
for event in deserialized_events.into_iter().rev() {
if let RoomEvent::RoomMessage(mut event) = event {
- if let MessageEventContent::Image(mut image_event) = event.clone().content {
+ if let MessageEventContent::Image(mut image_event) =
+ event.clone().content
+ {
if image_event.url.is_some() {
let new_url = Some(get_media_download_url(
agent.matrix_client.clone().unwrap(),
@@ -381,10 +426,10 @@ impl Agent for MatrixAgent {
MessageEventContent::Text(TextMessageEventContent {
body: message,
relates_to: None,
- formatted: Some(FormattedBody{
+ formatted: Some(FormattedBody {
body: formatted_message,
format: MessageFormat::Html,
- })
+ }),
})
};
client.room_send(&room_id, content, None).await;
@@ -419,18 +464,9 @@ impl MatrixAgent {
}
fn login(&mut self) -> Option<Client> {
- if (self.matrix_state.homeserver.is_none()
- || self.matrix_state.username.is_none()
- || self.matrix_state.password.is_none()
- || self.matrix_client.is_some())
- && self.session.is_none()
- {
- for sub in self.subscribers.iter() {
- let resp = Response::Error(MatrixError::MissingFields);
- self.link.respond(*sub, resp);
- }
- None
- } else if self.session.is_some() {
+ info!("preparing client");
+ if self.session.is_some() {
+ info!("restoring login");
let homeserver = self.session.clone().unwrap().homeserver_url;
let client_config = ClientConfig::new();
@@ -452,7 +488,26 @@ impl MatrixAgent {
});
Some(client)
+ } else if self.matrix_state.homeserver.is_none() {
+ for sub in self.subscribers.iter() {
+ let resp = Response::Error(MatrixError::MissingFields(Field::Homeserver));
+ self.link.respond(*sub, resp);
+ }
+ None
+ } else if self.matrix_state.username.is_none() {
+ for sub in self.subscribers.iter() {
+ let resp = Response::Error(MatrixError::MissingFields(Field::MXID));
+ self.link.respond(*sub, resp);
+ }
+ None
+ } else if self.matrix_state.password.is_none() {
+ for sub in self.subscribers.iter() {
+ let resp = Response::Error(MatrixError::MissingFields(Field::Password));
+ self.link.respond(*sub, resp);
+ }
+ None
} else {
+ info!("new login");
let homeserver = self.matrix_state.homeserver.clone().unwrap();
let client_config = ClientConfig::new();
diff --git a/src/app/views/login.rs b/src/app/views/login.rs
@@ -1,37 +1,58 @@
-use yew::agent::{Dispatched, Dispatcher};
use yew::prelude::*;
+use serde::{Deserialize, Serialize};
+use log::*;
use tr::tr;
-use crate::app::matrix::{MatrixAgent, Request};
+use crate::app::matrix::{MatrixAgent, Request, Response};
+use crate::errors::{MatrixError, Field};
+use wasm_bindgen::__rt::std::thread::sleep;
+use wasm_bindgen::__rt::core::time::Duration;
pub struct Login {
link: ComponentLink<Self>,
- homeserver: String,
- username: String,
- password: String,
- matrix_agent: Dispatcher<MatrixAgent>,
+ state: State,
+ matrix_agent: Box<dyn Bridge<MatrixAgent>>,
}
pub enum Msg {
+ NewMessage(Response),
SetHomeserver(String),
SetUsername(String),
SetPassword(String),
Login,
}
+#[derive(Serialize, Deserialize, Default)]
+pub struct State {
+ loading: bool,
+ homeserver: String,
+ username: String,
+ password: String,
+ error: Option<String>,
+ error_field: Option<Field>,
+ retries: u64
+}
+
impl Component for Login {
type Message = Msg;
type Properties = ();
fn create(_: Self::Properties, link: ComponentLink<Self>) -> Self {
- let matrix_agent = MatrixAgent::dispatcher();
- Login {
- link,
- //TODO use state
+ let matrix_callback = link.callback(Msg::NewMessage);
+ let matrix_agent = MatrixAgent::bridge(matrix_callback);
+ let state = State {
+ loading: false,
homeserver: "".to_string(),
username: "".to_string(),
password: "".to_string(),
+ error: None,
+ error_field: None,
+ retries: 0
+ };
+ Login {
+ link,
+ state,
matrix_agent,
}
}
@@ -39,23 +60,66 @@ impl Component for Login {
fn update(&mut self, msg: Self::Message) -> ShouldRender {
match msg {
Msg::SetHomeserver(homeserver) => {
- self.homeserver = homeserver.clone();
+ self.state.homeserver = homeserver.clone();
self.matrix_agent.send(Request::SetHomeserver(homeserver));
true
}
Msg::SetUsername(username) => {
- self.username = username.clone();
+ self.state.username = username.clone();
self.matrix_agent.send(Request::SetUsername(username));
true
}
Msg::SetPassword(password) => {
- self.password = password.clone();
+ self.state.password = password.clone();
self.matrix_agent.send(Request::SetPassword(password));
true
}
Msg::Login => {
- self.matrix_agent.send(Request::Login());
- false
+ // Reset Errors
+ self.state.error = None;
+ self.state.error_field = None;
+
+ // Start loading
+ self.matrix_agent.send(Request::Login);
+ self.state.loading = true;
+ true
+ }
+ Msg::NewMessage(response) => {
+ match response {
+ Response::Error(error) => {
+ match error.clone() {
+ MatrixError::MissingFields(field) => {
+ self.state.loading = false;
+ self.state.error = Some(error.to_string());
+ self.state.error_field = Some(field);
+ true
+ },
+ MatrixError::LoginTimeout => {
+ // If we had less than 10 tries try again
+ if self.state.retries < 10 {
+ self.state.retries += 1;
+ info!("Trying login again in 5 seconds");
+ sleep(Duration::from_secs(5));
+ self.link.send_message(Msg::Login);
+ false
+ } else {
+ self.state.loading = false;
+ self.state.error = Some("Login failed after 10 tries.".to_string());
+ true
+ }
+ },
+ MatrixError::SDKError(e) => {
+ // TODO handle login error != timeout better
+ error!("SDK Error: {}", e);
+ false
+ }
+ _ => {
+ false
+ }
+ }
+ }
+ _ => false
+ }
}
}
}
@@ -64,105 +128,165 @@ impl Component for Login {
false
}
+ //noinspection RsTypeCheck
fn view(&self) -> Html {
- html! {
- <div class="container">
- <div class="uk-position-center uk-padding">
- <h1 class="title">{"Login"}</h1>
- <form class="uk-form-stacked uk-margin" onsubmit=self.link.callback(|e: FocusEvent| {e.prevent_default(); Msg::Login})>
- <div class="uk-margin">
- <label class="uk-form-label">
- {
- tr!(
- // The URL Field of the Login page
- "Homeserver URL"
- )
+ let mut homeserver_classes = "uk-input";
+ let mut mxid_classes = "uk-input";
+ let mut password_classes = "uk-input";
+ match self.state.error_field.as_ref() {
+ Some(v) => {
+ match v {
+ Field::Homeserver => {
+ homeserver_classes = "uk-input uk-form-danger"
+ },
+ Field::MXID => {
+ mxid_classes = "uk-input uk-form-danger"
+ },
+ Field::Password => {
+ password_classes = "uk-input uk-form-danger"
+ },
+ }
+ },
+ _ => {}
+ }
+
+ if self.state.loading {
+ html! {
+ <div class="container">
+ <div class="uk-position-center uk-padding">
+ <span uk-spinner="ratio: 4.5"></span>
+ </div>
+ </div>
+ }
+ } else {
+ html! {
+ <div class="container">
+ <div class="uk-position-center uk-padding">
+ <h1 class="title">
+ {
+ tr!(
+ // The Login Button of the Login page
+ "Login"
+ )
+ }
+ </h1>
+ {
+ match &self.state.error {
+ Some(v) => {
+ html! {
+ <h3 class="error">
+ {
+ tr!(
+ // {0} is the Error that happened on login
+ // The error message of the Login page
+ "Error: {0}",
+ v
+ )
+ }
+ </h3>
+ }
+ }
+ None => {
+ html!{}
}
- </label>
- <div class="uk-form-controls">
- <input
- class="uk-input"
- type="url"
- id="homeserver"
- placeholder=
+ }
+ }
+
+ <form class="uk-form-stacked uk-margin" onsubmit=self.link.callback(|e: FocusEvent| {e.prevent_default(); Msg::Login})>
+ <div class="uk-margin">
+ <label class="uk-form-label">
{
tr!(
// The URL Field of the Login page
"Homeserver URL"
)
}
- value=&self.homeserver
- oninput=self.link.callback(|e: InputData| Msg::SetHomeserver(e.value))
- />
+ </label>
+ <div class="uk-form-controls">
+ <input
+ class=homeserver_classes
+ type="url"
+ id="homeserver"
+ placeholder=
+ {
+ tr!(
+ // The URL Field of the Login page
+ "Homeserver URL"
+ )
+ }
+ value=&self.state.homeserver
+ oninput=self.link.callback(|e: InputData| Msg::SetHomeserver(e.value))
+ />
+ </div>
</div>
- </div>
- <div class="uk-margin">
- <label class="uk-form-label">
- {
- tr!(
- // The Matrix ID Field of the Login page
- "MXID"
- )
- }
- </label>
- <div class="uk-form-controls">
- <input
- class="uk-input"
- id="username"
- placeholder=
+ <div class="uk-margin">
+ <label class="uk-form-label">
{
tr!(
// The Matrix ID Field of the Login page
"MXID"
)
}
- value=&self.username
- oninput=self.link.callback(|e: InputData| Msg::SetUsername(e.value))
- />
+ </label>
+ <div class="uk-form-controls">
+ <input
+ class=mxid_classes
+ id="username"
+ placeholder=
+ {
+ tr!(
+ // The Matrix ID Field of the Login page
+ "MXID"
+ )
+ }
+ value=&self.state.username
+ oninput=self.link.callback(|e: InputData| Msg::SetUsername(e.value))
+ />
+ </div>
</div>
- </div>
- <div class="uk-margin">
- <label class="uk-form-label">
- {
- tr!(
- // The Password Field of the Login page
- "Password"
- )
- }
- </label>
- <div class="uk-form-controls">
- <input
- class="uk-input"
- type="password"
- id="password"
- placeholder=
+ <div class="uk-margin">
+ <label class="uk-form-label">
{
tr!(
// The Password Field of the Login page
"Password"
)
}
- value=&self.password
- oninput=self.link.callback(|e: InputData| Msg::SetPassword(e.value))
- />
+ </label>
+ <div class="uk-form-controls">
+ <input
+ class=password_classes
+ type="password"
+ id="password"
+ placeholder=
+ {
+ tr!(
+ // The Password Field of the Login page
+ "Password"
+ )
+ }
+ value=&self.state.password
+ oninput=self.link.callback(|e: InputData| Msg::SetPassword(e.value))
+ />
+ </div>
</div>
- </div>
- <div class="uk-margin">
- <div class="uk-form-controls">
- <button class="uk-button uk-button-primary">
- {
- tr!(
- // The Login Button of the Login page
- "Login"
- )
- }
- </button>
+ <div class="uk-margin">
+ <div class="uk-form-controls">
+ <button class="uk-button uk-button-primary">
+ {
+ tr!(
+ // The Login Button of the Login page
+ "Login"
+ )
+ }
+ </button>
+ </div>
</div>
- </div>
- </form>
+ </form>
+ </div>
</div>
- </div>
+ }
}
}
}
diff --git a/src/app/views/main_view.rs b/src/app/views/main_view.rs
@@ -1,7 +1,8 @@
+use log::*;
use matrix_sdk::Room;
use serde::{Deserialize, Serialize};
-use yew::prelude::*;
use yew::ComponentLink;
+use yew::prelude::*;
use crate::app::components::{event_list::EventList, room_list::RoomList};
use crate::app::matrix::{MatrixAgent, Request};
@@ -39,6 +40,7 @@ impl Component for MainView {
fn update(&mut self, msg: Self::Message) -> bool {
match msg {
Msg::ChangeRoom(room) => {
+ info!("Changing room to: {}", room.room_id);
self.state.current_room = Some(room);
}
}
@@ -82,7 +84,6 @@ impl Component for MainView {
</div>
}
}
-
}
}
}
diff --git a/src/errors.rs b/src/errors.rs
@@ -1,10 +1,39 @@
-use serde::{Deserialize, Serialize};
use thiserror::Error;
+use std::fmt;
+use serde::{Serialize, Deserialize};
-#[derive(Error, Debug, Copy, Clone, Serialize, Deserialize)]
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub enum Field {
+ Homeserver,
+ MXID,
+ Password,
+}
+
+impl fmt::Display for Field {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match *self {
+ Field::Homeserver => write!(f, "Homeserver Field"),
+ Field::MXID => write!(f, "MXID Field"),
+ Field::Password => write!(f, "Password Field"),
+ }
+ }
+}
+
+// TODO figure out a way to translate this
+#[derive(Error, Debug, Clone)]
pub enum MatrixError {
#[error("No Matrix Client is available yet")]
MissingClient,
- #[error("Missing required Data")]
- MissingFields,
+ #[error("Missing required Data in {0}")]
+ MissingFields(Field),
+
+ /// An error occurred in the Matrix client library.
+ /// This can't use transparent as we need Clone
+ #[error("A Timeout happened on Login")]
+ LoginTimeout,
+
+ /// An error occurred in the Matrix client library.
+ /// This can't use transparent as we need Clone
+ #[error("An error occurred in the Matrix client library: `{0}`")]
+ SDKError(String),
}
diff --git a/static/custom-css.scss b/static/custom-css.scss
@@ -85,3 +85,7 @@ em {
.uk-nav-default > li > a {
color: $active-menu !important;
}
+
+.error {
+ color: red;
+}