From d9695be1534a2c0674e828f2fcc03a8c548cf194 Mon Sep 17 00:00:00 2001 From: Sam Sartor Date: Sat, 25 Oct 2025 19:03:02 -0600 Subject: [PATCH] proper reactivity on config load --- common/src/lib.rs | 2 +- gui/src/app.rs | 48 ++++++++++++++++++++++-------------------- gui/src/imp/desktop.rs | 6 +++++- gui/src/imp/web.rs | 45 ++++++++++++++++++--------------------- gui/src/lib.rs | 12 ++++++----- 5 files changed, 59 insertions(+), 54 deletions(-) diff --git a/common/src/lib.rs b/common/src/lib.rs index 9cec27f..3c9b4b8 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -1,6 +1,6 @@ use serde::{Deserialize, Serialize}; -#[derive(Clone, Deserialize, Serialize, Default)] +#[derive(Debug, Clone, Deserialize, Serialize, Default)] pub struct GuiConfig { #[serde(default)] pub force_proxy: bool, diff --git a/gui/src/app.rs b/gui/src/app.rs index b36614e..1a0e8c5 100644 --- a/gui/src/app.rs +++ b/gui/src/app.rs @@ -9,7 +9,7 @@ use sir::{css, global_css}; use std::collections::HashMap; use tracing::error; -use crate::{imp, CONFIG}; +use crate::imp; pub type ChannelId = u32; pub type UserId = u32; @@ -26,6 +26,7 @@ pub enum Command { Connect { address: String, username: String, + config: GuiConfig, }, SendChat { markdown: String, @@ -436,9 +437,7 @@ pub fn ChatView() -> Element { //}, #[component] -pub fn ControlView() -> Element { - let config_future = use_resource(|| CONFIG.get()); - +pub fn ControlView(config: Resource) -> Element { let net: Coroutine = use_coroutine_handle(); let status = &STATE.status; let server = STATE.server.read(); @@ -457,7 +456,7 @@ pub fn ControlView() -> Element { let current_channel_name = server.channels[&channel].name.clone(); - let Some(proxy_url) = config_future + let Some(proxy_url) = config .read_unchecked() .as_ref() .and_then(|gui_config| gui_config.proxy_url.clone()) @@ -706,7 +705,7 @@ pub fn ControlView() -> Element { } #[component] -pub fn ServerView() -> Element { +pub fn ServerView(config: Resource) -> Element { let net: Coroutine = use_coroutine_handle(); let server = STATE.server.read(); let Some(&UserState { @@ -797,25 +796,26 @@ pub fn ServerView() -> Element { } div { class: "{control_box}", - ControlView {} + ControlView { config } } } ) } #[component] -pub fn LoginView() -> Element { +pub fn LoginView(config: Resource) -> Element { let net: Coroutine = use_coroutine_handle(); - let config_future = use_resource(|| CONFIG.get()); - - let default_address = &*config_future - .read_unchecked() - .as_ref() - .and_then(|gui_config| gui_config.proxy_url.clone()) - .unwrap_or("".to_string()); - - let mut address = use_signal(|| default_address.to_string()); + let mut address_input = use_signal(|| None::); + let mut address = use_memo(move || { + if let Some(addr) = address_input() { + addr.clone() + } else { + config() + .and_then(|c| c.proxy_url.clone()) + .unwrap_or_default() + } + }); let previous_username = imp::load_username(); let mut username = use_signal(|| previous_username.unwrap_or(String::new())); @@ -869,6 +869,7 @@ pub fn LoginView() -> Element { net.send(Connect { address: address.read().clone(), username: username.read().clone(), + config: config.read().clone().unwrap_or_default(), }) }; let status = &STATE.status; @@ -918,7 +919,7 @@ pub fn LoginView() -> Element { placeholder: "server address", value: "{address.read()}", autofocus: "true", - oninput: move |evt| address.set(evt.value().clone()), + oninput: move |evt| address_input.set(Some(evt.value().clone())), } {bottom} } @@ -927,9 +928,10 @@ pub fn LoginView() -> Element { pub fn app() -> Element { use_coroutine(|rx: UnboundedReceiver| super::network_entrypoint(rx)); - use_future(|| async move { - if let Err(err) = imp::load_config().await { - error!("{}", err) + let config = use_resource(|| async move { + match imp::load_config().await { + Ok(config) => config, + Err(_) => GuiConfig::default(), } }); @@ -1006,8 +1008,8 @@ pub fn app() -> Element { sir::AppStyle { } match *STATE.status.read() { - Connected => rsx!(ServerView {}), - _ => rsx!(LoginView {}), + Connected => rsx!(ServerView { config }), + _ => rsx!(LoginView { config }), } ) } diff --git a/gui/src/imp/desktop.rs b/gui/src/imp/desktop.rs index 9ed2beb..2b517d8 100644 --- a/gui/src/imp/desktop.rs +++ b/gui/src/imp/desktop.rs @@ -17,7 +17,7 @@ use tokio_rustls::rustls::ClientConfig; use tokio_rustls::rustls::DigitallySignedStruct; use tokio_rustls::TlsConnector; use tokio_util::compat::{TokioAsyncReadCompatExt as _, TokioAsyncWriteCompatExt as _}; -use tracing::{error, warn}; +use tracing::{error, info, instrument, warn}; pub use tokio::task::spawn; pub use tokio::time::sleep; @@ -183,11 +183,15 @@ impl ServerCertVerifier for NoCertificateVerification { } } +#[instrument] pub async fn network_connect( address: String, username: String, event_rx: &mut UnboundedReceiver, + gui_config: &GuiConfig, ) -> Result<(), Error> { + info!("connecting"); + let config = ClientConfig::builder() .dangerous() .with_custom_certificate_verifier(Arc::new(NoCertificateVerification)) diff --git a/gui/src/imp/web.rs b/gui/src/imp/web.rs index 4267560..0f71d40 100644 --- a/gui/src/imp/web.rs +++ b/gui/src/imp/web.rs @@ -1,5 +1,4 @@ use crate::app::Command; -use crate::CONFIG; use color_eyre::eyre::{bail, eyre, Error}; use dioxus::prelude::*; use futures::{AsyncRead, AsyncWrite}; @@ -9,7 +8,9 @@ use mumble_protocol::control::{ClientControlCodec, ControlPacket}; use mumble_protocol::voice::{VoicePacket, VoicePacketPayload}; use mumble_protocol::Serverbound; use mumble_web2_common::GuiConfig; +use reqwest::Url; use std::time::Duration; +use tracing::level_filters::LevelFilter; use tracing::{debug, error, info, instrument}; use wasm_bindgen::prelude::*; use wasm_bindgen_futures::JsFuture; @@ -308,8 +309,9 @@ pub async fn network_connect( address: String, username: String, event_rx: &mut UnboundedReceiver, + gui_config: &GuiConfig, ) -> Result<(), Error> { - info!("Rust via WASM!"); + info!("connecting"); let object = web_sys::js_sys::Object::new(); @@ -320,8 +322,7 @@ pub async fn network_connect( ) .ey()?; - if let Some(server_hash) = &CONFIG.try_get().and_then(|cfg| cfg.cert_hash) { - error!("{:?}", server_hash); + if let Some(server_hash) = &gui_config.cert_hash { let hash = web_sys::js_sys::Uint8Array::from(server_hash.as_slice()); web_sys::js_sys::Reflect::set(&object, &"value".into(), &hash).ey()?; } @@ -387,27 +388,20 @@ pub fn load_username() -> Option { .ok()? } -fn load_config_from_window() -> Option { - serde_wasm_bindgen::from_value(Reflect::get(window()?.as_ref(), &"config".into()).ok()?).ok() -} +pub async fn load_config() -> color_eyre::Result { + let config_url = match option_env!("MUMBLE_WEB2_GUI_CONFIG_URL") { + Some(url) => Url::parse(url)?, + None => { + let window: web_sys::Window = web_sys::window().expect("no global `window` exists"); + let location = window.location(); + Url::parse(&location.href().ey()?)?.join("config")? + } + }; + info!("loading config from {}", config_url); -fn load_config_from_env() -> Option { - serde_json::from_str(option_env!("MUMBLE_WEB2_GUI_CONFIG")?).ok()? -} + let config = reqwest::get(config_url).await?.json::().await?; -pub async fn load_config() -> color_eyre::Result<()> { - let config_url = option_env!("MUMBLE_WEB2_GUI_CONFIG_URL").ok_or(eyre!("foo"))?; - - let config = reqwest::get(config_url) - .await - .unwrap() - .json::() - .await - .unwrap(); - - crate::CONFIG.set(config); - - Ok(()) + Ok(config) } pub fn init_logging() { @@ -420,11 +414,14 @@ pub fn init_logging() { let fmt_layer = tracing_subscriber::fmt::layer() .with_ansi(false) // Only partially supported across browsers .without_time() // std::time is not available in browsers - .with_writer(MakeWebConsoleWriter::new()); // write events to the console + .with_writer(MakeWebConsoleWriter::new()) // write events to the console + .with_filter(LevelFilter::DEBUG); let perf_layer = performance_layer().with_details_from_fields(Pretty::default()); tracing_subscriber::registry() .with(fmt_layer) .with(perf_layer) .init(); + + info!("logging initiated"); } diff --git a/gui/src/lib.rs b/gui/src/lib.rs index ae0f7d3..d8150cc 100644 --- a/gui/src/lib.rs +++ b/gui/src/lib.rs @@ -33,18 +33,20 @@ pub mod app; pub mod imp; mod msghtml; -//pub static CONFIG: Lazy = Lazy::new(|| imp::load_config().unwrap_or_default()); -pub static CONFIG: async_cell::sync::AsyncCell = async_cell::sync::AsyncCell::new(); - pub async fn network_entrypoint(mut event_rx: UnboundedReceiver) { loop { - let Some(Command::Connect { address, username }) = event_rx.next().await else { + let Some(Command::Connect { + address, + username, + config, + }) = event_rx.next().await + else { panic!("did not receive connect command") }; *STATE.server.write() = Default::default(); *STATE.status.write() = ConnectionState::Connecting; - if let Err(error) = imp::network_connect(address, username, &mut event_rx).await { + if let Err(error) = imp::network_connect(address, username, &mut event_rx, &config).await { error!("could not connect {:?}", error); *STATE.status.write() = ConnectionState::Failed(error.to_string()); } else {