daydream

A small matrix web client written in rust
git clone git://archive.git.mtrnord.blog/daydream-mx/daydream.git
Log | Files | Refs | README | LICENSE

commit b0186dad8a6b9434b12621779b626aaad435d953
parent bb4a25c3e064e56b780785c7bdd9d8c7edd07955
Author: Marcel <mtrnord1@gmail.com>
Date:   Tue, 26 May 2020 14:45:22 +0200

Save access_token after login and use enum as agent response

Took 1 hour 6 minutes

Diffstat:
MCargo.toml | 1+
Msrc/app.rs | 33++++++++++++++++++---------------
Msrc/app/matrix.rs | 158+++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------------
Msrc/app/views/login.rs | 4----
Msrc/app/views/main_view.rs | 12+++++-------
Asrc/constants.rs | 1+
Asrc/errors.rs | 10++++++++++
Msrc/lib.rs | 2++
8 files changed, 152 insertions(+), 69 deletions(-)

diff --git a/Cargo.toml b/Cargo.toml @@ -25,6 +25,7 @@ futures = "0.3" matrix-sdk = { version = "0.1.0", git = "https://github.com/matrix-org/matrix-rust-sdk.git", default-features = false} url = "2.1.1" yew_styles = "0.3.1" +thiserror = "1.0" [dev-dependencies] wasm-bindgen-test = "0.3" diff --git a/src/app.rs b/src/app.rs @@ -64,23 +64,26 @@ impl Component for App { } Msg::NewMessage(response) => { info!("NewMessage: {:#?}", response); - if response.message == "client_logged_in"{ - info!("client_logged_in"); - info!("{}", response.content); - let route: Route = if response.content == "true" { - //self.state.logged_in = true; + match response { + Response::Error(_) => {}, + Response::LoggedIn(logged_in) => { + info!("client_logged_in"); + info!("{}",logged_in); + let route: Route = if logged_in { + //self.state.logged_in = true; - // replace with sync routeagent message once its possible - // https://github.com/yewstack/yew/issues/1127 - //RouteService::new().get_route(); - AppRoute::MainView.into() - } else { - AppRoute::Start.into() - }; + // replace with sync routeagent message once its possible + // https://github.com/yewstack/yew/issues/1127 + //RouteService::new().get_route(); + AppRoute::MainView.into() + } else { + AppRoute::Start.into() + }; - info!("{:#?}", route.clone()); - self.route = Some(route.clone()); - self.route_agent.send(ChangeRoute(route)); + info!("{:#?}", route.clone()); + self.route = Some(route.clone()); + self.route_agent.send(ChangeRoute(route)); + }, } } } diff --git a/src/app/matrix.rs b/src/app/matrix.rs @@ -1,24 +1,42 @@ +use std::collections::HashSet; +use std::convert::TryFrom; +use std::sync::{Arc, Mutex}; + use log::*; -use matrix_sdk::{Client, ClientConfig}; +use matrix_sdk::{Client, ClientConfig, Session, Error}; use serde_derive::{Deserialize, Serialize}; -use std::collections::HashSet; use url::Url; use wasm_bindgen_futures::spawn_local; +use yew::format::Json; +use yew::services::{storage::Area, StorageService}; use yew::worker::*; -#[derive(Serialize, Deserialize, Default, Clone)] +use crate::constants::AUTH_KEY; +use crate::errors::MatrixError; + +#[derive(Serialize, Deserialize, Default, Clone, Debug)] pub struct MatrixClient { pub(crate) homeserver: Option<String>, pub(crate) username: Option<String>, pub(crate) password: Option<String>, } -#[derive(Clone)] +#[derive(Serialize, Deserialize, Default, Clone, Debug)] +pub struct SessionStore { + access_token: String, + user_id: String, + device_id: String, + homeserver_url: String, +} + +#[derive(Clone, Debug)] pub struct MatrixAgent { link: AgentLink<MatrixAgent>, matrix_state: MatrixClient, matrix_client: Option<Client>, subscribers: HashSet<HandlerId>, + storage: Arc<Mutex<StorageService>>, + session: Option<SessionStore>, } #[derive(Serialize, Deserialize, Debug)] @@ -30,11 +48,10 @@ pub enum Request { GetLoggedIn, } -// TODO make enum #[derive(Serialize, Deserialize, Debug, Clone)] -pub struct Response { - pub message: String, - pub content: String, +pub enum Response { + Error(MatrixError), + LoggedIn(bool), } impl Agent for MatrixAgent { @@ -44,16 +61,30 @@ impl Agent for MatrixAgent { type Output = Response; fn create(link: AgentLink<Self>) -> Self { + let storage = Arc::new(Mutex::new(StorageService::new(Area::Local).expect("storage was disabled by the user"))); + let session: Option<SessionStore> = { + if let Json(Ok(restored_model)) = storage.lock().unwrap().restore(AUTH_KEY) { + Some(restored_model) + } else { + None + } + }; + info!("session: {:#?}", session); MatrixAgent { link, matrix_state: Default::default(), matrix_client: None, subscribers: HashSet::new(), + storage, + session, } } fn update(&mut self, _: Self::Message) {} + fn connected(&mut self, id: HandlerId) { + self.subscribers.insert(id); + } fn handle_input(&mut self, msg: Self::Input, _: HandlerId) { match msg { Request::SetHomeserver(homeserver) => { @@ -68,10 +99,7 @@ impl Agent for MatrixAgent { Request::Login() => { let login_client = self.login(); if login_client.is_none() { - let resp = Response { - message: "login_missing_client".to_string(), - content: "".to_string(), - }; + let resp = Response::Error(MatrixError::MissingClient); for sub in self.subscribers.iter() { self.link.respond(*sub, resp.clone()); } @@ -84,21 +112,38 @@ impl Agent for MatrixAgent { let password = password.clone(); let client = client.clone(); let subscribers = self.subscribers.clone(); - let agent = self.clone(); + let mut agent = self.clone(); spawn_local(async move { - client - .login( - username.clone(), - password.clone(), - None, - Some("Daydream".to_string()), - ) - .await; + // TODO handle login error + if agent.session.is_some() { + let stored_session = agent.session.clone().unwrap(); + let session = Session { + access_token: stored_session.access_token, + user_id: matrix_sdk::identifiers::UserId::try_from(stored_session.user_id.as_str()).unwrap(), + device_id: stored_session.device_id, + }; + client.restore_login(session).await; + } else { + let login_response: matrix_sdk::api::r0::session::login::Response = client + .login( + username.clone(), + password.clone(), + 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().clone().into_string(), + }; + let mut storage = agent.storage.lock().unwrap(); + storage.store(AUTH_KEY, Json(&session_store)); + } + info!("did login"); - let resp = Response { - message: "client_logged_in".to_string(), - content: "true".to_string(), - }; + let resp = Response::LoggedIn(true); info!("prepared login response"); for sub in subscribers.iter() { agent.link.respond(*sub, resp.clone()); @@ -108,23 +153,33 @@ impl Agent for MatrixAgent { } Request::GetLoggedIn => { let subscribers = self.subscribers.clone(); + let login_client = self.login(); + if login_client.is_none() { + let resp = Response::Error(MatrixError::MissingClient); + for sub in self.subscribers.iter() { + self.link.respond(*sub, resp.clone()); + } + return; + } + + // Always clone agent after having tried to login! let agent = self.clone(); + spawn_local(async move { let logged_in = agent.get_logged_in().await; - let resp = Response { - message: "client_logged_in".to_string(), - content: logged_in, - }; - for sub in subscribers.iter() { - agent.link.respond(*sub, resp.clone()); + + if !logged_in && agent.session.is_some() { + error!("Not logged in but got session"); + } else { + let resp = Response::LoggedIn(logged_in.clone()); + for sub in subscribers.iter() { + agent.link.respond(*sub, resp.clone()); + } } }); } } } - fn connected(&mut self, id: HandlerId) { - self.subscribers.insert(id); - } fn disconnected(&mut self, id: HandlerId) { self.subscribers.remove(&id); @@ -132,32 +187,49 @@ impl Agent for MatrixAgent { } impl MatrixAgent { - async fn get_logged_in(&self) -> String { + async fn get_logged_in(&self) -> bool { if self.matrix_client.is_none() { - return "false".to_string(); + return false; } self.matrix_client .clone() .unwrap() .logged_in() .await - .to_string() } fn login(&mut self) -> Option<Client> { - return if self.matrix_state.homeserver.is_none() + return 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.matrix_client.is_some()) && self.session.is_none() { - let resp = Response { - message: "login_fields_missing".to_string(), - content: "".to_string(), - }; + let resp = Response::Error(MatrixError::MissingFields); for sub in self.subscribers.iter() { self.link.respond(*sub, resp.clone()); } None + } else if self.session.is_some() { + let homeserver = self.session.clone().unwrap().homeserver_url; + + let client_config = ClientConfig::new(); + let homeserver_url = Url::parse(&homeserver.clone()).unwrap(); + let client = Client::new_with_config(homeserver_url, client_config).unwrap(); + self.matrix_client = Some(client.clone()); + + // Also directly restore Login data + let stored_session = self.session.clone().unwrap(); + let session = Session { + access_token: stored_session.access_token, + user_id: matrix_sdk::identifiers::UserId::try_from(stored_session.user_id.as_str()).unwrap(), + device_id: stored_session.device_id, + }; + let client_clone = client.clone(); + spawn_local(async move { + client_clone.restore_login(session).await; + }); + + Some(client.clone()) } else { let homeserver = self.matrix_state.homeserver.clone().unwrap(); diff --git a/src/app/views/login.rs b/src/app/views/login.rs @@ -1,7 +1,6 @@ use log::*; use yew::agent::{Dispatched, Dispatcher}; use yew::prelude::*; -use yew::services::storage::{Area, StorageService}; use crate::app::matrix::{MatrixAgent, Request}; @@ -10,7 +9,6 @@ pub struct Login { homeserver: String, username: String, password: String, - storage: StorageService, matrix_agent: Dispatcher<MatrixAgent>, } @@ -28,7 +26,6 @@ impl Component for Login { type Properties = (); fn create(_: Self::Properties, link: ComponentLink<Self>) -> Self { - let storage = StorageService::new(Area::Local).unwrap(); let matrix_agent = MatrixAgent::dispatcher(); Login { link, @@ -36,7 +33,6 @@ impl Component for Login { homeserver: "".to_string(), username: "".to_string(), password: "".to_string(), - storage, matrix_agent, } } diff --git a/src/app/views/main_view.rs b/src/app/views/main_view.rs @@ -1,15 +1,12 @@ use log::*; use serde_derive::{Deserialize, Serialize}; use yew::prelude::*; -use yew::services::storage::Area; -use yew::services::StorageService; use yew::ComponentLink; use crate::app::matrix::{MatrixAgent, Response}; pub struct MainView { link: ComponentLink<Self>, - storage: StorageService, state: State, matrix_agent: Box<dyn Bridge<MatrixAgent>>, } @@ -26,14 +23,12 @@ impl Component for MainView { type Properties = (); fn create(_: Self::Properties, link: ComponentLink<Self>) -> Self { - let storage = StorageService::new(Area::Local).unwrap(); let matrix_callback = link.callback(Msg::NewMessage); let matrix_agent = MatrixAgent::bridge(matrix_callback); let state = State {}; MainView { link, - storage, matrix_agent, state, } @@ -43,8 +38,11 @@ impl Component for MainView { match msg { Msg::NewMessage(response) => { info!("NewMessage: {:#?}", response); - if response.message == "client_logged_in" { - info!("client_logged_in: {}", response.content); + match response { + Response::LoggedIn(v) => { + info!("client_logged_in: {}", v); + }, + _ => {} } } } diff --git a/src/constants.rs b/src/constants.rs @@ -0,0 +1 @@ +pub const AUTH_KEY: &str = "nordgedanken.daydream.auth_data"; diff --git a/src/errors.rs b/src/errors.rs @@ -0,0 +1,10 @@ +use serde_derive::{Deserialize, Serialize};use thiserror::Error; + + +#[derive(Error, Debug, Clone, Serialize, Deserialize)] +pub enum MatrixError { + #[error("No Matrix Client is available yet")] + MissingClient, + #[error("Missing required Data")] + MissingFields, +} diff --git a/src/lib.rs b/src/lib.rs @@ -4,6 +4,8 @@ extern crate cfg_if; mod app; +mod errors; +mod constants; use wasm_bindgen::prelude::*;