secure chat addon

This commit is contained in:
ariska committed 2026-07-07 16:35:55 +07:00
1 parent 0715f5d66d
commit a6a4375281
4 files changed
+134 -67

No files matched your search

+15 -7
View File
@@ -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,
+20
View File
@@ -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<String> {
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<String> {
hosts
}
fn push_unique_host(hosts: &mut Vec<String>, 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);
+70 -60
View File
@@ -37,7 +37,7 @@ pub enum HubCommand {
},
SendChatRemote {
host: String,
port: u16,
ports: Vec<u16>,
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::<Value>(&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::<Value>(&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<HubCommand>, Receiver<HubEvent>) {
let (cmd_tx, cmd_rx) = mpsc::channel::<HubCommand>();
@@ -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,
+29
View File
@@ -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<u16> {
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<String> {
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::<Value>(&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()
}