diff --git a/src/components/atoms/helper.rs b/src/components/atoms/helper.rs index b9cf6d0..23742da 100644 --- a/src/components/atoms/helper.rs +++ b/src/components/atoms/helper.rs @@ -9,7 +9,7 @@ pub struct HelperData { #[derive(Props)] pub struct HelperProps<'a> { helper: HelperData, - on_click: EventHandler<'a, MouseEvent>, + _on_click: EventHandler<'a, MouseEvent>, } pub fn Helper<'a>(cx: Scope<'a, HelperProps<'a>>) -> Element<'a> { diff --git a/src/components/atoms/input.rs b/src/components/atoms/input.rs index 5aafb34..72b0e7b 100644 --- a/src/components/atoms/input.rs +++ b/src/components/atoms/input.rs @@ -50,7 +50,7 @@ pub fn MessageInput<'a>(cx: Scope<'a, MessageInputProps<'a>>) -> Element<'a> { ) } div { - class: "input-wrapper input_error_container", + class: "input-wrapper {input_error_container}", match cx.props.itype { InputType::Search => { render!( diff --git a/src/components/atoms/messages/content/file.rs b/src/components/atoms/messages/content/file.rs index c9d197d..151e3ca 100644 --- a/src/components/atoms/messages/content/file.rs +++ b/src/components/atoms/messages/content/file.rs @@ -13,12 +13,6 @@ pub struct FileProps { } pub fn File<'a>(cx: Scope<'a, FileProps>) -> Element<'a> { - let message_reply = if cx.props.is_reply { - "message-reply__content--file" - } else { - "" - }; - cx.render(rsx!( section { class: "file", diff --git a/src/components/molecules/input_message.rs b/src/components/molecules/input_message.rs index 60532a2..661b929 100644 --- a/src/components/molecules/input_message.rs +++ b/src/components/molecules/input_message.rs @@ -5,9 +5,9 @@ use dioxus_std::{i18n::use_i18, translate}; use futures_util::TryFutureExt; use crate::{ - components::{atoms::{header_main::{HeaderEvent, HeaderCallOptions}, hover_menu::{MenuEvent, MenuOption}, input::InputType, message::MessageView, Attach, Button, Close, Icon, Message, TextareaInput + components::{atoms::{header_main::{HeaderEvent, HeaderCallOptions}, hover_menu::{MenuEvent, MenuOption}, message::MessageView, Attach, Button, Close, Icon, Message, TextareaInput }, molecules::AttachPreview}, - services::matrix::matrix::{TimelineMessageType, EventOrigin, Attachment}, hooks::{use_attach::{use_attach, AttachError, AttachFile}, use_client::use_client, use_notification::use_notification, use_reply::use_reply, use_room::use_room, use_send_attach::SendAttachStatus}, + services::matrix::matrix::{TimelineMessageType, EventOrigin, Attachment}, hooks::{use_attach::{use_attach, AttachError, AttachFile}, use_notification::use_notification, use_reply::use_reply}, }; #[derive(Debug, Clone)] @@ -26,7 +26,6 @@ pub struct ReplyingTo { #[derive(Props)] pub struct InputMessageProps<'a> { - message_type: InputType, placeholder: &'a str, on_submit: EventHandler<'a, FormMessageEvent>, on_event: EventHandler<'a, HeaderEvent>, @@ -36,8 +35,6 @@ pub struct InputMessageProps<'a> { pub fn InputMessage<'a>(cx: Scope<'a, InputMessageProps<'a>>) -> Element<'a> { let i18 = use_i18(cx); let attach = use_attach(cx); - let client = use_client(cx); - let room = use_room(cx); let notification = use_notification(cx); let key_input_message_unknown_content = translate!(i18, "chat.input_message.unknown_content"); @@ -45,8 +42,6 @@ pub fn InputMessage<'a>(cx: Scope<'a, InputMessageProps<'a>>) -> Element<'a> { let key_input_message_not_found = translate!(i18, "chat.input_message.not_found"); let key_input_message_cta = translate!(i18, "chat.input_message.cta"); - let send_attach_status = - use_shared_state::(cx).expect("Unable to use SendAttachStatus"); let replying_to = use_reply(cx); let message_field = use_state(cx, String::new); @@ -73,7 +68,7 @@ pub fn InputMessage<'a>(cx: Scope<'a, InputMessageProps<'a>>) -> Element<'a> { let infered_type = infer::get(content.deref()).ok_or(AttachError::UncoverType)?; let content_type: Result = infered_type.mime_type().parse(); - let content_type = content_type.map_err(|e|AttachError::UnknownContent)?; + let content_type = content_type.map_err(|_|AttachError::UnknownContent)?; let blob = match content_type.type_() { mime::IMAGE => { @@ -168,7 +163,7 @@ pub fn InputMessage<'a>(cx: Scope<'a, InputMessageProps<'a>>) -> Element<'a> { if let Some(_) = attach.get() { rsx!( AttachPreview { - on_event: move |event| { + on_event: move |_| { on_handle_send_attach(); attach.reset(); } @@ -191,7 +186,7 @@ pub fn InputMessage<'a>(cx: Scope<'a, InputMessageProps<'a>>) -> Element<'a> { Button { text: "{key_input_message_cta}", status: None, - on_click: move |event| { + on_click: move |_| { if let Some(l) = &cx.props.on_attach { let attachment = Attachment { body: file.name.clone(), @@ -212,7 +207,6 @@ pub fn InputMessage<'a>(cx: Scope<'a, InputMessageProps<'a>>) -> Element<'a> { value: "{message_field}", placeholder: cx.props.placeholder, on_input: move |event: FormEvent| { - let value = event.value.clone(); message_field.set(event.value.clone()); }, on_keypress: move |event: KeyboardEvent| { diff --git a/src/components/molecules/list.rs b/src/components/molecules/list.rs index 8a3b0c7..afe1c94 100644 --- a/src/components/molecules/list.rs +++ b/src/components/molecules/list.rs @@ -58,7 +58,7 @@ pub fn List<'a>(cx: Scope<'a, ListProps<'a>>) -> Element<'a> { cx.render(rsx! { div { - class:"messages-list messages_list_thread", + class:"messages-list {messages_list_thread}", onmounted: move |event| { event.data.get_raw_element() .ok() @@ -82,7 +82,7 @@ pub fn List<'a>(cx: Scope<'a, ListProps<'a>>) -> Element<'a> { let mut old_value = 0; - let on_down = EventListener::new(&container.clone(), "scroll", move |_| { + EventListener::new(&container.clone(), "scroll", move |_| { let container_height = container.client_height(); let scroll_top = container.scroll_top() * -1; let list_height = list.client_height(); diff --git a/src/components/molecules/menu.rs b/src/components/molecules/menu.rs index 5e8cf15..8f0c874 100644 --- a/src/components/molecules/menu.rs +++ b/src/components/molecules/menu.rs @@ -1,16 +1,13 @@ -use crate::hooks::use_notification::use_notification; -use crate::MatrixClientState; -use crate::{hooks::use_auth::use_auth, services::matrix::matrix::create_client}; -use crate::hooks::use_client::use_client; use dioxus::prelude::*; use dioxus_std::{i18n::use_i18, translate}; -use gloo::storage::LocalStorage; -use wasm_bindgen::prelude::wasm_bindgen; - -use crate::components::atoms::{ChatConversation, Icon, LogOut, MenuItem, UserCircle}; - use dioxus_router::prelude::*; +use futures::TryFutureExt; +use crate::components::atoms::{ChatConversation, Icon, LogOut, MenuItem, UserCircle}; +use crate::hooks::use_auth::LogoutError; +use crate::hooks::use_notification::use_notification; +use crate::hooks::use_auth::use_auth; +use crate::hooks::use_client::use_client; use crate::pages::route::Route; #[derive(Props)] @@ -36,24 +33,15 @@ pub fn Menu<'a>(cx: Scope<'a, MenuProps<'a>>) -> Element<'a> { to_owned![client, auth, notification, key_logout_error_server, key_chat_common_error_default_server]; async move { - let response = client.get().logout().await; - - let Ok(_) = response else { - return notification.handle_error(&key_logout_error_server) + auth.logout(&client).await + }.unwrap_or_else(move |e: LogoutError| { + let message = match e { + LogoutError::Failed |LogoutError::DefaultClient => key_logout_error_server, + LogoutError::RemoveSession => key_chat_common_error_default_server, }; - - let _ = ::delete("session_file"); - let Ok(c) = create_client("https://matrix.org").await else { - return notification.handle_error(&key_chat_common_error_default_server) - }; - - client.set(MatrixClientState { - client: Some(c.clone()), - }); - - auth.set_logged_in(false) - } + notification.handle_error(&message) + }) }); }; diff --git a/src/components/organisms/chat/active_room.rs b/src/components/organisms/chat/active_room.rs index 50125c9..5f7d1d9 100644 --- a/src/components/organisms/chat/active_room.rs +++ b/src/components/organisms/chat/active_room.rs @@ -6,7 +6,6 @@ use crate::{ components::{ atoms::{ header_main::{HeaderCallOptions, HeaderEvent}, - input::InputType, Avatar, Close, Header, Icon, }, molecules::{input_message::FormMessageEvent, rooms::CurrentRoom, InputMessage, List}, @@ -118,7 +117,6 @@ pub fn ActiveRoom(cx: Scope) -> Element { } }, InputMessage { - message_type: InputType::Message, placeholder: input_placeholder.get().as_str(), on_submit: move |event| { on_push_message(event, false) @@ -165,7 +163,6 @@ pub fn ActiveRoom(cx: Scope) -> Element { } }, InputMessage { - message_type: InputType::Message, placeholder: input_placeholder.get().as_str(), on_submit: move |event| { on_push_message(event, true) diff --git a/src/hooks/factory/message_factory.rs b/src/hooks/factory/message_factory.rs index 8b3f154..db56a8a 100644 --- a/src/hooks/factory/message_factory.rs +++ b/src/hooks/factory/message_factory.rs @@ -11,7 +11,7 @@ use dioxus::prelude::*; #[allow(clippy::needless_return)] -pub fn use_message_factory(cx: &ScopeState) -> MessageFactoryType { +pub fn use_message_factory(_cx: &ScopeState) -> MessageFactoryType { MessageFactoryType {} } diff --git a/src/hooks/use_attach.rs b/src/hooks/use_attach.rs index c059547..3a9f3f0 100644 --- a/src/hooks/use_attach.rs +++ b/src/hooks/use_attach.rs @@ -13,12 +13,11 @@ pub struct AttachFile { pub size: u64, } -#[derive(Clone)] -#[derive(Debug)] +#[derive(Clone, Debug)] pub enum AttachError { - NotFound, + NotFound, UncoverType, - UnknownContent + UnknownContent, } #[allow(clippy::needless_return)] diff --git a/src/hooks/use_auth.rs b/src/hooks/use_auth.rs index 5a5a2ad..d678893 100644 --- a/src/hooks/use_auth.rs +++ b/src/hooks/use_auth.rs @@ -1,14 +1,15 @@ use dioxus::prelude::*; use gloo::storage::{errors::StorageError, LocalStorage}; use matrix_sdk::Client; +use ruma::api::client::discovery::discover_homeserver::Response as WellKnownResponse; use ruma::api::IncomingResponse; use serde::{Deserialize, Serialize}; use url::Url; -use ruma::api::client::discovery::discover_homeserver::Response as WellKnownResponse; - use crate::pages::login::LoggedIn; +use super::use_client::UseClientState; + #[derive(Debug, Clone, Serialize, Deserialize)] pub enum AuthError { BuildError, @@ -180,7 +181,7 @@ impl UseAuthState { let Some(domain) = server.domain() else { return; }; - + let domain_name = extract_domain_name(domain); if !username_parse.ends_with(domain_name.as_str()) { username_parse = format!("{}:{}", username_parse, domain_name); @@ -248,6 +249,30 @@ impl UseAuthState { pub fn set_logged_in(&self, option: bool) { *self.logged_in.write() = LoggedIn(option); } + + pub async fn logout(&self, client: &UseClientState) -> Result<(), LogoutError> { + client + .get() + .logout() + .await + .map_err(|_| LogoutError::Failed)?; + ::delete("session_file"); + + client + .default() + .await + .map_err(|_| LogoutError::DefaultClient)?; + + self.set_logged_in(false); + + Ok(()) + } +} + +pub enum LogoutError { + DefaultClient, + RemoveSession, + Failed, } fn extract_domain_name(host: &str) -> String { diff --git a/src/hooks/use_chat.rs b/src/hooks/use_chat.rs index a385444..6ec2a99 100644 --- a/src/hooks/use_chat.rs +++ b/src/hooks/use_chat.rs @@ -37,8 +37,6 @@ pub fn use_chat(cx: &ScopeState) -> &UseChatState { let key_common_error_room_id = translate!(i18, "chat.common.error.room_id"); let key_chat_session_error_not_found = translate!(i18, "chat.session.error.not_found"); - let key_chat_message_list_errors_thread_not_found = - translate!(i18, "chat.message_list.errors.thread_not_found"); let key_chat_message_list_errors_room_not_found = translate!(i18, "chat.message_list.errors.room_not_found"); let key_chat_message_list_errors_timeline_invalid_limit = @@ -74,7 +72,7 @@ pub fn use_chat(cx: &ScopeState) -> &UseChatState { .unwrap_or(&15) .clone(); - process( + if let Err(e) = process( current_events, &session, &messages, @@ -83,7 +81,7 @@ pub fn use_chat(cx: &ScopeState) -> &UseChatState { ¤t_room_id, ) .await - .map_err(|e: ChatError| { + { let message = match e { ChatError::InvalidSession => &key_chat_session_error_not_found, ChatError::InvalidRoom => &key_common_error_room_id, @@ -100,9 +98,7 @@ pub fn use_chat(cx: &ScopeState) -> &UseChatState { messages_loading.set(false); notification.handle_error(&message); - - return; - }); + }; if let Some(thread) = threading_to.get() { messages.get().iter().find_map(|m| { @@ -137,7 +133,7 @@ pub fn use_chat(cx: &ScopeState) -> &UseChatState { } }); - use_effect(cx, &(room.get().id), |id| { + use_effect(cx, &(room.get().id), |_| { to_owned![task_timeline, from]; async move { from.set(None); @@ -192,9 +188,8 @@ impl UseChatState { self.inner.clone() } - pub fn set(&self, state: ChatState) { - let mut inner = &self.inner; - inner = &state; + pub fn set(&mut self, state: ChatState) { + self.inner = state; } pub fn loadmore(&self, current_room_id: &str) { diff --git a/src/hooks/use_client.rs b/src/hooks/use_client.rs index ade4f39..38d9b72 100644 --- a/src/hooks/use_client.rs +++ b/src/hooks/use_client.rs @@ -1,9 +1,11 @@ -use std::ops::Deref; - use dioxus::prelude::*; + use matrix_sdk::Client; +use std::ops::Deref; -use crate::MatrixClientState; +use crate::{ + services::matrix::matrix::create_client, utils::get_homeserver::Homeserver, MatrixClientState, +}; #[allow(clippy::needless_return)] pub fn use_client(cx: &ScopeState) -> &UseClientState { @@ -33,4 +35,24 @@ impl UseClientState { let mut inner = self.inner.write(); *inner = client; } + + pub async fn default(&self) -> Result<(), ClientError> { + let homeserver = Homeserver::new().map_err(|_| ClientError::InvalidUrl)?; + + let c = match create_client(&homeserver.get_base_url()).await { + Ok(c) => c, + Err(_) => create_client(&Homeserver::default().get_base_url()) + .await + .map_err(|_| ClientError::DefaultServer)?, + }; + + self.set(MatrixClientState { client: Some(c) }); + + Ok(()) + } +} + +pub enum ClientError { + InvalidUrl, + DefaultServer, } diff --git a/src/hooks/use_listen_message.rs b/src/hooks/use_listen_message.rs index 0d297af..595aa4e 100644 --- a/src/hooks/use_listen_message.rs +++ b/src/hooks/use_listen_message.rs @@ -1,6 +1,6 @@ use dioxus::prelude::*; use dioxus_std::{i18n::use_i18, translate}; -use futures_util::StreamExt; +use futures_util::{StreamExt, TryFutureExt}; use log::info; use matrix_sdk::{ config::SyncSettings, room::Room, ruma::events::room::message::OriginalSyncRoomMessageEvent, @@ -33,9 +33,8 @@ pub fn use_listen_message(cx: &ScopeState) -> &UseListenMessagesState { let handler_added = use_ref(cx, || false); - let key_common_error_thread_id = translate!(i18, "chat.common.error.thread_id"); - let key_common_error_event_id = translate!(i18, "chat.common.error.event_id"); let key_common_error_user_id = translate!(i18, "chat.common.error.user_id"); + let key_common_error_sync = translate!(i18, "chat.common.error.sync"); let key_listen_message_image = translate!(i18, "chat.listen.message.image"); let key_listen_message_file = translate!(i18, "chat.listen.message.file"); let key_listen_message_video = translate!(i18, "chat.listen.message.video"); @@ -45,204 +44,240 @@ pub fn use_listen_message(cx: &ScopeState) -> &UseListenMessagesState { let message_dispatch_id = use_shared_state::(cx).expect("Unable to use MessageDispatchId"); let threading_to = use_thread(cx); + let position = use_ref(cx, || None); - let task_sender = use_coroutine( - cx, - |mut rx: UnboundedReceiver<(MessageEvent, Option)>| { - to_owned![ - client, - messages, - notification, - room, - threading_to, - session, - key_common_error_user_id - ]; + let task_sender = use_coroutine(cx, |mut rx: UnboundedReceiver| { + to_owned![ + messages, + notification, + room, + threading_to, + session, + key_common_error_user_id, + position + ]; - async move { - while let Some((message_event, message_position_local)) = rx.next().await { - if let Some(message) = message_event.mgs { - let mut msgs = messages.get().clone(); - let mut plain_message = None; - - let is_in_current_room = - message_event.room.room_id().as_str().eq(&room.get().id); - - let last_message_id = messages.get().len() as i64; - - match &message { - TimelineRelation::Thread(timeline_thread) => { - // Position of an existing thread timeline - let position = msgs.iter().position(|m| { - let TimelineRelation::CustomThread(t) = m else { - return false; - }; + async move { + while let Some(message_event) = rx.next().await { + let message_position_local = *position.read(); + if let Some(message) = message_event.mgs { + let mut msgs = messages.get().clone(); + let mut plain_message = None; + + let is_in_current_room = + message_event.room.room_id().as_str().eq(&room.get().id); + + match &message { + TimelineRelation::Thread(timeline_thread) => { + // Position of an existing thread timeline + let position = msgs.iter().position(|m| { + let TimelineRelation::CustomThread(t) = m else { + return false; + }; - t.event_id.eq(&timeline_thread.event_id) - }); + t.event_id.eq(&timeline_thread.event_id) + }); - match position { - Some(p) => { - if let TimelineRelation::CustomThread(ref mut t) = msgs[p] { - t.thread.push(timeline_thread.thread[0].clone()); - }; - } - None => { - let relation = - TimelineRelation::CustomThread(TimelineThread { - event_id: timeline_thread.event_id.clone(), - thread: timeline_thread.thread.clone(), - latest_event: timeline_thread.thread - [timeline_thread.thread.len() - 1] - .clone() - .event_id, - count: timeline_thread.thread.len(), - }); - - if is_in_current_room { - msgs.push(relation); - } + match position { + Some(p) => { + if let TimelineRelation::CustomThread(ref mut t) = msgs[p] { + t.thread.push(timeline_thread.thread[0].clone()); + }; + } + None => { + let relation = TimelineRelation::CustomThread(TimelineThread { + event_id: timeline_thread.event_id.clone(), + thread: timeline_thread.thread.clone(), + latest_event: timeline_thread.thread + [timeline_thread.thread.len() - 1] + .clone() + .event_id, + count: timeline_thread.thread.len(), + }); + + if is_in_current_room { + msgs.push(relation); } } - - plain_message = Some(key_listen_message_thread.as_str()); } - TimelineRelation::None(timeline_message) => { - // Position of a head thread timeline - let position = msgs.iter().position(|m| { - let TimelineRelation::CustomThread(t) = m else { - return false; - }; - t.event_id.eq(&timeline_message.event_id) - }); + plain_message = Some(key_listen_message_thread.as_str()); + } + TimelineRelation::None(timeline_message) => { + // Position of a head thread timeline + let position = msgs.iter().position(|m| { + let TimelineRelation::CustomThread(t) = m else { + return false; + }; - match position { - Some(p) => { - if let TimelineRelation::CustomThread(ref mut z) = msgs[p] { - } - } - None => { - if is_in_current_room { - if let Some(position) = message_position_local { - msgs[position] = message.clone() - } else { - msgs.push(message.clone()); - } + t.event_id.eq(&timeline_message.event_id) + }); + + match position { + Some(p) => if let TimelineRelation::CustomThread(_) = msgs[p] {}, + None => { + if is_in_current_room { + if let Some(position) = message_position_local { + msgs[position] = message.clone() } else { - plain_message = Some(message_to_plain_content( - &timeline_message.body, - &key_listen_message_image, - &key_listen_message_file, - &key_listen_message_video, - &key_listen_message_html, - )); + msgs.push(message.clone()); } + } else { + plain_message = Some(message_to_plain_content( + &timeline_message.body, + &key_listen_message_image, + &key_listen_message_file, + &key_listen_message_video, + &key_listen_message_html, + )); } } } - TimelineRelation::Reply(timeline_message) => { - if is_in_current_room { - if let Some(position) = message_position_local { - msgs[position] = message.clone(); - } else { - msgs.push(message.clone()); - }; + } + TimelineRelation::Reply(timeline_message) => { + if is_in_current_room { + if let Some(position) = message_position_local { + msgs[position] = message.clone(); } else { - plain_message = Some(message_to_plain_content( - &timeline_message.event.body, - &key_listen_message_image, - &key_listen_message_file, - &key_listen_message_video, - &key_listen_message_html, - )); - } + msgs.push(message.clone()); + }; + } else { + plain_message = Some(message_to_plain_content( + &timeline_message.event.body, + &key_listen_message_image, + &key_listen_message_file, + &key_listen_message_video, + &key_listen_message_html, + )); } - TimelineRelation::CustomThread(timeline_message) => { - if is_in_current_room { - msgs.push(message); - } - - plain_message = Some(key_listen_message_thread.as_str()); + } + TimelineRelation::CustomThread(_) => { + if is_in_current_room { + msgs.push(message); } - }; - - messages.set(msgs.clone()); - let thread_to = threading_to.get().clone(); - - if let Some(thread) = thread_to { - messages.get().iter().for_each(|m| { - if let TimelineRelation::CustomThread(t) = m { - if t.event_id.eq(&thread.event_id) { - threading_to.set(Some(TimelineThread { - event_id: t.event_id.clone(), - thread: t.thread.clone(), - count: t.count.clone(), - latest_event: t.latest_event.clone(), - })); - } - } else if let TimelineRelation::None(t) = m { - if t.event_id.eq(&thread.event_id) { - let mut new_thread = thread.clone(); - - new_thread.thread.push(t.clone()); - - threading_to.set(Some(TimelineThread { - event_id: t.event_id.clone(), - thread: new_thread.thread.clone(), - count: new_thread.count.clone(), - latest_event: new_thread.latest_event.clone(), - })); - } - } - }); + + plain_message = Some(key_listen_message_thread.as_str()); } + }; + + messages.set(msgs.clone()); + let thread_to = threading_to.get().clone(); + + if let Some(thread) = thread_to { + messages.get().iter().for_each(|m| { + if let TimelineRelation::CustomThread(t) = m { + if t.event_id.eq(&thread.event_id) { + threading_to.set(Some(TimelineThread { + event_id: t.event_id.clone(), + thread: t.thread.clone(), + count: t.count.clone(), + latest_event: t.latest_event.clone(), + })); + } + } else if let TimelineRelation::None(t) = m { + if t.event_id.eq(&thread.event_id) { + let mut new_thread = thread.clone(); + + new_thread.thread.push(t.clone()); + + threading_to.set(Some(TimelineThread { + event_id: t.event_id.clone(), + thread: new_thread.thread.clone(), + count: new_thread.count.clone(), + latest_event: new_thread.latest_event.clone(), + })); + } + } + }); + } - let room_name = match message_event.room.name() { - Some(name) => name, - None => { - let mut name = String::from("Unknown name room"); + let room_name = match message_event.room.name() { + Some(name) => name, + None => { + let mut name = String::from("Unknown name room"); - let Some(session_data) = session.get() else { - notification.handle_error(&key_common_error_user_id); - return; - }; + let Some(session_data) = session.get() else { + notification.handle_error(&key_common_error_user_id); + return; + }; - let users = message_event.room.members().await; + let users = message_event.room.members().await; - if let Ok(members) = users { - let member = members - .into_iter() - .find(|member| !member.user_id().eq(&session_data.user_id)); + if let Ok(members) = users { + let member = members + .into_iter() + .find(|member| !member.user_id().eq(&session_data.user_id)); - if let Some(m) = member { - let n = m.name(); + if let Some(m) = member { + let n = m.name(); - name = String::from(n); - } + name = String::from(n); } - - name } - }; - - if let Some(content) = plain_message { - notification.handle_notification(NotificationItem { - title: room_name, - body: String::from(content), - show: true, - handle: NotificationHandle { - value: NotificationType::Click, - }, - }) + + name } + }; + + if let Some(content) = plain_message { + notification.handle_notification(NotificationItem { + title: room_name, + body: String::from(content), + show: true, + handle: NotificationHandle { + value: NotificationType::Click, + }, + }) } } } - }, - ) + } + }) .clone(); + let task_replacer = use_coroutine( + cx, + |mut rx: UnboundedReceiver| { + to_owned![messages, message_dispatch_id, position]; + + async move { + while let Some(ev) = rx.next().await { + let back_messages = messages.get().clone(); + let value = &message_dispatch_id.read().value; + + let to_find: Option<(String, Option)> = + value.iter().find_map(|(uuid, event_id)| { + event_id.clone().and_then(|id| { + if ev.event_id == id { + Some((uuid.clone(), event_id.clone())) + } else { + None + } + }) + }); + + info!("to find {:?}", to_find); + info!("back messages {:?}", back_messages); + + if let Some((uuid, _)) = to_find { + position.set(back_messages.iter().position(|m| match m { + TimelineRelation::None(relation) => relation.event_id == uuid, + TimelineRelation::Reply(relation) => relation.event.event_id == uuid, + TimelineRelation::CustomThread(relation) => { + relation.thread.iter().any(|rm| rm.event_id == uuid) + } + TimelineRelation::Thread(relation) => { + relation.thread.iter().any(|rm| rm.event_id == uuid) + } + })); + + info!("position {:?}", position.read()); + } + } + } + }, + ); + // After logging is mandatory to perform a client sync, // since the chat needs sync to listen for new messages // this coroutine is necesary @@ -251,20 +286,19 @@ pub fn use_listen_message(cx: &ScopeState) -> &UseListenMessagesState { client, handler_added, task_sender, - message_dispatch_id, - messages, + task_replacer, session, notification, key_common_error_user_id ]; async move { - client.sync_once(SyncSettings::default()).await; + client + .sync_once(SyncSettings::default()) + .await + .map_err(|_| ListenMessageError::FailedSync)?; - let Some(me) = session.get() else { - notification.handle_error(&key_common_error_user_id); - return; - }; + let me = session.get().ok_or(ListenMessageError::SessionNotFound)?; if !*handler_added.read() { client.add_event_handler( @@ -272,21 +306,8 @@ pub fn use_listen_message(cx: &ScopeState) -> &UseListenMessagesState { room: Room, client: matrix_sdk::Client| { let task_sender = task_sender.clone(); - let messages = messages.clone(); let me = me.clone(); - let mut back_messages = messages.get().clone(); - - let value = &message_dispatch_id.read().value; - let to_find: Option<(String, Option)> = - value.iter().find_map(|(uuid, event_id)| { - event_id.clone().and_then(|id| { - if ev.event_id == id { - Some((uuid.clone(), event_id.clone())) - } else { - None - } - }) - }); + task_replacer.send(ev.clone()); async move { let message_type = &ev.content.msgtype; @@ -296,28 +317,6 @@ pub fn use_listen_message(cx: &ScopeState) -> &UseListenMessagesState { }; let relates = &ev.content.relates_to; let time = ev.origin_server_ts; - let to_find = to_find.clone(); - let mut position = None; - - info!("to find {:?}", to_find); - info!("back messages {:?}", back_messages); - - if let Some((uuid, event_id)) = to_find { - position = back_messages.iter().position(|m| match m { - TimelineRelation::None(relation) => relation.event_id == uuid, - TimelineRelation::Reply(relation) => { - relation.event.event_id == uuid - } - TimelineRelation::CustomThread(relation) => { - relation.thread.iter().any(|rm| rm.event_id == uuid) - } - TimelineRelation::Thread(relation) => { - relation.thread.iter().any(|rm| rm.event_id == uuid) - } - }); - - info!("position {:?}", position); - } let formatted_message = format_original_any_room_message_event( &message_type, @@ -362,13 +361,10 @@ pub fn use_listen_message(cx: &ScopeState) -> &UseListenMessagesState { } } } - task_sender.send(( - MessageEvent { - room, - mgs: message_result, - }, - position, - )); + task_sender.send(MessageEvent { + room, + mgs: message_result, + }); } }, ); @@ -376,13 +372,30 @@ pub fn use_listen_message(cx: &ScopeState) -> &UseListenMessagesState { handler_added.set(true); } - let _ = client.sync(SyncSettings::default()).await; + client + .sync(SyncSettings::default()) + .await + .map_err(|_| ListenMessageError::FailedSync)?; + + Ok::<(), ListenMessageError>(()) } + .unwrap_or_else(move |e: ListenMessageError| { + let message = match e { + ListenMessageError::FailedSync => key_common_error_sync, + ListenMessageError::SessionNotFound => key_common_error_user_id, + }; + notification.handle_error(&message); + }) }); cx.use_hook(move || UseListenMessagesState {}) } +pub enum ListenMessageError { + FailedSync, + SessionNotFound, +} + #[derive(Clone)] pub struct UseListenMessagesState {} @@ -400,8 +413,8 @@ pub fn message_to_plain_content<'a>( match &content { TimelineMessageType::Image(_) => key_image, TimelineMessageType::Text(t) => t, - TimelineMessageType::File(t) => key_file, - TimelineMessageType::Video(t) => key_video, - TimelineMessageType::Html(t) => key_html, + TimelineMessageType::File(_) => key_file, + TimelineMessageType::Video(_) => key_video, + TimelineMessageType::Html(_) => key_html, } } diff --git a/src/hooks/use_send_attach.rs b/src/hooks/use_send_attach.rs index 9b475f1..a241bfc 100644 --- a/src/hooks/use_send_attach.rs +++ b/src/hooks/use_send_attach.rs @@ -51,7 +51,6 @@ pub fn use_send_attach(cx: &ScopeState) -> &UseSendMessageState { let key_common_error_room_id = translate!(i18, "chat.common.error.room_id"); let key_common_error_file_type = translate!(i18, "chat.common.error.file_type"); - let key_attach_error_upload_file = translate!(i18, "chat.attach.error.upload_file"); let key_attach_error_send_message = translate!(i18, "chat.attach.error.send_message"); let send_attach_status = @@ -88,7 +87,7 @@ pub fn use_send_attach(cx: &ScopeState) -> &UseSendMessageState { // build message relation let content_type = file.content_type.type_(); - let source = match content_type { + match content_type { mime::IMAGE => { crate::services::matrix::matrix::ImageType::Media(file.data.clone()) } @@ -126,7 +125,7 @@ pub fn use_send_attach(cx: &ScopeState) -> &UseSendMessageState { ×tamp, &session.get().unwrap(), ) - } else if let Some(mut thread) = thread_to.to_owned() { + } else if let Some(thread) = thread_to.to_owned() { message_factory.thread(thread).create_message( &attach_type, &uuid.to_string(), diff --git a/src/hooks/use_send_message.rs b/src/hooks/use_send_message.rs index 9d9d7c8..8a041d8 100644 --- a/src/hooks/use_send_message.rs +++ b/src/hooks/use_send_message.rs @@ -53,7 +53,6 @@ pub fn use_send_message(cx: &ScopeState) -> &UseSendMessageState { let session = use_session(cx); let replying_to = use_reply(cx); let threading_to = use_thread(cx); - let message_factory = use_message_factory(cx); let key_common_error_thread_id = translate!(i18, "chat.common.error.thread_id"); @@ -64,8 +63,6 @@ pub fn use_send_message(cx: &ScopeState) -> &UseSendMessageState { let key_message_error_send_message = translate!(i18, "chat.message.error.send_message"); - let key_message_error_send_message = translate!(i18, "chat.message.error.send_message"); - let key_commands_join_errors_room_not_found = translate!(i18, "chat.commands.join.errors.room_not_found"); let key_commands_join_errors_action_not_found = @@ -78,7 +75,6 @@ pub fn use_send_message(cx: &ScopeState) -> &UseSendMessageState { let message_dispatch_id = use_shared_state::(cx).expect("Unable to use MessageDispatchId"); - let message_item = use_state::>(cx, || None); let value = use_ref::(cx, || MessageStatus::None); let task_push = use_coroutine(cx, |mut rx: UnboundedReceiver| { @@ -87,8 +83,6 @@ pub fn use_send_message(cx: &ScopeState) -> &UseSendMessageState { replying_to, threading_to, notification, - message_item, - value, messages, session, message_dispatch_id, @@ -144,7 +138,7 @@ pub fn use_send_message(cx: &ScopeState) -> &UseSendMessageState { ×tamp, &session_data, ) - } else if let Some(mut thread) = threading_to.get().to_owned() { + } else if let Some(thread) = threading_to.get().to_owned() { message_factory.thread(thread).create_message( &TimelineMessageType::Text(message_item.msg.clone().into()), &uuid, diff --git a/src/hooks/use_session.rs b/src/hooks/use_session.rs index 192ddfe..6068801 100644 --- a/src/hooks/use_session.rs +++ b/src/hooks/use_session.rs @@ -2,11 +2,11 @@ use dioxus::prelude::*; use gloo::storage::{errors::StorageError, LocalStorage}; use log::info; use matrix_sdk::HttpError; -use matrix_sdk::{config::SyncSettings, Client, Error}; +use matrix_sdk::{config::SyncSettings, Client}; use ruma::api::client::filter::{FilterDefinition, RoomEventFilter}; use ruma::api::client::sync::sync_events; -use ruma::events::EventType; +use ruma::events::RoomEventType; use std::time::Duration; use crate::services::matrix::matrix::FullSession; @@ -29,6 +29,13 @@ pub struct UserSession { pub device_id: Option, } +pub enum SessionError { + SaveFailed, + GetFailed, + WhoamiFailed, + SyncFailed, +} + impl UseSessionState { fn set(&self, data: UserSession) { *self.data.write() = Some(data); @@ -39,10 +46,20 @@ impl UseSessionState { } pub async fn whoami(&self, client: Client) -> Result { - let user = client.whoami().await?; - let data = UserSession { - user_id: user.user_id.to_string(), - device_id: user.device_id.map(|id| id.to_string()), + let user_id = client.user_id(); + let device_id = client.device_id(); + + let data = if let Some(user_id) = user_id { + UserSession { + user_id: user_id.to_string(), + device_id: device_id.map(|id| id.to_string()), + } + } else { + let user = client.whoami().await?; + UserSession { + user_id: user.user_id.to_string(), + device_id: user.device_id.map(|id| id.to_string()), + } }; Self::set(&self, data.clone()); @@ -53,11 +70,11 @@ impl UseSessionState { &self, client: Client, initial_sync_token: Option, - ) -> anyhow::Result<(), anyhow::Error> { + ) -> Result<(), SessionError> { let mut room_event_filter = RoomEventFilter::empty(); room_event_filter.rooms = Some(&[]); - let filter_event_type = vec![EventType::RoomMessage.to_string()]; + let filter_event_type = vec![RoomEventType::RoomMessage.to_string()]; room_event_filter.types = Some(&filter_event_type); let mut filter = FilterDefinition::empty(); @@ -78,8 +95,9 @@ impl UseSessionState { match client.sync_once(sync_settings.clone()).await { Ok(response) => { - Self::whoami(&self, client).await?; - + Self::whoami(&self, client) + .await + .map_err(|_| SessionError::WhoamiFailed)?; Self::persist_sync_token(&response.next_batch).await?; Ok(()) @@ -88,12 +106,17 @@ impl UseSessionState { info!("An error occurred during initial sync: {err}"); info!("Trying again from login…"); - Err(err.into()) + Err(SessionError::SyncFailed) } } } - async fn persist_sync_token(sync_token: &str) -> anyhow::Result<(), Error> { + pub fn persist_session_file(&self, session_file: &str) -> Result<(), SessionError> { + ::set("session_file", session_file) + .map_err(|_| SessionError::SaveFailed) + } + + async fn persist_sync_token(sync_token: &str) -> anyhow::Result<(), SessionError> { let serialized_session: Result = ::get("session_file"); @@ -102,11 +125,14 @@ impl UseSessionState { Err(e) => e.to_string(), }; - let mut full_session: FullSession = serde_json::from_str(&serialized_session)?; + let mut full_session: FullSession = + serde_json::from_str(&serialized_session).map_err(|_| SessionError::GetFailed)?; full_session.sync_token = Some(sync_token.to_owned()); - let serialized_session = serde_json::to_string(&full_session)?; - let _ = ::set("session_file", serialized_session); + let serialized_session = + serde_json::to_string(&full_session).map_err(|_| SessionError::GetFailed)?; + ::set("session_file", serialized_session) + .map_err(|_| SessionError::SaveFailed)?; Ok(()) } diff --git a/src/locales/en-US.json b/src/locales/en-US.json index 94ef496..a1e89cb 100644 --- a/src/locales/en-US.json +++ b/src/locales/en-US.json @@ -94,6 +94,15 @@ "description": "Complete the captcha below", "cta": "Completed" } + }, + "errors": { + "unknown": "Unknown error", + "unsupported_flow": "Unsupported flow", + "key_recaptcha": "ReCaptcha key not found", + "server": "Internal server error", + "flow_not_found": "Verification flow not found", + "register_failed": "Registration not completed, try again or contact support", + "login_failed": "We couldn't redirect you, go to Sign in" } }, "onboard": { @@ -153,8 +162,9 @@ "device_id": "Unexpected error: (Device ID)", "file_type": "Check that the file is a multimedia or document type", "server": "Unexpected error: (Server)", - "sync": "Unexpected error: (Sync)", - "default_server": "Could not connect to the default server" + "sync": "Failed to sync, reload browser", + "default_server": "Could not connect to the default server", + "persist": "Failed to persist session" } }, "listen": { @@ -326,6 +336,12 @@ "verified": { "title": "Verification completed", "description": "You have verified this device." + }, + "errors": { + "flow_not_found": "Verification flow not found", + "sas_accept": "Error accepting verification", + "sas_confirm": "Error confirming verification", + "sas_cancel": "Error canceling verification" } } } diff --git a/src/locales/es-ES.json b/src/locales/es-ES.json index 8d32864..09cc6af 100644 --- a/src/locales/es-ES.json +++ b/src/locales/es-ES.json @@ -94,6 +94,15 @@ "description": "Completa el captcha a continuación", "cta": "Completado" } + }, + "errors": { + "unknown": "Error desconocido", + "unsupported_flow": "Flujo no soportado", + "key_recaptcha": "Llave de Captcha no encontrada", + "server": "Error en el servidor", + "flow_not_found": "Flujo de verificación no encontrado", + "register_failed": "Registro no realizado, intenta de nuevo o contacta con soporte", + "login_failed": "No hemos podido redirigirte, ve a Iniciar sesión" } }, "onboard": { @@ -153,8 +162,9 @@ "device_id": "Error inesperado: (Id de dispositivo)", "file_type": "Verifica que el archivo sea tipo multimedia o documento", "server": "Error inesperado: (Servidor)", - "sync": "Error inesperado: (Sincronización)", - "default_server": "No se ha podido conectar al servidor por defecto" + "sync": "No se ha podido sincronizar, recarga el navegador", + "default_server": "No se ha podido conectar al servidor por defecto", + "persist": "No se ha podido persistir la sesión" } }, "listen": { @@ -326,6 +336,12 @@ "verified": { "title": "Verificación completada", "description": "Haz verificado este dispositivo." + }, + "errors": { + "flow_not_found": "Flujo de verificación no encontrado", + "sas_accept": "Error al aceptar la verificación", + "sas_confirm": "Error al confirmar la verificación", + "sas_cancel": "Error al cancelar la verificación" } } } diff --git a/src/main.rs b/src/main.rs index 704a2c5..b2806ff 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,35 +1,27 @@ #![allow(non_snake_case)] -use chat::components::atoms::{notification, LoadingStatus, Notification, Spinner}; -use chat::hooks::use_auth::use_auth; -use chat::hooks::use_client::use_client; -use chat::hooks::use_init_app::{use_init_app, BeforeSession}; -use chat::hooks::use_notification::{use_notification, NotificationType}; -use chat::hooks::use_session::use_session; -use chat::pages::login::{LoggedIn, Login}; -use chat::pages::route::Route; -use chat::pages::signup::Signup; -use chat::utils::get_homeserver::{Homeserver, HomeserverError}; -use chat::utils::{get_element, get_homeserver}; -use chat::MatrixClientState; use dioxus::prelude::*; use dioxus_router::prelude::Router; +use dioxus_std::{i18n::*, translate}; +use futures_util::TryFutureExt; use gloo::storage::errors::StorageError; use gloo::storage::LocalStorage; -use log::LevelFilter; -use chat::services::matrix::matrix::*; -use dioxus_std::{i18n::*, translate}; -use futures_util::TryFutureExt; -use matrix_sdk::config::SyncSettings; -use matrix_sdk::ruma::exports::serde_json; -use matrix_sdk::Client; -use ruma::api::client::filter::{Filter, FilterDefinition, RoomEventFilter, RoomFilter}; -use ruma::api::client::sync::sync_events; -use ruma::events::EventType; use std::str::FromStr; use unic_langid::LanguageIdentifier; use web_sys::window; +use chat::components::atoms::{LoadingStatus, Notification, Spinner}; +use chat::hooks::use_auth::use_auth; +use chat::hooks::use_client::use_client; +use chat::hooks::use_init_app::{use_init_app, BeforeSession}; +use chat::hooks::use_notification::{use_notification, NotificationType}; +use chat::hooks::use_session::use_session; +use chat::pages::login::Login; +use chat::pages::route::Route; +use chat::pages::signup::Signup; +use chat::services::matrix::matrix::*; +use chat::MatrixClientState; + fn main() { wasm_logger::init(wasm_logger::Config::default()); dioxus_web::launch(App); @@ -38,13 +30,19 @@ fn main() { static EN_US: &str = include_str!("./locales/en-US.json"); static ES_ES: &str = include_str!("./locales/es-ES.json"); +pub enum MainError { + DefaultServer, + RestoreFailed, + SyncFailed, +} + fn App(cx: Scope) -> Element { if let Some(static_login_form) = window()?.document()?.get_element_by_id("static-login-form") { if let Some(parent) = static_login_form.parent_node() { - parent.remove_child(&static_login_form); + let _ = parent.remove_child(&static_login_form); }; }; - + let navigator_language = window() .expect("window") .navigator() @@ -84,8 +82,6 @@ fn App(cx: Scope) -> Element { let key_chat_common_error_sync = translate!(i18, "chat.common.error.sync"); let key_chat_common_error_default_server = translate!(i18, "chat.common.error.default_server"); - let key_main_error_homeserver_invalid_url = - translate!(i18, "main.errors.homeserver.invalid_url"); let key_main_error_restore = translate!(i18, "main.errors.restore"); let restoring_session = use_ref::(cx, || true); @@ -94,54 +90,46 @@ fn App(cx: Scope) -> Element { to_owned![client, auth, restoring_session, session, notification]; async move { - let homeserver = Homeserver::new().map_err(|e| match e { - HomeserverError::InvalidUrl => key_main_error_homeserver_invalid_url, - })?; - - let c = match create_client(&homeserver.get_base_url()).await { - Ok(c) => c, - Err(_) => create_client(&Homeserver::default().get_base_url()) - .await - .map_err(|_| { - format!( - "{} {}", - key_chat_common_error_default_server, - homeserver.get_base_url() - ) - })?, - }; - - client.set(MatrixClientState { client: Some(c) }); - let serialized_session: Result = ::get("session_file"); - let Ok(s) = serialized_session else { - restoring_session.set(false); - return Ok(()); - }; + match serialized_session { + Ok(s) => { + let (c, sync_token) = restore_session(&s) + .await + .map_err(|_| MainError::RestoreFailed)?; - let (c, sync_token) = restore_session(&s) - .await - .map_err(|_| key_main_error_restore)?; + client.set(MatrixClientState { + client: Some(c.clone()), + }); - client.set(MatrixClientState { - client: Some(c.clone()), - }); + session + .sync(c.clone(), sync_token) + .await + .map_err(|_| MainError::SyncFailed)?; - session - .sync(c.clone(), sync_token) - .await - .map_err(|_| key_chat_common_error_sync)?; - - auth.set_logged_in(true); + auth.set_logged_in(true); + } + Err(_) => { + client + .default() + .await + .map_err(|_| MainError::DefaultServer)?; + } + } + restoring_session.set(false); restoring_session.set(false); - Ok::<(), String>(()) + Ok::<(), MainError>(()) } - .unwrap_or_else(move |e: String| { - notification.handle_error(&e); + .unwrap_or_else(move |e: MainError| { + let message = match e { + MainError::DefaultServer => &key_chat_common_error_default_server, + MainError::RestoreFailed => &key_main_error_restore, + MainError::SyncFailed => &key_chat_common_error_sync, + }; + notification.handle_error(&message); }) }); @@ -156,10 +144,9 @@ fn App(cx: Scope) -> Element { NotificationType::Click => { }, - NotificationType::AcceptSas(sas, redirect) => { + NotificationType::AcceptSas(_, _) => { cx.spawn({ async move { - let x = sas.accept().await; todo!() } }); diff --git a/src/pages/chat/chat_list.rs b/src/pages/chat/chat_list.rs index 7dd4b05..f0cc00a 100644 --- a/src/pages/chat/chat_list.rs +++ b/src/pages/chat/chat_list.rs @@ -1,7 +1,6 @@ use std::collections::HashMap; use dioxus::prelude::*; -use dioxus_router::prelude::use_navigator; use dioxus_std::{i18n::use_i18, translate}; use crate::{ @@ -17,13 +16,8 @@ use crate::{ organisms::{chat::ActiveRoom, main::TitleHeaderMain}, }, hooks::{ - use_client::use_client, - use_messages::use_messages, - use_notification::{ - use_notification, NotificationHandle, NotificationItem, NotificationType, - }, - use_room::use_room, - use_session::use_session, + use_client::use_client, use_messages::use_messages, use_notification::use_notification, + use_room::use_room, use_session::use_session, }, services::matrix::matrix::{list_rooms_and_spaces, Conversations}, }; @@ -31,7 +25,6 @@ use crate::{ #[inline_props] pub fn ChatList(cx: Scope) -> Element { let i18 = use_i18(cx); - let nav = use_navigator(cx); let client = use_client(cx).get(); let session = use_session(cx); let notification = use_notification(cx); @@ -190,7 +183,6 @@ pub fn ChatList(cx: Scope) -> Element { } if !room.get().name.is_empty() { - let room_tabs = room_tabs.read().clone(); rsx!( section { class: "chat-list__active-room", diff --git a/src/pages/chat/chat_room.rs b/src/pages/chat/chat_room.rs index ac95d71..541754e 100644 --- a/src/pages/chat/chat_room.rs +++ b/src/pages/chat/chat_room.rs @@ -2,9 +2,9 @@ use dioxus::prelude::*; use crate::{components::organisms::chat::ActiveRoom, hooks::use_messages::use_messages}; -// The name prop comes from the /:name route segment #[inline_props] pub fn ChatRoom(cx: Scope, name: String) -> Element { + let _ = &name; let messages = use_messages(cx); use_coroutine(cx, |_: UnboundedReceiver| { diff --git a/src/pages/chat/room/group.rs b/src/pages/chat/room/group.rs index 4214c20..2534cbb 100644 --- a/src/pages/chat/room/group.rs +++ b/src/pages/chat/room/group.rs @@ -54,17 +54,10 @@ pub fn RoomGroup(cx: Scope) -> Element { let i18 = use_i18(cx); - let key_common_error_thread_id = translate!(i18, "chat.common.error.thread_id"); - let key_common_error_event_id = translate!(i18, "chat.common.error.event_id"); - let key_common_error_room_id = translate!(i18, "chat.common.error.room_id"); let key_common_error_user_id = translate!(i18, "chat.common.error.user_id"); let key_common_error_server = translate!(i18, "chat.common.error.server"); - let key_group_error_not_found = translate!(i18, "group.error.not_found"); - let key_group_error_dm = translate!(i18, "group.error.dm"); let key_group_error_profile = translate!(i18, "group.error.profile"); - let key_group_error_file = translate!(i18, "group.error.file"); - let key_group_success_description = translate!(i18, "group.success.description"); let key_group_title = "group-title"; let key_group_select_label = "group-select-label"; @@ -124,7 +117,6 @@ pub fn RoomGroup(cx: Scope) -> Element { let users = use_ref::>(cx, || vec![]); let error = use_state::>(cx, || None); - let error_creation = use_state::>(cx, || None); let handle_complete_group = use_ref::(cx, || false); let group_name = use_state::(cx, || String::from("")); @@ -140,7 +132,7 @@ pub fn RoomGroup(cx: Scope) -> Element { if let None = element { match process_find_user_by_id(&id, &client).await { Ok(profile) => users.with_mut(|user| user.push(profile)), - Err(err) => { + Err(_) => { notification.handle_error(&key_group_error_not_found); } } @@ -156,13 +148,13 @@ pub fn RoomGroup(cx: Scope) -> Element { selected_users, attach, group_name, - error_creation, + navigation, key_common_error_user_id, key_group_error_profile, key_group_error_not_found, key_common_error_server, - key_group_success_description, + notification, status, room @@ -248,7 +240,7 @@ pub fn RoomGroup(cx: Scope) -> Element { let infered_type = infer::get(content.deref()).ok_or(AttachError::UncoverType)?; let content_type: Result = infered_type.mime_type().parse(); - let content_type = content_type.map_err(|e| AttachError::UnknownContent)?; + let content_type = content_type.map_err(|_| AttachError::UnknownContent)?; let blob = match content_type.type_() { mime::IMAGE => gloo::file::Blob::new(content.deref()), diff --git a/src/pages/chat/room/new.rs b/src/pages/chat/room/new.rs index 61e53d2..eae4618 100644 --- a/src/pages/chat/room/new.rs +++ b/src/pages/chat/room/new.rs @@ -12,7 +12,6 @@ use crate::{ }, hooks::{ use_client::use_client, use_notification::use_notification, use_room::use_room, - use_session::use_session, }, pages::chat::room::group::{self, CreateRoomError, Profile}, services::matrix::matrix::create_room, @@ -30,17 +29,11 @@ pub enum CreationStatus { pub fn RoomNew(cx: Scope) -> Element { let i18 = use_i18(cx); - let key_common_error_thread_id = translate!(i18, "chat.common.error.thread_id"); - let key_common_error_event_id = translate!(i18, "chat.common.error.event_id"); - let key_common_error_room_id = translate!(i18, "chat.common.error.room_id"); - let key_dm_error_not_found = translate!(i18, "chat.common.error.user_id"); let key_common_error_user_id = translate!(i18, "chat.common.error.user_id"); let key_common_error_server = translate!(i18, "chat.common.error.server"); let key_dm_error_not_found = translate!(i18, "dm.error.not_found"); - let key_dm_error_dm = translate!(i18, "dm.error.dm"); let key_dm_error_profile = translate!(i18, "dm.error.profile"); - let key_dm_error_file = translate!(i18, "dm.error.file"); let key_dm_title = "dm-title"; let key_dm_label = "dm-label"; @@ -58,12 +51,10 @@ pub fn RoomNew(cx: Scope) -> Element { let client = use_client(cx); let notification = use_notification(cx); let room = use_room(cx); - let session = use_session(cx); let user_id = use_state::(cx, || String::from("")); let user = use_state::>(cx, || None); let error_field = use_state::>(cx, || None); - let error_creation = use_state::>(cx, || None); let status = use_state::(cx, || CreationStatus::Start); let task_search_user = use_coroutine(cx, |mut rx: UnboundedReceiver| { @@ -73,7 +64,7 @@ pub fn RoomNew(cx: Scope) -> Element { while let Some(id) = rx.next().await { match group::process_find_user_by_id(&id, &client).await { Ok(profile) => user.set(Some(profile)), - Err(err) => { + Err(_) => { notification.handle_error(&key_dm_error_not_found); } } @@ -86,7 +77,6 @@ pub fn RoomNew(cx: Scope) -> Element { to_owned![ client, user_id, - error_creation, navigation, room, user, @@ -95,7 +85,6 @@ pub fn RoomNew(cx: Scope) -> Element { key_dm_error_not_found, key_common_error_server, notification, - session, status ]; diff --git a/src/pages/login.rs b/src/pages/login.rs index 31de900..6361cb0 100644 --- a/src/pages/login.rs +++ b/src/pages/login.rs @@ -1,8 +1,7 @@ -use std::{collections::HashMap, rc::Rc}; - use dioxus::{html::input_data::keyboard_types, prelude::*}; use dioxus_std::{translate, i18n::use_i18}; -use gloo::storage::LocalStorage; +use std::{collections::HashMap, rc::Rc}; + use crate::{ components::{ atoms::{MessageInput, input::InputType}, @@ -37,7 +36,6 @@ impl LoggedInStatus { LoggedInStatus::Done | LoggedInStatus::Persisting | LoggedInStatus::LoggedAs(_) => true, - _ => false } } @@ -59,8 +57,10 @@ enum LoginFrom { pub fn Login(cx: Scope) -> Element { let i18 = use_i18(cx); - - let key_chat_errors_not_found = translate!(i18, "login.chat_errors.not_found"); + + let key_chat_common_error_sync = translate!(i18, "chat.common.error.sync"); + let key_chat_common_error_persist = translate!(i18, "chat.common.error.persist"); + let key_login_chat_errors_invalid_server = translate!(i18, "login.chat_errors.invalid_server"); let key_login_unlock_title = translate!(i18, "login.unlock.title"); let key_login_unlock_description = translate!(i18, "login.unlock.description"); @@ -69,18 +69,13 @@ pub fn Login(cx: Scope) -> Element { let key_login_chat_credentials_description = "login-chat-credentials-description"; let key_login_chat_credentials_title = "login-chat-credentials-title"; - let key_login_chat_credentials_username_message = "login-chat-credentials-username-message"; let key_login_chat_credentials_username_placeholder = "login-chat-credentials-username-placeholder"; - - let key_login_chat_credentials_password_message = "login-chat-credentials-password-message"; let key_login_chat_credentials_password_placeholder = "login-chat-credentials-password-placeholder"; - let key_login_chat_credentials_cta = "login-chat-credentials-cta"; let key_login_chat_messages_validating = "login-chat-messages-validating"; let key_login_chat_messages_welcome = "login-chat-messages-welcome"; - let key_login_chat_errors_invalid_url = "login-chat-errors-invalid-url"; let key_login_chat_errors_unknown = "login-chat-errors-unknown"; let key_login_chat_errors_invalid_username_password = "login-chat-errors-invalid-username-password"; @@ -94,17 +89,14 @@ pub fn Login(cx: Scope) -> Element { (key_login_chat_credentials_description, translate!(i18, "login.chat_steps.credentials.description")), - (key_login_chat_credentials_username_message, translate!(i18, "login.chat_steps.credentials.username.message")), (key_login_chat_credentials_username_placeholder, translate!(i18, "login.chat_steps.credentials.username.placeholder")), - (key_login_chat_credentials_password_message, translate!(i18, "login.chat_steps.credentials.password.message")), (key_login_chat_credentials_password_placeholder, translate!(i18, "login.chat_steps.credentials.password.placeholder")), (key_login_chat_credentials_cta, translate!(i18, "login.chat_steps.credentials.cta")), (key_login_chat_messages_validating, translate!(i18, "login.chat_steps.messages.validating")), (key_login_chat_messages_welcome, translate!(i18, "login.chat_steps.messages.welcome")), - (key_login_chat_errors_invalid_url, translate!(i18, "login.chat_errors.invalid_url")), (key_login_chat_errors_unknown, translate!(i18, "login.chat_errors.unknown")), (key_login_chat_errors_invalid_username_password, translate!(i18, "login.chat_errors.invalid_username_password")), ]); @@ -151,7 +143,7 @@ pub fn Login(cx: Scope) -> Element { let on_handle_login = Rc::new(move || { cx.spawn({ - to_owned![auth, session, username, password, is_loading_loggedin, client, error, error_invalid_credentials, error_unknown, homeserver, notification, key_login_chat_errors_invalid_server]; + to_owned![auth, session, username, password, is_loading_loggedin, client, error, error_invalid_credentials, error_unknown, homeserver, notification, key_login_chat_errors_invalid_server, key_chat_common_error_persist, key_chat_common_error_sync]; async move { is_loading_loggedin.set(LoggedInStatus::Loading); @@ -163,8 +155,10 @@ pub fn Login(cx: Scope) -> Element { is_loading_loggedin.set(LoggedInStatus::Start); return; }; - }else { - auth.set_server(homeserver.get()).await; + } else { + if let Err(e) = auth.set_server(homeserver.get()).await { + log::warn!("Failed to set server: {e:?}"); + } } auth.set_username(username.get(), true); @@ -186,36 +180,31 @@ pub fn Login(cx: Scope) -> Element { .await; match response { - Ok((c, serialized_session)) => { + Ok((c, serialized_session)) => { is_loading_loggedin.set(LoggedInStatus::Done); let display_name = c.account().get_display_name().await.ok().flatten(); - ::set( - "session_file", - serialized_session, - ); + if let Err(_) = session.persist_session_file(&serialized_session) { + notification.handle_error(&key_chat_common_error_persist); + }; is_loading_loggedin.set(LoggedInStatus::Persisting); - session.sync(c.clone(), None).await; - - client.set(crate::MatrixClientState { client: Some(c.clone()) }); - - let Some(user_id) = c.user_id() else { - return notification.handle_error("{key_chat_errors_not_found}"); + if let Err(_) = session.sync(c.clone(), None).await { + notification.handle_error(&key_chat_common_error_sync); }; - is_loading_loggedin.set(LoggedInStatus::LoggedAs(user_id.to_string())); - - auth.set_logged_in(true); + client.set(crate::MatrixClientState { client: Some(c.clone()) }); - auth.persist_data(CacheLogin { + if let Err(_) = auth.persist_data(CacheLogin { server: homeserver.get().to_string(), username: username.get().to_string(), display_name - }); - + }) { + notification.handle_error(&key_chat_common_error_persist); + }; + auth.set_logged_in(true); } Err(err) => { is_loading_loggedin.set(LoggedInStatus::Start); @@ -231,7 +220,7 @@ pub fn Login(cx: Scope) -> Element { username.set(String::new()); password.set(String::new()); - auth.reset() + auth.reset(); } } } @@ -261,7 +250,9 @@ pub fn Login(cx: Scope) -> Element { homeserver.set(data.server.clone()); username.set(data.username.clone()); - auth.set_server(&data.server).await; + if let Err(e) = auth.set_server(homeserver.get()).await { + log::warn!("Failed to set server: {e:?}"); + } auth.set_username(&data.username, true); } } diff --git a/src/pages/profile/profile.rs b/src/pages/profile/profile.rs index 140200d..6ec2487 100644 --- a/src/pages/profile/profile.rs +++ b/src/pages/profile/profile.rs @@ -51,9 +51,6 @@ pub enum ProfileError { pub fn Profile(cx: Scope) -> Element { let i18 = use_i18(cx); - let key_common_error_thread_id = translate!(i18, "chat.common.error.thread_id"); - let key_common_error_event_id = translate!(i18, "chat.common.error.event_id"); - let key_common_error_room_id = translate!(i18, "chat.common.error.room_id"); let key_common_error_user_id = translate!(i18, "chat.common.error.user_id"); let key_common_error_device_id = translate!(i18, "chat.common.error.device_id"); let key_common_error_server = translate!(i18, "chat.common.error.server"); @@ -62,7 +59,6 @@ pub fn Profile(cx: Scope) -> Element { let key_profile_error_not_found = translate!(i18, "profile.error.not_found"); let key_profile_error_profile = translate!(i18, "profile.error.profile"); - let key_profile_error_file = translate!(i18, "profile.error.file"); let key_input_message_unknown_content = translate!(i18, "chat.input_message.unknown_content"); let key_input_message_file_type = translate!(i18, "chat.input_message.file_type"); @@ -157,7 +153,6 @@ pub fn Profile(cx: Scope) -> Element { current_profile.set(original_profile.read().deref().clone()); - let homeserver = client.homeserver().await; let user_id = client.user_id().ok_or(ProfileError::InvalidUserId)?; let device_id = client @@ -221,7 +216,7 @@ pub fn Profile(cx: Scope) -> Element { let infered_type = infer::get(content.deref()).ok_or(AttachError::UncoverType)?; let content_type: Result = infered_type.mime_type().parse(); - let content_type = content_type.map_err(|e| AttachError::UnknownContent)?; + let content_type = content_type.map_err(|_| AttachError::UnknownContent)?; let blob = match content_type.type_() { mime::IMAGE => gloo::file::Blob::new(content.deref()), diff --git a/src/pages/profile/verify.rs b/src/pages/profile/verify.rs index 09fabb7..1deecf6 100644 --- a/src/pages/profile/verify.rs +++ b/src/pages/profile/verify.rs @@ -3,7 +3,7 @@ use dioxus_std::{i18n::use_i18, translate}; use log::info; use matrix_sdk::encryption::verification::{SasVerification, Verification}; -use crate::{components::atoms::Button, hooks::use_client::use_client}; +use crate::{components::atoms::Button, hooks::{use_client::use_client, use_notification::use_notification}}; use futures_util::StreamExt; @@ -22,9 +22,25 @@ use matrix_sdk::{ Client, }; +pub enum VerificationError { + FlowNotFound, + SasAcceptFailed, + SasConfirmFailed, + SasCancelFailed, + SyncFailed +} + #[inline_props] pub fn Verify(cx: Scope, id: String) -> Element { + let _ = &id; let i18 = use_i18(cx); + let notification = use_notification(cx); + + let key_verify_error_flow_not_found = translate!(i18, "verify.errors.flow_not_found"); + let key_verify_error_sas_accept = translate!(i18, "verify.errors.sas_accept"); + let key_verify_error_sas_confirm = translate!(i18, "verify.errors.sas_confirm"); + let key_verify_error_sas_cancel = translate!(i18, "verify.errors.sas_cancel"); + let key_chat_common_error_sync = translate!(i18, "verify.errors.sas_sync"); let key_verify_unverified_cta_match = translate!(i18, "verify.unverified.cta_match"); let key_verify_unverified_cta_disagree = translate!(i18, "verify.unverified.cta_disagree"); @@ -46,12 +62,47 @@ pub fn Verify(cx: Scope, id: String) -> Element { }) .clone(); - use_coroutine(cx, |mut rx: UnboundedReceiver| { - to_owned![task_wait_confirmation, client, is_verified]; + let task_verify = use_coroutine(cx, |mut rx: UnboundedReceiver| { + to_owned![is_verified]; + + async move { + while let Some(verify) = rx.next().await { + is_verified.set(verify); + } + } + }); + + let task_handle_error = use_coroutine(cx, |mut rx: UnboundedReceiver| { + to_owned![notification, key_verify_error_flow_not_found, key_verify_error_sas_accept, key_verify_error_sas_confirm, key_verify_error_sas_cancel, key_chat_common_error_sync]; + + async move { + while let Some(e) = rx.next().await { + let message = match e { + VerificationError::FlowNotFound => &key_verify_error_flow_not_found, + VerificationError::SasAcceptFailed => &key_verify_error_sas_accept, + VerificationError::SasConfirmFailed => &key_verify_error_sas_confirm, + VerificationError::SasCancelFailed => &key_verify_error_sas_cancel, + VerificationError::SyncFailed => &key_chat_common_error_sync + }; + notification.handle_error(&message); + } + } + }); + + let task_handle_error_a = task_handle_error.clone(); + let task_handle_error_b = task_handle_error.clone(); + let task_handle_error_c = task_handle_error.clone(); + let task_handle_error_d = task_handle_error.clone(); + let task_verify_to_device = task_verify.clone(); + + use_coroutine(cx, |mut _rx: UnboundedReceiver<()>| { + to_owned![task_wait_confirmation, client, task_verify, task_handle_error, task_handle_error_a, task_handle_error_b, task_handle_error_c, task_handle_error_d]; async move { client.add_event_handler( - |ev: ToDeviceKeyVerificationRequestEvent, client: Client| async move { + move |ev: ToDeviceKeyVerificationRequestEvent, client: Client| { + let task_handle_error_a = task_handle_error_a.clone(); + async move { info!("here ToDeviceKeyVerificationRequestEvent"); let request = client .encryption() @@ -59,34 +110,35 @@ pub fn Verify(cx: Scope, id: String) -> Element { .await .expect("Request object wasn't created"); - request - .accept() - .await - .expect("Can't accept verification request"); - }, + if let Err(_) = request.accept().await { + task_handle_error_a.send(VerificationError::SasAcceptFailed) + }; + }}, ); client.add_event_handler( - |ev: ToDeviceKeyVerificationStartEvent, client: Client| async move { - if let Some(Verification::SasV1(sas)) = client - .encryption() - .get_verification(&ev.sender, ev.content.transaction_id.as_str()) - .await - { - info!( - "ToDeviceKeyVerificationStartEvent Starting verification with {} {}", - &sas.other_device().user_id(), - &sas.other_device().device_id() - ); - // print_devices(&ev.sender, &client).await; - sas.accept().await; + move |ev: ToDeviceKeyVerificationStartEvent, client: Client| { + let task_handle_error_b = task_handle_error_b.clone(); + async move { + if let Some(Verification::SasV1(sas)) = client + .encryption() + .get_verification(&ev.sender, ev.content.transaction_id.as_str()) + .await + { + info!( + "ToDeviceKeyVerificationStartEvent Starting verification with {} {}", + &sas.other_device().user_id(), + &sas.other_device().device_id() + ); + if let Err(_) = sas.accept().await { + task_handle_error_b.send(VerificationError::SasAcceptFailed); + }; + } } - }, + } ); client.add_event_handler(move |ev: ToDeviceKeyVerificationKeyEvent, client: Client| { - // let task_wait_confirmation = task_wait_confirmation.clone(); - to_owned![task_wait_confirmation]; async move { @@ -96,16 +148,13 @@ pub fn Verify(cx: Scope, id: String) -> Element { .await { task_wait_confirmation.send(sas); - // emoji.set(Some(sas)); } } }); - let x = is_verified.clone(); client.add_event_handler( move |ev: ToDeviceKeyVerificationDoneEvent, client: Client| { - to_owned![x]; - + let task_verify = task_verify_to_device.clone(); async move { if let Some(Verification::SasV1(sas)) = client .encryption() @@ -113,8 +162,7 @@ pub fn Verify(cx: Scope, id: String) -> Element { .await { if sas.is_done() { - x.set(true); - // print_devices(&ev.sender, &client).await; + task_verify.send(true); } } } @@ -122,59 +170,62 @@ pub fn Verify(cx: Scope, id: String) -> Element { ); client.add_event_handler( - |ev: OriginalSyncRoomMessageEvent, client: Client| async move { - info!("here OriginalSyncRoomMessageEvent"); - - if let MessageType::VerificationRequest(_) = &ev.content.msgtype { - let request = client - .encryption() - .get_verification_request(&ev.sender, &ev.event_id) - .await - .expect("Request object wasn't created"); - - request - .accept() - .await - .expect("Can't accept verification request"); + move |ev: OriginalSyncKeyVerificationStartEvent, client: Client| { + let task_handle_error_c = task_handle_error_c.clone(); + async move { + if let Some(Verification::SasV1(sas)) = client + .encryption() + .get_verification(&ev.sender, ev.content.relates_to.event_id.as_str()) + .await + { + info!( + "OriginalSyncKeyVerificationStartEvent Starting verification with {} {}", + &sas.other_device().user_id(), + &sas.other_device().device_id() + ); + if let Err(_) = sas.accept().await { + task_handle_error_c.send(VerificationError::SasAcceptFailed) + }; } - }, + }}, ); client.add_event_handler( - |ev: OriginalSyncKeyVerificationStartEvent, client: Client| async move { - if let Some(Verification::SasV1(sas)) = client - .encryption() - .get_verification(&ev.sender, ev.content.relates_to.event_id.as_str()) - .await - { - info!( - "OriginalSyncKeyVerificationStartEvent Starting verification with {} {}", - &sas.other_device().user_id(), - &sas.other_device().device_id() - ); - // print_devices(&ev.sender, &client).await; - sas.accept().await; + move |ev: OriginalSyncRoomMessageEvent, client: Client| { + let task_handle_error_d = task_handle_error_d.clone(); + async move { + info!("here OriginalSyncRoomMessageEvent"); + + if let MessageType::VerificationRequest(_) = &ev.content.msgtype { + let request = client + .encryption() + .get_verification_request(&ev.sender, &ev.event_id) + .await + .expect("Request object wasn't created"); + + if let Err(_) = request.accept().await { + task_handle_error_d.send(VerificationError::SasAcceptFailed); + }; } - }, - ); + } + }, + ); client.add_event_handler( |ev: OriginalSyncKeyVerificationKeyEvent, client: Client| async move { - if let Some(Verification::SasV1(sas)) = client + if let Some(Verification::SasV1(_)) = client .encryption() .get_verification(&ev.sender, ev.content.relates_to.event_id.as_str()) .await { info!("here OriginalSyncKeyVerificationKeyEvent this function need task_wait_confirmation"); - // task_wait_confirmation.send(sas); } }, ); client.add_event_handler( move |ev: OriginalSyncKeyVerificationDoneEvent, client: Client| { - to_owned![is_verified]; - + let task_verify = task_verify.clone(); async move { if let Some(Verification::SasV1(sas)) = client .encryption() @@ -182,29 +233,30 @@ pub fn Verify(cx: Scope, id: String) -> Element { .await { if sas.is_done() { - is_verified.set(true); - // print_devices(&ev.sender, &client).await; + task_verify.send(true); } } } }, ); - client.sync(SyncSettings::new()).await; + if let Err(_) = client.sync(SyncSettings::new()).await { + task_handle_error.send(VerificationError::SyncFailed) + }; } - // } }); let on_handle_confirm = move |sas: SasVerification| { - to_owned![is_verified, emoji]; + to_owned![is_verified, emoji, task_handle_error]; cx.spawn({ let sas = sas.clone(); - let client = client.clone(); let is_verified = is_verified.clone(); async move { - sas.confirm().await; + if let Err(_) = sas.confirm().await { + task_handle_error.send(VerificationError::SasConfirmFailed) + }; if sas.is_done() { is_verified.set(true); @@ -216,13 +268,15 @@ pub fn Verify(cx: Scope, id: String) -> Element { }; let on_handle_cancel = move |sas: SasVerification| { - to_owned![emoji, is_verified]; + to_owned![emoji, is_verified, task_handle_error]; cx.spawn({ let sas = sas.clone(); async move { - sas.cancel().await; + if let Err(_) = sas.cancel().await { + task_handle_error.send(VerificationError::SasCancelFailed) + }; if sas.is_cancelled() { is_verified.set(false); diff --git a/src/pages/signup.rs b/src/pages/signup.rs index 1572c37..0b30475 100644 --- a/src/pages/signup.rs +++ b/src/pages/signup.rs @@ -2,6 +2,7 @@ use std::{collections::HashMap, ops::Deref}; use dioxus::{html::input_data::keyboard_types, prelude::*}; use dioxus_std::{i18n::use_i18, translate}; +use futures_util::TryFutureExt; use gloo::storage::{LocalStorage, Storage}; use log::info; use matrix_sdk::{Error, HttpError}; @@ -141,7 +142,9 @@ pub fn Signup(cx: Scope) -> Element { to_owned![homeserver, auth]; async move { - auth.set_server(homeserver.get()).await; + if let Err(e) = auth.set_server(homeserver.get()).await { + log::warn!("Failed to set server: {e:?}"); + } } }) }; @@ -167,20 +170,35 @@ pub fn Signup(cx: Scope) -> Element { auth, username, password, - client, error, flows, session_ref, - homeserver + homeserver, + i18 ]; + let auth_error = auth.clone(); + let homeserver_error = homeserver.clone(); + let username_error = username.clone(); + let password_error = password.clone(); + + let key_presignup_error_unknown = translate!(i18, "signup.errors.unknown"); + let key_presignup_error_unsupported_flow = + translate!(i18, "signup.errors.unsupported_flow"); + let key_presignup_error_key_recaptcha = translate!(i18, "signup.errors.key_recaptcha"); + let key_presignup_error_server = translate!(i18, "signup.errors.server"); + let key_presignup_error_flow_not_found = + translate!(i18, "signup.errors.flow_not_found"); + let key_presignup_error_register_failed = + translate!(i18, "signup.errors.register_failed"); + let key_presignup_error_login_failed = translate!(i18, "signup.errors.login_failed"); + + let key_chat_common_error_persist = translate!(i18, "chat.common.error.persist"); + let key_chat_common_error_sync = translate!(i18, "chat.common.error.sync"); + async move { - let login_config = auth.build(); + let info = auth.clone().build().map_err(|_| SignupError::Server)?; - let Ok(info) = login_config else { - reset_login_info(&auth, &homeserver, &username, &password); - return; - }; let response = prepare_register(info.server.as_str(), &info.username, &info.password).await; @@ -194,14 +212,38 @@ pub fn Signup(cx: Scope) -> Element { &username, &password, &session_ref, - &error, &f_error, &flows, - ) + )?; + } else { + return Err(SignupError::Unknown); } info!("response {response:?}"); + Ok::<(), SignupError>(()) } + .unwrap_or_else(move |e: SignupError| { + let message_error = match e { + SignupError::Unknown => key_presignup_error_unknown, + SignupError::UnsupportedFlow => key_presignup_error_unsupported_flow, + SignupError::KeyRecaptcha => key_presignup_error_key_recaptcha, + SignupError::Server => key_presignup_error_server, + SignupError::FlowNotFound => key_presignup_error_flow_not_found, + SignupError::RegisterFailed => key_presignup_error_register_failed, + SignupError::LoginFailed => key_presignup_error_login_failed, + SignupError::SyncFailed => key_chat_common_error_sync, + SignupError::SessionFile => key_chat_common_error_persist, + SignupError::SetSiteKey => key_presignup_error_key_recaptcha, + }; + reset_login_info( + &auth_error, + &homeserver_error, + &username_error, + &password_error, + ); + + error.set(Some(message_error)) + }) }) }; @@ -210,30 +252,41 @@ pub fn Signup(cx: Scope) -> Element { to_owned![ auth, client, - error, session_ref, is_loading_loggedin, before_session, session, homeserver, username, - password + password, + error, + i18 ]; + let auth_error = auth.clone(); + let homeserver_error = homeserver.clone(); + let username_error = username.clone(); + let password_error = password.clone(); + + let key_signup_error_unknown = translate!(i18, "signup.errors.unknown"); + let key_signup_error_unsupported_flow = + translate!(i18, "signup.errors.unsupported_flow"); + let key_signup_error_key_recaptcha = translate!(i18, "signup.errors.key_recaptcha"); + let key_signup_error_server = translate!(i18, "signup.errors.server"); + let key_signup_error_flow_not_found = translate!(i18, "signup.errors.flow_not_found"); + let key_signup_error_register_failed = translate!(i18, "signup.errors.register_failed"); + let key_signup_error_login_failed = translate!(i18, "signup.errors.login_failed"); + let key_chat_common_error_persist = translate!(i18, "chat.common.error.persist"); + let key_chat_common_error_sync = translate!(i18, "chat.common.error.sync"); + async move { - let recaptcha_token = ::get("recaptcha"); + let token = ::get::("recaptcha") + .map_err(|_| SignupError::KeyRecaptcha)?; let session_id = session_ref.read().clone(); - let Ok(token) = recaptcha_token else { - info!("token not found"); - return; - }; - let Ok(info) = auth.build() else { - reset_login_info(&auth, &homeserver, &username, &password); - return; - }; + let info = auth.build().map_err(|_| SignupError::RegisterFailed)?; - let response = register( + register( &info.server.to_string(), &info.username, &info.password, @@ -241,32 +294,63 @@ pub fn Signup(cx: Scope) -> Element { session_id, ) .await - .expect("TODO: handle failed registration"); - - let Ok((c, serialized_session)) = - login(info.server.as_str(), &info.username, &info.password).await - else { - is_loading_loggedin.set(LoggedInStatus::Start); - *before_session.write() = BeforeSession::Login; - return; - }; + .map_err(|e| { + log::info!("{:?}", e); + SignupError::RegisterFailed + })?; + + let (c, serialized_session) = + login(info.server.as_str(), &info.username, &info.password) + .await + .map_err(|_| { + is_loading_loggedin.set(LoggedInStatus::Start); + *before_session.write() = BeforeSession::Login; + + SignupError::LoginFailed + })?; is_loading_loggedin.set(LoggedInStatus::Done); - ::set("session_file", serialized_session); + ::set("session_file", serialized_session) + .map_err(|_| SignupError::SessionFile)?; is_loading_loggedin.set(LoggedInStatus::Persisting); - session.sync(c.clone(), None).await; + session + .sync(c.clone(), None) + .await + .map_err(|_| SignupError::SyncFailed)?; client.set(crate::MatrixClientState { client: Some(c.clone()), }); - is_loading_loggedin.set(LoggedInStatus::LoggedAs(c.user_id().unwrap().to_string())); - auth.set_logged_in(true); + + Ok::<(), SignupError>(()) } + .unwrap_or_else(move |e: SignupError| { + let message_error = match e { + SignupError::Unknown => key_signup_error_unknown, + SignupError::UnsupportedFlow => key_signup_error_unsupported_flow, + SignupError::KeyRecaptcha => key_signup_error_key_recaptcha, + SignupError::Server => key_signup_error_server, + SignupError::FlowNotFound => key_signup_error_flow_not_found, + SignupError::RegisterFailed => key_signup_error_register_failed, + SignupError::LoginFailed => key_signup_error_login_failed, + SignupError::SyncFailed => key_chat_common_error_sync, + SignupError::SessionFile => key_chat_common_error_persist, + SignupError::SetSiteKey => key_signup_error_key_recaptcha, + }; + reset_login_info( + &auth_error, + &homeserver_error, + &username_error, + &password_error, + ); + + error.set(Some(message_error)); + }) }) }; @@ -414,51 +498,67 @@ pub fn Signup(cx: Scope) -> Element { ) } +pub enum SignupError { + UnsupportedFlow, + Unknown, + KeyRecaptcha, + Server, + FlowNotFound, + RegisterFailed, + LoginFailed, + SyncFailed, + SessionFile, + SetSiteKey, +} + fn flow_error( auth: &UseAuthState, homeserver: &UseState, username: &UseState, password: &UseState, session_ref: &UseRef>, - error: &UseState>, f_error: &UiaaResponse, flows: &UseRef>, -) { +) -> Result<(), SignupError> { match f_error { uiaa::UiaaResponse::AuthResponse(uiaa_info) => { let completed = &uiaa_info.completed; let mut flows_to_complete: Vec = vec![]; - uiaa_info.flows[0].stages.iter().for_each(|f| { - if completed.iter().find(|e| *e == f).is_none() { - flows_to_complete.push(f.clone()); - } - session_ref.set(uiaa_info.session.clone()); + let flow = uiaa_info.flows.get(0).ok_or(SignupError::FlowNotFound)?; - match f { - AuthType::ReCaptcha => { + flow.stages + .iter() + .map(|f: &AuthType| -> Result<(), SignupError> { + if completed.iter().find(|e| *e == f).is_none() { + flows_to_complete.push(f.clone()); + } + session_ref.set(uiaa_info.session.clone()); + + if let AuthType::ReCaptcha = f { let params = uiaa_info.params.deref().get(); - let uiaa_response = serde_json::from_str(params); + let uiaa_response = + serde_json::from_str(params).map_err(|_| SignupError::KeyRecaptcha)?; - set_site_key(uiaa_response) - } - _ => { - info!("Unsuported flow"); + set_site_key(uiaa_response)?; + flows.set(flows_to_complete.clone()); + + Ok(()) + } else { + Err(SignupError::UnsupportedFlow) } - } - }); + }) + .collect::>()?; - flows.set(flows_to_complete) + Ok(()) } - uiaa::UiaaResponse::MatrixError(e) => { + uiaa::UiaaResponse::MatrixError(_) => { reset_login_info(&auth, &homeserver, &username, &password); - - error.set(Some(e.message.clone())); + return Err(SignupError::Server); } _ => { reset_login_info(&auth, &homeserver, &username, &password); - - error.set(Some(String::from("Unspecified error"))); + return Err(SignupError::Unknown); } } } @@ -475,17 +575,18 @@ fn reset_login_info( auth.reset(); } -fn set_site_key(uiaa_response: Result, serde_json::Error>) { - match uiaa_response { - Ok(u) => { - let m = u.get("m.login.recaptcha"); +fn set_site_key(uiaa_response: HashMap<&str, Value>) -> Result<(), SignupError> { + let value = uiaa_response + .get("m.login.recaptcha") + .ok_or(SignupError::SetSiteKey)?; + let Value::Object(ref recaptcha) = value else { + return Err(SignupError::SetSiteKey); + }; - if let Some(Value::Object(ref recaptcha)) = m { - if let Some(Value::String(public_key)) = recaptcha.get("public_key") { - gloo::storage::LocalStorage::set("sitekey", public_key.clone()); - } - } - } - Err(_) => todo!(), + let Some(Value::String(public_key)) = recaptcha.get("public_key") else { + return Err(SignupError::SetSiteKey); }; + + gloo::storage::LocalStorage::set("sitekey", public_key.clone()) + .map_err(|_| SignupError::SetSiteKey) } diff --git a/src/services/matrix.rs b/src/services/matrix.rs index a478da7..3a4f6bc 100644 --- a/src/services/matrix.rs +++ b/src/services/matrix.rs @@ -511,7 +511,7 @@ pub mod matrix { lazy_load_options: LazyLoadOptions::Enabled { include_redundant_members: false }, }); let options = assign!(MessagesOptions::backward(), { - limit: UInt::new(20).ok_or(TimelineError::InvalidLimit)?, + limit: UInt::new(limit).ok_or(TimelineError::InvalidLimit)?, filter, from: from.as_deref() }); @@ -1150,11 +1150,10 @@ pub mod matrix { let uiaa_dummy = uiaa::Dummy::new(); request.auth = Some(uiaa::AuthData::Dummy(uiaa_dummy)); - let result = build_client(homeserver, username).await; - let (client, client_session) = match result { - Ok((client, client_session)) => (client, client_session), - Err(_) => panic!("Can't create client"), - }; + // Temporal use of UnknownError + let (client, _) = build_client(homeserver, username) + .await + .map_err(|e| matrix_sdk::Error::UnknownError(e.into()))?; match client.register(request.clone()).await { Ok(info) => { @@ -1181,17 +1180,16 @@ pub mod matrix { request.auth = Some(uiaa::AuthData::ReCaptcha(uiaa_recaptcha)); } - let result = build_client(homeserver, username).await; - let (client, client_session) = match result { - Ok((client, client_session)) => (client, client_session), - Err(_) => panic!("Can't create client"), - }; + // Temporal use of UnknownError + let (client, _) = build_client(homeserver, username) + .await + .map_err(|e| matrix_sdk::Error::UnknownError(e.into()))?; match client.register(request.clone()).await { Ok(info) => { info!("signup result {:?}", info); - client.logout(); + client.logout().await?; Ok((client, "registered".to_string())) } diff --git a/src/utils/vec_to_url.rs b/src/utils/vec_to_url.rs index 2f4e295..d958544 100644 --- a/src/utils/vec_to_url.rs +++ b/src/utils/vec_to_url.rs @@ -6,7 +6,7 @@ use web_sys::Url; pub fn vec_to_url(content: Vec) -> Result { let c = content.deref(); - let mut parts = js_sys::Array::of1(&unsafe { c.into_jsvalue() }); + let parts = js_sys::Array::of1(&unsafe { c.into_jsvalue() }); let blob = web_sys::Blob::new_with_u8_array_sequence(&parts)?; let url = Url::create_object_url_with_blob(&blob)?;