secure chat addon
This commit is contained in:
1 parent
0715f5d66d
commit
a6a4375281
4 files changed
+134
-67
No files matched your search
+15
-7
@@ -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,
|
||||
|
||||
@@ -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
@@ -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,
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user