diff --git a/src/secure_chat/mod.rs b/src/secure_chat/mod.rs index 70a5bf6..11073d3 100644 --- a/src/secure_chat/mod.rs +++ b/src/secure_chat/mod.rs @@ -26,6 +26,8 @@ use crate::serviceclient::ServiceClient; const WINDOW_W: c_int = 960; const WINDOW_H: c_int = 640; +const DEFAULT_WEBHUB_PORT: u16 = 9994; +const DEFAULT_BACKONE_PORT: u16 = 9993; struct SecureChatCtx { client: ServiceClient, @@ -226,7 +228,7 @@ unsafe fn update_core_status() { if c.session.hub_connected { "WebHub connected" } else { - "WebHub disconnected — messages may not send" + "WebHub disconnected — incoming sync may pause" } ), ); @@ -296,10 +298,7 @@ unsafe fn update_send_enabled() { if send_button.is_null() || compose_entry.is_null() { return; } - let c = ctx(); - let ok = c.session.hub_connected - && selected_network_id().is_some() - && selected_contact().is_some(); + let ok = selected_network_id().is_some() && selected_contact().is_some(); if ok { libui::uiControlEnable(send_button.cast()); libui::uiControlEnable(compose_entry.cast()); @@ -780,9 +779,16 @@ unsafe extern "C" fn on_send_clicked(_: *mut libui::uiButton, _: *mut c_void) { let name = self_display_name(&c.client); let sender_id = c.client.self_address(); let now = crate::serviceclient::ms_since_epoch(); + let mut ports = vec![cv.contact.port]; + if !ports.contains(&DEFAULT_WEBHUB_PORT) { + ports.push(DEFAULT_WEBHUB_PORT); + } + if !ports.contains(&DEFAULT_BACKONE_PORT) { + ports.push(DEFAULT_BACKONE_PORT); + } let _ = c.hub_cmd.send(HubCommand::SendChatRemote { host: cv.contact.ip.clone(), - port: cv.contact.port, + ports, network_id: network_id.to_lowercase(), sender_id: sender_id.to_lowercase(), target_id: cv.contact.peer_id.to_lowercase(), @@ -887,6 +893,7 @@ unsafe extern "C" fn on_timer(_: *mut c_void) -> c_int { &c.client.self_address(), &name, &cv.contact.peer_id, + &cv.contact.ip, &body, ); let now = crate::serviceclient::ms_since_epoch(); @@ -1066,8 +1073,9 @@ pub fn secure_chat_main() { } let auth_token = client.auth_token().to_string(); + let hub_host = client.webhub_host(); let hub_port = client.webhub_port(); - let (hub_cmd, hub_events) = spawn_webhub(auth_token, hub_port); + let (hub_cmd, hub_events) = spawn_webhub(auth_token, hub_host, hub_port); let ctx_box = Box::new(SecureChatCtx { client, diff --git a/src/secure_chat/relay.rs b/src/secure_chat/relay.rs index a1cb9dd..3b84918 100644 --- a/src/secure_chat/relay.rs +++ b/src/secure_chat/relay.rs @@ -2,6 +2,8 @@ use serde_json::json; use crate::serviceclient::ServiceClient; +use super::state::load_member_ip_for_peer; + fn peer_path_hosts(client: &ServiceClient, target_id: &str) -> Vec { let target = target_id.to_lowercase(); let Some(peers) = client.http_get_json("/peer").and_then(|v| v.as_array().cloned()) else { @@ -35,6 +37,14 @@ fn peer_path_hosts(client: &ServiceClient, target_id: &str) -> Vec { hosts } +fn push_unique_host(hosts: &mut Vec, host: &str) { + let host = host.split('/').next().unwrap_or(host).trim(); + if host.is_empty() || hosts.iter().any(|candidate| candidate == host) { + return; + } + hosts.push(host.to_string()); +} + fn post_relay_url( host: &str, port: u16, @@ -69,6 +79,7 @@ pub fn relay_chat_to_peer_paths( sender_id: &str, sender_name: &str, target_id: &str, + preferred_host: &str, body: &str, ) -> bool { let port = client.http_port(); @@ -85,8 +96,17 @@ pub fn relay_chat_to_peer_paths( }); let payload_str = payload.to_string(); let token = client.auth_token(); + let mut hosts = Vec::new(); + push_unique_host(&mut hosts, preferred_host); + if let Some(member_ip) = load_member_ip_for_peer(client, network_id, target_id) { + push_unique_host(&mut hosts, &member_ip); + } for host in peer_path_hosts(client, target_id) { + push_unique_host(&mut hosts, &host); + } + + for host in hosts { for use_auth in [true, false] { let auth = if use_auth { Some(token) } else { None }; let (status, resp_body) = post_relay_url(&host, port, auth, &payload_str); diff --git a/src/secure_chat/webhub.rs b/src/secure_chat/webhub.rs index d9efda0..b1584df 100644 --- a/src/secure_chat/webhub.rs +++ b/src/secure_chat/webhub.rs @@ -37,7 +37,7 @@ pub enum HubCommand { }, SendChatRemote { host: String, - port: u16, + ports: Vec, network_id: String, sender_id: String, target_id: String, @@ -65,44 +65,13 @@ fn is_loopback_host(host: &str) -> bool { fn send_chat_remote( auth_token: &str, host: &str, - port: u16, + ports: &[u16], network_id: &str, sender_id: &str, target_id: &str, sender_name: &str, body: &str, ) -> bool { - let url = if is_loopback_host(host) && !auth_token.is_empty() { - format!( - "ws://{}:{}/?auth={}", - host, - port, - encode_query_component(auth_token) - ) - } else { - format!("ws://{}:{}/", host, port) - }; - - let Ok((mut socket, _)) = connect(&url) else { - return false; - }; - if let MaybeTlsStream::Plain(tcp) = socket.get_ref() { - let _ = tcp.set_read_timeout(Some(Duration::from_millis(3000))); - } - if is_loopback_host(host) && !auth_token.is_empty() { - let _ = socket.send(Message::Text( - json!({ "type": "auth", "token": auth_token }).to_string(), - )); - let _ = socket.send(Message::Text( - json!({ - "type": "register", - "peerId": sender_id, - "networkId": network_id.to_lowercase(), - }) - .to_string(), - )); - } - let payload = json!({ "type": "chat_message", "networkId": network_id.to_lowercase(), @@ -111,34 +80,73 @@ fn send_chat_remote( "senderName": sender_name, "body": body, }); - if socket.send(Message::Text(payload.to_string())).is_err() { - return false; - } - let deadline = std::time::Instant::now() + Duration::from_secs(3); - while std::time::Instant::now() < deadline { - match socket.read() { - Ok(Message::Text(text)) => { - if let Ok(msg) = serde_json::from_str::(&text) { - if msg.get("type").and_then(|t| t.as_str()) == Some("chat_delivery") { - return msg - .get("delivered") - .and_then(|v| v.as_bool()) - .unwrap_or(false); + for port in ports { + let url = if is_loopback_host(host) && !auth_token.is_empty() { + format!( + "ws://{}:{}/?auth={}", + host, + port, + encode_query_component(auth_token) + ) + } else { + format!("ws://{}:{}/", host, port) + }; + + let Ok((mut socket, _)) = connect(&url) else { + continue; + }; + if let MaybeTlsStream::Plain(tcp) = socket.get_ref() { + let _ = tcp.set_read_timeout(Some(Duration::from_millis(3000))); + } + if is_loopback_host(host) && !auth_token.is_empty() { + let _ = socket.send(Message::Text( + json!({ "type": "auth", "token": auth_token }).to_string(), + )); + let _ = socket.send(Message::Text( + json!({ + "type": "register", + "peerId": sender_id, + "networkId": network_id.to_lowercase(), + }) + .to_string(), + )); + } + + if socket.send(Message::Text(payload.to_string())).is_err() { + continue; + } + + let deadline = std::time::Instant::now() + Duration::from_secs(3); + while std::time::Instant::now() < deadline { + match socket.read() { + Ok(Message::Text(text)) => { + if let Ok(msg) = serde_json::from_str::(&text) { + if msg.get("type").and_then(|t| t.as_str()) == Some("chat_delivery") { + if msg + .get("delivered") + .and_then(|v| v.as_bool()) + .unwrap_or(false) + { + return true; + } + break; + } } } + Ok(Message::Close(_)) => break, + Err(e) => match e { + tungstenite::Error::Io(ref io) + if io.kind() == ErrorKind::WouldBlock + || io.kind() == ErrorKind::TimedOut => + { + thread::sleep(Duration::from_millis(50)); + continue; + } + _ => break, + }, + Ok(_) => {} } - Ok(Message::Close(_)) => break, - Err(e) => match e { - tungstenite::Error::Io(ref io) - if io.kind() == ErrorKind::WouldBlock || io.kind() == ErrorKind::TimedOut => - { - thread::sleep(Duration::from_millis(50)); - continue; - } - _ => break, - }, - Ok(_) => {} } } false @@ -146,6 +154,7 @@ fn send_chat_remote( pub fn spawn_webhub( auth_token: String, + host: String, port: u16, ) -> (Sender, Receiver) { let (cmd_tx, cmd_rx) = mpsc::channel::(); @@ -159,7 +168,8 @@ pub fn spawn_webhub( } let url = format!( - "ws://127.0.0.1:{}/?auth={}", + "ws://{}:{}/?auth={}", + host, port, encode_query_component(auth_token.as_str()) ); @@ -204,7 +214,7 @@ pub fn spawn_webhub( } HubCommand::SendChatRemote { host, - port: remote_port, + ports, network_id, sender_id, target_id, @@ -217,7 +227,7 @@ pub fn spawn_webhub( let delivered = send_chat_remote( &auth, &host, - remote_port, + &ports, &network_id, &sender_id, &target_id, diff --git a/src/serviceclient.rs b/src/serviceclient.rs index 59ebeea..a1c92a3 100644 --- a/src/serviceclient.rs +++ b/src/serviceclient.rs @@ -432,6 +432,10 @@ impl ServiceClient { Self::webhub_port_from_local_conf().unwrap_or(9994) } + pub fn webhub_host(&self) -> String { + Self::webhub_host_from_local_conf().unwrap_or_else(|| String::from("127.0.0.1")) + } + fn webhub_port_from_local_conf() -> Option { for path in [crate::GLOBAL_SERVICE_HOME_V2, crate::GLOBAL_SERVICE_HOME_V1] { let conf_path = Path::new(path).join("local.conf"); @@ -452,6 +456,31 @@ impl ServiceClient { None } + fn webhub_host_from_local_conf() -> Option { + for path in [crate::GLOBAL_SERVICE_HOME_V2, crate::GLOBAL_SERVICE_HOME_V1] { + let conf_path = Path::new(path).join("local.conf"); + let Ok(body) = std::fs::read_to_string(conf_path) else { + continue; + }; + let Ok(cfg) = serde_json::from_str::(&body) else { + continue; + }; + let Some(bind) = cfg + .get("settings") + .and_then(|s| s.get("webhubBind")) + .and_then(|b| b.as_str()) + .map(str::trim) + else { + continue; + }; + if bind.is_empty() || bind.eq_ignore_ascii_case("overlay") { + continue; + } + return Some(bind.to_string()); + } + None + } + pub fn self_address(&self) -> String { self.get_str(&["status", "address"]).to_lowercase() }