async sockets

This commit is contained in:
Alex Emmet 2025-11-09 19:25:01 +00:00
commit 4170b9f387
23 changed files with 544 additions and 479 deletions

208
Cargo.lock generated
View file

@ -8,6 +8,7 @@ version = "0.1.0"
dependencies = [
"aes-gcm",
"async-trait",
"async-tungstenite",
"axum",
"base64",
"bytes",
@ -20,10 +21,12 @@ dependencies = [
"hex",
"hkdf",
"hmac",
"hyper",
"json",
"native-tls",
"once_cell",
"pkcs8",
"rand",
"rand 0.8.5",
"rand_core 0.6.4",
"ratatui",
"reactive-rs",
@ -35,9 +38,11 @@ dependencies = [
"sysinfo",
"tokio",
"tokio-tungstenite",
"tokio-util",
"tungstenite",
"uuid",
"walkdir",
"warp",
"x448",
"x509",
]
@ -118,6 +123,22 @@ dependencies = [
"syn",
]
[[package]]
name = "async-tungstenite"
version = "0.32.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6f89c129ab749940f95509d84950c62092c8b4bc6e386ddb162229037a6ec91"
dependencies = [
"atomic-waker",
"futures-core",
"futures-io",
"futures-task",
"futures-util",
"log",
"pin-project-lite",
"tungstenite",
]
[[package]]
name = "atomic-waker"
version = "1.1.2"
@ -166,9 +187,9 @@ dependencies = [
[[package]]
name = "axum-core"
version = "0.5.2"
version = "0.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68464cd0412f486726fb3373129ef5d2993f90c34bc2bc1c1e9943b2f4fc7ca6"
checksum = "59446ce19cd142f8833f856eb31f3eb097812d1479ab224f54d72428ca21ea22"
dependencies = [
"bytes",
"futures-core",
@ -177,7 +198,6 @@ dependencies = [
"http-body-util",
"mime",
"pin-project-lite",
"rustversion",
"sync_wrapper",
"tower-layer",
"tower-service",
@ -232,12 +252,6 @@ version = "3.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
[[package]]
name = "byteorder"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "bytes"
version = "1.10.1"
@ -871,6 +885,30 @@ dependencies = [
"foldhash",
]
[[package]]
name = "headers"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3314d5adb5d94bcdf56771f2e50dbbc80bb4bdf88967526706205ac9eff24eb"
dependencies = [
"base64",
"bytes",
"headers-core",
"http",
"httpdate",
"mime",
"sha1",
]
[[package]]
name = "headers-core"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "54b4a22553d4242c49fddb9ba998a99962b5cc6f22cb5a3482bec22522403ce4"
dependencies = [
"http",
]
[[package]]
name = "heck"
version = "0.5.0"
@ -1355,6 +1393,16 @@ version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
[[package]]
name = "mime_guess"
version = "2.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e"
dependencies = [
"mime",
"unicase",
]
[[package]]
name = "miniz_oxide"
version = "0.8.9"
@ -1532,6 +1580,26 @@ version = "2.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
[[package]]
name = "pin-project"
version = "1.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a"
dependencies = [
"pin-project-internal",
]
[[package]]
name = "pin-project-internal"
version = "1.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "pin-project-lite"
version = "0.2.16"
@ -1627,10 +1695,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
"libc",
"rand_chacha",
"rand_chacha 0.3.1",
"rand_core 0.6.4",
]
[[package]]
name = "rand"
version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
dependencies = [
"rand_chacha 0.9.0",
"rand_core 0.9.3",
]
[[package]]
name = "rand_chacha"
version = "0.3.1"
@ -1641,6 +1719,16 @@ dependencies = [
"rand_core 0.6.4",
]
[[package]]
name = "rand_chacha"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
dependencies = [
"ppv-lite86",
"rand_core 0.9.3",
]
[[package]]
name = "rand_core"
version = "0.5.1"
@ -1656,6 +1744,15 @@ dependencies = [
"getrandom 0.2.16",
]
[[package]]
name = "rand_core"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
dependencies = [
"getrandom 0.3.3",
]
[[package]]
name = "ratatui"
version = "0.29.0"
@ -1866,6 +1963,12 @@ dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "scoped-tls"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294"
[[package]]
name = "scopeguard"
version = "1.2.0"
@ -2197,18 +2300,18 @@ dependencies = [
[[package]]
name = "thiserror"
version = "1.0.69"
version = "2.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.69"
version = "2.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913"
dependencies = [
"proc-macro2",
"quote",
@ -2308,18 +2411,16 @@ dependencies = [
[[package]]
name = "tokio-tungstenite"
version = "0.23.1"
version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c6989540ced10490aaf14e6bad2e3d33728a2813310a0c71d1574304c49631cd"
checksum = "d25a406cddcc431a75d3d9afc6a7c0f7428d4891dd973e4d54c56b46127bf857"
dependencies = [
"futures-util",
"log",
"rustls",
"rustls-pki-types",
"native-tls",
"tokio",
"tokio-rustls",
"tokio-native-tls",
"tungstenite",
"webpki-roots 0.26.11",
]
[[package]]
@ -2330,8 +2431,12 @@ checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5"
dependencies = [
"bytes",
"futures-core",
"futures-io",
"futures-sink",
"futures-util",
"hashbrown",
"pin-project-lite",
"slab",
"tokio",
]
@ -2431,19 +2536,17 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
[[package]]
name = "tungstenite"
version = "0.23.0"
version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e2e2ce1e47ed2994fd43b04c8f618008d4cabdd5ee34027cf14f9d918edd9c8"
checksum = "8628dcc84e5a09eb3d8423d6cb682965dea9133204e8fb3efee74c2a0c259442"
dependencies = [
"byteorder",
"bytes",
"data-encoding",
"http",
"httparse",
"log",
"rand",
"rustls",
"rustls-pki-types",
"native-tls",
"rand 0.9.2",
"sha1",
"thiserror",
"utf-8",
@ -2455,6 +2558,12 @@ version = "1.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f"
[[package]]
name = "unicase"
version = "2.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539"
[[package]]
name = "unicode-ident"
version = "1.0.18"
@ -2578,6 +2687,33 @@ dependencies = [
"try-lock",
]
[[package]]
name = "warp"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51d06d9202adc1f15d709c4f4a2069be5428aa912cc025d6f268ac441ab066b0"
dependencies = [
"bytes",
"futures-util",
"headers",
"http",
"http-body",
"http-body-util",
"log",
"mime",
"mime_guess",
"percent-encoding",
"pin-project",
"scoped-tls",
"serde",
"serde_json",
"serde_urlencoded",
"tokio",
"tokio-util",
"tower-service",
"tracing",
]
[[package]]
name = "wasi"
version = "0.11.1+wasi-snapshot-preview1"
@ -2675,24 +2811,6 @@ dependencies = [
"wasm-bindgen",
]
[[package]]
name = "webpki-roots"
version = "0.26.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9"
dependencies = [
"webpki-roots 1.0.2",
]
[[package]]
name = "webpki-roots"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e8983c3ab33d6fb807cfcdad2491c4ea8cbc8ed839181c7dfd9c67c83e261b2"
dependencies = [
"rustls-pki-types",
]
[[package]]
name = "winapi"
version = "0.3.9"

View file

@ -4,41 +4,42 @@ version = "0.1.0"
edition = "2024"
[dependencies]
async-trait = "*"
json = "*"
axum = "*"
futures-util = "*"
rustls = { version = "*", default-features = false, features = ["ring"] }
tokio-tungstenite = { version = "0.23.1", default-features = false, features = [
"connect",
"rustls-tls-webpki-roots",
"tokio-rustls"
] }
cmake = "*"
tokio = { version = "*", features = ["full"] }
uuid = { version = "*", features = ["v4"] }
walkdir = "2.5.0"
sysinfo = "0.30"
serde = { version = "1.0.219", features = ["derive"] }
ratatui = { version = "*", features = ["all-widgets"]}
bytes = "*"
crossterm = "*"
color-eyre = "*"
sys-info = "*"
rand = "0.8"
rand_core = { version = "0.6", features = ["getrandom", "std"] }
base64 = "0.22.1"
once_cell = "1.21.3"
reqwest = "0.12.23"
x448 = { version = "*" }
hex = "*"
x509 = "*"
sha2 = "*"
der = "*"
pkcs8 = { version = "*", features = ["alloc"] }
tungstenite = "*"
futures = "*"
aes-gcm = "*"
async-trait = "*"
async-tungstenite = { version = "*" }
axum = "*"
base64 = "0.22.1"
bytes = "*"
cmake = "*"
color-eyre = "*"
crossterm = "*"
der = "*"
futures = "*"
futures-util = "*"
hex = "*"
hkdf = "*"
hmac = "*"
hyper = { version = "*", features = ["full"] }
json = "*"
native-tls = { version = "*", default-features = false }
once_cell = "1.21.3"
pkcs8 = { version = "*", features = ["alloc"] }
rand = "0.8"
rand_core = { version = "0.6", features = ["getrandom", "std"] }
ratatui = { version = "*", features = ["all-widgets"]}
reactive-rs = "*"
reqwest = "0.12.23"
rustls = { version = "*", default-features = false, features = ["ring"] }
serde = { version = "1.0.219", features = ["derive"] }
sha2 = "*"
sys-info = "*"
sysinfo = "0.30"
tokio = { version = "*", features = ["full"] }
tokio-util = { version = "*", features = ["full"] }
tokio-tungstenite = { version = "*", features = ["native-tls"] }
tungstenite = "*"
uuid = { version = "*", features = ["v4"] }
walkdir = "2.5.0"
warp = "*"
x448 = { version = "*" }
x509 = "*"

View file

@ -51,7 +51,7 @@ pub async fn get_uuid(username: &str) -> Option<Uuid> {
let client = client();
let res = client.get(&url).send().await.ok()?;
let json = res.text().await.ok()?;
let mut cv = CommunicationValue::from_json(&json);
let cv = CommunicationValue::from_json(&json);
if !cv.is_type(CommunicationType::success) {
return None;
}
@ -64,7 +64,7 @@ pub async fn get_user(user_id: Uuid) -> Option<AuthUser> {
let res = client.get(&url).send().await.ok()?;
let json = res.text().await.ok()?;
let mut cv = CommunicationValue::from_json(&json);
let cv = CommunicationValue::from_json(&json);
if cv.comm_type != CommunicationType::success {
return None;
}
@ -103,7 +103,7 @@ pub async fn get_register() -> Option<Uuid> {
let res = client.get(&url).send().await.ok()?;
let json = res.text().await.ok()?;
let mut cv = CommunicationValue::from_json(&json);
let cv = CommunicationValue::from_json(&json);
Uuid::parse_str(&*cv.get_data(DataTypes::user_id).unwrap().to_string()).ok()
}

View file

@ -4,16 +4,8 @@ use aes_gcm::{
aead::{Aead, KeyInit, OsRng},
};
use base64::{Engine as _, engine::general_purpose::STANDARD};
use base64::{decode as b64_decode, encode as b64_encode};
use hex;
use json::JsonValue;
use once_cell::sync::Lazy;
use rand::Rng;
use rand_core::RngCore;
use sha2::{Digest, Sha256};
use std::io;
use std::sync::Mutex;
use uuid::Uuid;
use x448::{PublicKey, Secret, SharedSecret}; // from the `x448` crate
/// Errors for crypto operations
@ -47,20 +39,20 @@ pub fn generate_keypair() -> KeyPair {
}
pub fn public_key_to_base64(pubkey: &PublicKey) -> String {
b64_encode(pubkey.as_bytes().as_ref())
STANDARD.encode(pubkey.as_bytes().as_ref())
}
pub fn secret_key_to_base64(secret: &Secret) -> String {
b64_encode(secret.as_bytes().as_ref())
STANDARD.encode(secret.as_bytes().as_ref())
}
pub fn load_public_key(base64_pub: &str) -> Option<PublicKey> {
let bytes = b64_decode(base64_pub).unwrap();
let bytes = STANDARD.decode(base64_pub).unwrap();
PublicKey::from_bytes(&bytes)
}
pub fn load_secret_key(base64_secret: &str) -> Option<Secret> {
let bytes = b64_decode(base64_secret).unwrap();
let bytes = STANDARD.decode(base64_secret).unwrap();
Secret::from_bytes(&bytes)
}
@ -95,7 +87,7 @@ pub fn encrypt(
let mut out = Vec::with_capacity(nonce_bytes.len() + ciphertext.len());
out.extend_from_slice(&nonce_bytes);
out.extend_from_slice(&ciphertext);
Ok(b64_encode(&out))
Ok(STANDARD.encode(&out))
}
pub fn decrypt(
@ -111,7 +103,7 @@ pub fn decrypt(
let key_bytes = derive_aes_key(&shared);
let cipher = Aes256Gcm::new_from_slice(&key_bytes).expect("Key length should be correct");
let encrypted = b64_decode(encrypted_base64)?;
let encrypted = STANDARD.decode(encrypted_base64)?;
if encrypted.len() < 12 {
return Err(CryptoError::DecryptionError(aes_gcm::Error));
}

View file

@ -1,7 +1,5 @@
use crate::communities::interactables::category::Category;
use crate::communities::interactables::registry;
use crate::communities::interactables::text_chat::TextChat;
use crate::communities::interactables::voice_chat::VoiceChat;
use crate::communities::{
community_connection::CommunityConnection, interactables::interactable::Interactable,
};
@ -12,8 +10,6 @@ use json::JsonValue;
use json::object::Object;
use rand::RngCore;
use rand_core::OsRng;
use ratatui::text;
use serde::de::value::StringDeserializer;
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::RwLock;
@ -200,10 +196,10 @@ impl Community {
let mut permissions = JsonValue::new_array();
for perm in self.permissions.get(user).unwrap() {
permissions.push(perm.to_string());
if let Ok(_) = permissions.push(perm.to_string()) {}
}
data.insert("permissions", permissions);
if let Ok(_) = data.insert("permissions", permissions) {}
user_data.insert(&user.to_string(), data);
}
file_util::save_file(
@ -230,7 +226,7 @@ pub async fn load(name: &String) -> Option<Arc<Community>> {
let (str, json): (&str, &JsonValue) = user;
let perms_j = &json["permissions"];
let perms = Vec::new();
for i in perms_j.entries() {
for _ in perms_j.entries() {
// let perm_j = i.as_str().unwrap();
// perms.push(perm_j.to_string());
}
@ -242,11 +238,12 @@ pub async fn load(name: &String) -> Option<Arc<Community>> {
}
let role_data = file_util::load_file(&format!("communities/{}/", name), "roles.json");
if let Ok(user_json) = json::parse(&role_data) {
let roles: HashMap<String, Vec<String>> = HashMap::new();
if let Ok(_) = json::parse(&role_data) {
// Fill roles
} else {
return None;
};
let mut roles: HashMap<String, Vec<String>> = HashMap::new();
let community = Community {
name: json_content["name"].as_str().unwrap().to_string(),
@ -283,15 +280,5 @@ pub async fn load(name: &String) -> Option<Arc<Community>> {
comarc.add_interactable(Arc::new(interactable)).await;
}
}
let mut text_chat: TextChat = TextChat::new();
text_chat.load(
comarc.clone(),
String::new(),
String::from("a"),
&JsonValue::Null,
);
comarc.add_interactable(Arc::new(Box::new(text_chat))).await;
Some(comarc)
}

View file

@ -4,6 +4,10 @@ use crate::communities::community::Community;
use crate::communities::interactables::interactable::Interactable;
use crate::data::communication::{CommunicationType, CommunicationValue, DataTypes};
use aes_gcm::{Aes256Gcm, KeyInit, Nonce, aead::Aead};
use async_tungstenite::WebSocketReceiver;
use async_tungstenite::WebSocketSender;
use async_tungstenite::tungstenite::Utf8Bytes;
use async_tungstenite::{WebSocketStream, tungstenite::Message};
use base64::{Engine as _, engine::general_purpose::STANDARD};
use futures::SinkExt;
use hkdf::Hkdf;
@ -13,11 +17,12 @@ use sha2::Sha256;
use std::sync::Arc;
use tokio::sync::Mutex;
use tokio::sync::RwLock;
use tokio_tungstenite::{WebSocketStream, tungstenite::Message};
use tokio_util::compat::Compat;
use uuid::Uuid;
use x448::PublicKey;
pub struct CommunityConnection {
pub session: Arc<Mutex<WebSocketStream<tokio::net::TcpStream>>>,
pub sender: Arc<RwLock<WebSocketSender<Compat<tokio::net::TcpStream>>>>,
pub receiver: Arc<RwLock<WebSocketReceiver<Compat<tokio::net::TcpStream>>>>,
pub user_id: Arc<RwLock<Option<Uuid>>>,
pub community: Arc<RwLock<Option<Arc<Community>>>>,
identified: Arc<RwLock<bool>>,
@ -28,11 +33,13 @@ pub struct CommunityConnection {
}
impl CommunityConnection {
pub fn new(
session: WebSocketStream<tokio::net::TcpStream>,
sender: WebSocketSender<Compat<tokio::net::TcpStream>>,
receiver: WebSocketReceiver<Compat<tokio::net::TcpStream>>,
community: Arc<Community>,
) -> Arc<Self> {
Arc::new(Self {
session: Arc::new(Mutex::new(session)),
sender: Arc::new(RwLock::new(sender)),
receiver: Arc::new(RwLock::new(receiver)),
user_id: Arc::new(RwLock::new(None)),
community: Arc::new(RwLock::new(Some(community))),
identified: Arc::new(RwLock::new(false)),
@ -43,9 +50,11 @@ impl CommunityConnection {
})
}
pub async fn send_message(&self, message: &CommunicationValue) {
let mut session = self.session.lock().await;
let mut session = self.sender.write().await;
session
.send(Message::Text(message.to_json().to_string()))
.send(Message::Text(Utf8Bytes::from(
message.to_json().to_string(),
)))
.await
.unwrap();
}
@ -60,7 +69,8 @@ impl CommunityConnection {
}
pub async fn handle_message(self: Arc<Self>, message: String) {
let cv = CommunicationValue::from_json(&message);
let cv =
CommunicationValue::from_json(&message).with_sender(self.get_user_id().await.unwrap());
if cv.is_type(CommunicationType::identification) && !self.is_identified().await {
self.handle_identification(cv).await;
@ -388,7 +398,7 @@ impl CommunityConnection {
self.send_message(&error).await;
}
pub async fn close(&self) {
let mut session = self.session.lock().await;
let mut session = self.sender.write().await;
let _ = session.close(None).await;
}
pub async fn handle_close(self: Arc<Self>) {

View file

@ -98,11 +98,11 @@ impl Interactable for Category {
let mut v = JsonValue::new_object();
v["children"] = JsonValue::new_array();
for child in &self.children {
v["children"].push(child.to_json());
let _ = v["children"].push(child.to_json());
}
v
}
fn load(&mut self, community: Arc<Community>, path: String, name: String, json: &JsonValue) {
fn load(&mut self, community: Arc<Community>, path: String, name: String, _json: &JsonValue) {
self.community = community;
self.name = name;
self.path = path;

View file

@ -11,13 +11,9 @@ use aes_gcm::aead::Payload;
use async_trait::async_trait;
use axum::Json;
use json::{JsonValue, array, object};
use rustls::ClientConnection;
use std::sync::Arc;
use std::{any::Any, collections::HashMap};
use std::{
fs::{self, File},
pin::Pin,
};
use std::{fs, pin::Pin};
use uuid::Uuid;
pub struct TextChat {
name: String,
@ -246,10 +242,9 @@ impl Interactable for TextChat {
CommunicationValue::new(CommunicationType::error).with_id(cv.get_id())
}
fn to_json(&self) -> JsonValue {
let mut v = JsonValue::new_object();
v
JsonValue::new_object()
}
fn load(&mut self, community: Arc<Community>, path: String, name: String, json: &JsonValue) {
fn load(&mut self, community: Arc<Community>, path: String, name: String, _: &JsonValue) {
self.community = community;
self.name = name;
self.path = path;

View file

@ -188,7 +188,7 @@ impl Interactable for VoiceChat {
let v = JsonValue::new_object();
v
}
fn load(&mut self, community: Arc<Community>, path: String, name: String, _: &JsonValue) {
fn load(&mut self, community: Arc<Community>, path: String, name: String, _json: &JsonValue) {
self.community = community;
self.name = name;
self.path = path;

View file

@ -1,5 +1,4 @@
pub mod community_manager;
pub mod community_socket;
pub mod interactables {
pub mod category;
pub mod interactable;

View file

@ -393,14 +393,6 @@ impl CommunicationValue {
data,
}
}
pub fn ack_message(message_id: Uuid, sender: Uuid) -> CommunicationValue {
let mut cv = CommunicationValue::new(CommunicationType::message).with_id(message_id);
if let s = sender {
cv = cv.add_data(DataTypes::send_time, JsonValue::String(s.to_string()));
}
cv
}
pub fn forward_to_other_iota(original: &mut CommunicationValue) -> CommunicationValue {
let receiver = Uuid::from_str(
&*original
@ -420,7 +412,8 @@ impl CommunicationValue {
CommunicationValue::new(CommunicationType::message_other_iota)
.with_id(original.get_id())
.with_receiver(receiver.unwrap())
.add_data( DataTypes::receiver_id,
.add_data(
DataTypes::receiver_id,
JsonValue::String(receiver.unwrap().to_string()),
)
.with_sender(sender.unwrap())

View file

@ -1,12 +1,11 @@
use crate::util::file_util::{load_file, save_file};
pub fn check_eula() -> bool{
pub fn check_eula() -> bool {
let eula = "By changing the value to \"true\" you agree to our end user license agreement and our terms of service!\
\nYou can find our Terms of service on https://docs.tensamin.methanium.net/legal/terms-of-service/.\
\nYou can find our Terms of service on https://docs.tensamin.net/legal/terms-of-service/.\
\neula=false";
let file = load_file("", "eula.txt");
if(file.is_empty()){
if file.is_empty() {
save_file("", "eula.txt", eula);
return false;
}
@ -19,9 +18,9 @@ pub fn check_eula() -> bool{
false
}
}
pub fn accept_eula(){
let eula = "By changing the value to \"true\" you agree to some shit we say on our website IDK this shouldn't be public yet!\
\nYou also give us all rights to your soul, and we own your dog now.\
pub fn accept_eula() {
let eula = "By changing the value to \"true\" you agree to our end user license agreement and our terms of service!\
\nYou can find our Terms of service on https://docs.tensamin.net/legal/terms-of-service/.\
\neula=true";
save_file("", "eula.txt", eula);
}

View file

@ -5,7 +5,6 @@ use crate::langu::language_manager::format;
use crate::langu::language_manager::from_key;
use crate::data::communication::{CommunicationType, CommunicationValue, DataTypes};
use json::Array;
use ratatui::widgets::canvas::{Canvas, Line};
use ratatui::{
layout::{Constraint, Direction, Layout},
@ -66,7 +65,7 @@ fn downsample_to_fit_width(data: &[(f64, f64)], width: u16) -> Vec<(f64, f64)> {
.first()
.map(|(x, _)| x - (dx * pad_len as f64))
.unwrap_or(0.0);
let y = data.first().map(|(_, y)| *y).unwrap_or(0.0);
let _ = data.first().map(|(_, y)| *y).unwrap_or(0.0);
// Fill padding with increasing x positions so they're visible
for i in 0..pad_len {
@ -96,8 +95,8 @@ pub fn setup() {
}
let ram = (sys.used_memory() as f64 / sys.total_memory() as f64) * 100.0;
let mut total_received = 0u64;
let mut total_transmitted = 0u64;
let total_received = 0u64;
let total_transmitted = 0u64;
let delta_received = if last_total_received == 0 {
0

View file

@ -1,7 +1,7 @@
use crate::util::file_util::{self, save_file};
use json::{self, JsonValue};
use crate::util::file_util::save_file;
use json::{self, JsonError, JsonValue};
pub fn create_languages() {
pub fn create_languages() -> Result<(), JsonError> {
let mut frontend_messages = JsonValue::new_object();
let mut omikron_messages = JsonValue::new_object();
let mut button_texts = JsonValue::new_object();
@ -9,40 +9,44 @@ pub fn create_languages() {
let mut debug_messages = JsonValue::new_object();
// FRONTEND
frontend_messages.insert("get_chats", "User {} is loading conversations");
frontend_messages.insert("message_get", "User {} is loading messages");
frontend_messages.insert("get_communities", "User {} is loading communities");
frontend_messages.insert("client_connected", "Client {} connected");
frontend_messages.insert("add_conversation", "User {} added {}");
frontend_messages.insert("get_chats", "User {} is loading conversations")?;
frontend_messages.insert("message_get", "User {} is loading messages")?;
frontend_messages.insert("get_communities", "User {} is loading communities")?;
frontend_messages.insert("client_connected", "Client {} connected")?;
frontend_messages.insert("add_conversation", "User {} added {}")?;
frontend_messages.insert("message_send", "User {} sent a message")?;
// OMIKRON
omikron_messages.insert(
"identification_response",
"IOTA identified on Omikron, {} users!",
);
)?;
omikron_messages.insert("send_message_failed", "Failed to send message to Omikron")?;
omikron_messages.insert("connection_failed", "Failed to connect to Omikron: {}")?;
// BUTTONS
button_texts.insert("exit", "Exit");
button_texts.insert("exit", "Exit")?;
// GENERAL
general_texts.insert("iota_id", "IOTA ID: {}-####-####-####-############");
general_texts.insert("user_id", "USER ID: {}");
general_texts.insert("user_ids", "USER IDS: {}");
general_texts.insert("setup_completed", "Launched");
general_texts.insert("iota_id", "IOTA ID: {}-####-####-####-############")?;
general_texts.insert("user_id", "USER ID: {}")?;
general_texts.insert("user_ids", "USER IDS: {}")?;
general_texts.insert("user_load_failed", "Failed to load user data")?;
general_texts.insert("setup_completed", "Launched")?;
general_texts.insert(
"community_active",
"Communities active on ws://0.0.0.0:{}/community/...",
);
)?;
general_texts.insert(
"community_start_error",
"Failed to start community socket on port {}!",
);
)?;
general_texts.insert(
"community_start_error_admin",
"Failed to start community socket on port {}! Run with admin privileges",
);
)?;
// DEBUG
debug_messages.insert("", "");
debug_messages.insert("", "")?;
save_file(
"languages/en_INT",
"frontend.json",
@ -68,4 +72,5 @@ pub fn create_languages() {
"general.json",
&general_texts.to_string(),
);
Ok(())
}

View file

@ -39,7 +39,7 @@ pub fn from_key(key: &str) -> String {
pub fn format(key: &str, args: &[&str]) -> String {
let message = from_key(key);
let mut formatted = String::new();
let mut parts = message.split("{}");
let parts = message.split("{}");
for (i, part) in parts.enumerate() {
formatted.push_str(part);
if i < args.len() {

View file

@ -12,11 +12,12 @@ mod eula;
mod gui;
mod langu;
mod omikron;
mod server;
mod users;
mod util;
use crate::communities::community_manager;
use crate::communities::interactables::registry;
use crate::communities::{community_manager, community_socket};
use crate::data::communication::{CommunicationType, CommunicationValue, DataTypes};
use crate::gui::app_state::AppState;
use crate::gui::log_panel::{log_message, log_message_trans};
@ -24,13 +25,14 @@ use crate::gui::{log_panel, ratatui_interface};
use crate::langu::language_creator;
use crate::langu::language_manager::format;
use crate::omikron::omikron_connection::OmikronConnection;
use crate::server::socket::start;
use crate::users::user_manager;
use crate::util::config_util::CONFIG;
pub static APP_STATE: LazyLock<Arc<Mutex<AppState>>> =
LazyLock::new(|| Arc::new(Mutex::new(AppState::new())));
#[tokio::main]
#[tokio::main(flavor = "multi_thread", worker_threads = 8)]
#[allow(unused_must_use, dead_code)]
async fn main() {
// EULA
//if !eula_checker::check_eula() {
@ -38,22 +40,33 @@ async fn main() {
// return;
//}
// LANGUAGE PACK
if let Err(e) = language_creator::create_languages() {
println!("Language pack creation failed: {}", e);
return;
}
// UI
log_panel::setup();
ratatui_interface::launch();
// LANGUAGE PACK
language_creator::create_languages();
if let Err(e) = ratatui_interface::launch() {
println!("Ui launch failed: {}", &e.to_string());
return;
}
// BASIC CONFIGURATION
CONFIG.lock().unwrap().load();
if !CONFIG.lock().unwrap().config.has_key("iota_id") {
CONFIG.lock().unwrap().change("iota_id", Uuid::new_v4());
CONFIG.lock().unwrap().save();
CONFIG.lock().unwrap().update();
}
// USER MANAGEMENT
let _ = user_manager::load_users().await;
if let Err(_) = user_manager::load_users().await {
log_message_trans("user_load_failed");
}
let mut sb = "".to_string();
for up in user_manager::get_users() {
sb = sb + "," + &up.user_id.to_string().as_str();
}
@ -91,7 +104,7 @@ async fn main() {
}
log_message(format!("Community IDS: {}", sb1));
let port = CONFIG.lock().unwrap().get_port();
if community_socket::start(port).await {
if start(port).await {
log_message(format("community_active", &[&port.to_string()]));
} else {
if port < 1024 {

View file

@ -1,5 +1,6 @@
use crate::data::communication::{CommunicationType, CommunicationValue, DataTypes};
use crate::gui::log_panel::{log_cv, log_message};
use crate::gui::log_panel::{log_cv, log_message, log_message_trans};
use crate::langu::language_manager::format;
use crate::users::contact::Contact;
use crate::users::user_community_util::UserCommunityUtil;
use crate::util::chat_files;
@ -16,6 +17,7 @@ use tokio::time::{Duration, Instant, sleep};
use tokio_tungstenite::{
MaybeTlsStream, WebSocketStream, connect_async, tungstenite::protocol::Message,
};
use tungstenite::Utf8Bytes;
use uuid::Uuid;
#[derive(Clone)]
@ -71,8 +73,8 @@ impl OmikronConnection {
*self.pingpong.lock().await = Some(handle);
break;
}
Err(_) => {
log_message("CONNECTION FAILED");
Err(e) => {
log_message(format("connection_failed", &[&e.to_string().as_str()]));
*self.is_connected.lock().await = false;
sleep(Duration::from_secs(2)).await;
}
@ -80,7 +82,7 @@ impl OmikronConnection {
}
}
pub async fn send_message(&self, msg: String) {
Self::send_message_static(&self.writer, msg).await;
Self::send_message_static(&self.writer, msg).await
}
/// Listener for all incoming messages
@ -90,28 +92,37 @@ impl OmikronConnection {
WebSocketStream<MaybeTlsStream<TcpStream>>,
>,
) {
let waiting = self.waiting.clone();
let writer = self.writer.clone();
let is_connected = self.is_connected.clone();
let sel = self.clone();
let waiting_out = self.waiting.clone();
let writer_out = self.writer.clone();
let is_connected_out = self.is_connected.clone();
let sel_out = self.clone();
tokio::spawn(async move {
while let Some(msg) = read_half.next().await {
let waiting = waiting_out.clone();
let writer = writer_out.clone();
let is_connected = is_connected_out.clone();
let sel = sel_out.clone();
tokio::spawn(async move {
match msg {
Ok(Message::Close(Some(frame))) => {
log_message(format!("[Omikron] Closed: {:?}", frame));
*is_connected.lock().await = false;
break;
return;
}
Ok(Message::Text(text)) => {
let mut cv = CommunicationValue::from_json(&text);
if cv.is_type(CommunicationType::pong) {
sel.handle_pong(&cv, true).await;
continue;
return;
}
// ************************************************ //
// Direct messages //
// ************************************************ //
log_cv(&cv);
if let Some(x) = waiting.lock().await.remove(&cv.get_id()) {
x(cv);
return;
}
if cv.is_type(CommunicationType::message_other_iota) {
let sender_id = &cv.get_sender().unwrap();
let receiver_id = &cv.get_receiver().unwrap();
@ -126,7 +137,8 @@ impl OmikronConnection {
*sender_id,
cv.get_data(DataTypes::content).unwrap().as_str().unwrap(),
);
let response = CommunicationValue::new(CommunicationType::message_live)
let response =
CommunicationValue::new(CommunicationType::message_live)
.with_id(cv.get_id())
.with_receiver(cv.get_receiver().unwrap())
.add_data(
@ -146,22 +158,10 @@ impl OmikronConnection {
response.to_json().to_string(),
)
.await;
continue;
return;
}
if cv.is_type(CommunicationType::message_send) {
/* DATA CONTAINER:
"sent_by_self": true,
"timestamp": unixTimestamp,
"files": [ // wenn keine files dann weglassen
{
"name": "<name>",
"id": "<uuid>",
"type": "[ image | image_top_right | file ]"
}
],
"content": "<enc markdown>"
*/
let my_id = cv.get_sender().unwrap();
let other_id = Uuid::from_str(
&*cv.get_data(DataTypes::receiver_id).unwrap().to_string(),
@ -177,8 +177,13 @@ impl OmikronConnection {
other_id,
&*cv.get_data(DataTypes::content).unwrap().to_string(),
);
let ack = CommunicationValue::ack_message(cv.get_id(), my_id);
Self::send_message_static(&writer.clone(), ack.to_json().to_string())
let ack = CommunicationValue::new(CommunicationType::message)
.with_id(cv.get_id())
.with_receiver(my_id);
Self::send_message_static(
&writer.clone(),
ack.to_json().to_string(),
)
.await;
let forward = CommunicationValue::forward_to_other_iota(&mut cv);
Self::send_message_static(
@ -186,7 +191,7 @@ impl OmikronConnection {
forward.to_json().to_string(),
)
.await;
continue;
return;
}
if cv.is_type(CommunicationType::messages_get) {
@ -214,9 +219,12 @@ impl OmikronConnection {
.with_receiver(my_id)
.add_data(DataTypes::messages, messages);
Self::send_message_static(&writer.clone(), resp.to_json().to_string())
Self::send_message_static(
&writer.clone(),
resp.to_json().to_string(),
)
.await;
continue;
return;
}
if cv.is_type(CommunicationType::get_chats) {
@ -226,9 +234,12 @@ impl OmikronConnection {
.with_id(cv.get_id())
.with_receiver(user_id)
.add_data(DataTypes::user_ids, users);
Self::send_message_static(&writer.clone(), resp.to_json().to_string())
Self::send_message_static(
&writer.clone(),
resp.to_json().to_string(),
)
.await;
continue;
return;
}
if cv.is_type(CommunicationType::add_chat) {
@ -249,9 +260,12 @@ impl OmikronConnection {
let resp = CommunicationValue::new(CommunicationType::add_chat)
.with_id(cv.get_id())
.with_receiver(user_id);
Self::send_message_static(&writer.clone(), resp.to_json().to_string())
Self::send_message_static(
&writer.clone(),
resp.to_json().to_string(),
)
.await;
continue;
return;
}
if cv.is_type(CommunicationType::add_community) {
@ -263,25 +277,35 @@ impl OmikronConnection {
cv.get_data(DataTypes::community_title).unwrap().to_string(),
cv.get_data(DataTypes::position).unwrap().to_string(),
);
let resp = CommunicationValue::new(CommunicationType::add_community)
let resp =
CommunicationValue::new(CommunicationType::add_community)
.with_id(cv.get_id())
.with_receiver(cv.get_sender().unwrap());
Self::send_message_static(&writer.clone(), resp.to_json().to_string())
Self::send_message_static(
&writer.clone(),
resp.to_json().to_string(),
)
.await;
continue;
return;
}
if cv.is_type(CommunicationType::get_communities) {
let resp = CommunicationValue::new(CommunicationType::get_communities)
let resp =
CommunicationValue::new(CommunicationType::get_communities)
.with_id(cv.get_id())
.with_receiver(cv.get_sender().unwrap())
.add_array(
DataTypes::communities,
UserCommunityUtil::get_communities(cv.get_sender().unwrap()),
UserCommunityUtil::get_communities(
cv.get_sender().unwrap(),
),
);
Self::send_message_static(&writer.clone(), resp.to_json().to_string())
Self::send_message_static(
&writer.clone(),
resp.to_json().to_string(),
)
.await;
continue;
return;
}
if cv.is_type(CommunicationType::remove_community) {
@ -291,21 +315,26 @@ impl OmikronConnection {
.unwrap()
.to_string(),
); // needs UserCommunityUtil
let resp = CommunicationValue::new(CommunicationType::remove_community)
let resp =
CommunicationValue::new(CommunicationType::remove_community)
.with_id(cv.get_id())
.with_receiver(cv.get_sender().unwrap());
Self::send_message_static(&writer.clone(), resp.to_json().to_string())
Self::send_message_static(
&writer.clone(),
resp.to_json().to_string(),
)
.await;
continue;
return;
}
}
Err(e) => {
log_message(format!("[Omikron] Error: {}", e));
*is_connected.lock().await = false;
break;
return;
}
_ => {}
}
});
}
});
}
@ -324,8 +353,12 @@ impl OmikronConnection {
) {
let mut guard = writer.lock().await;
if let Some(writer) = guard.as_mut() {
writer.send(Message::Text(msg)).await;
writer.flush().await;
if let Ok(_) = writer.send(Message::Text(Utf8Bytes::from(msg))).await {
if let Ok(_) = writer.flush().await {
return;
}
}
}
log_message_trans("send_message_failed");
}
}

View file

@ -1,9 +1,7 @@
use crate::APP_STATE;
use crate::data::communication::{CommunicationType, CommunicationValue, DataTypes};
use crate::omikron::omikron_connection::OmikronConnection;
use json::JsonValue;
use json::number::Number;
use std::sync::Arc;
use tokio::time::Instant;
use uuid::Uuid;

1
src/server/mod.rs Normal file
View file

@ -0,0 +1 @@
pub mod socket;

View file

@ -1,9 +1,15 @@
use crate::communities::{community_connection::CommunityConnection, community_manager};
use futures::StreamExt;
use std::sync::Arc;
use async_tungstenite::{WebSocketStream, accept_hdr_async, tungstenite::protocol::Message};
use futures::{SinkExt, StreamExt};
use std::{sync::Arc, time::Duration};
use tokio::net::TcpListener;
use tokio_tungstenite::accept_hdr_async;
use tungstenite::handshake::server::{Request, Response};
use tokio_util::compat::{Compat, TokioAsyncReadCompatExt};
use tungstenite::connect;
use tungstenite::{
Utf8Bytes,
handshake::server::{Request, Response},
};
pub async fn start(port: u16) -> bool {
let listener = TcpListener::bind(format!("0.0.0.0:{}", port)).await;
@ -19,20 +25,21 @@ pub async fn start(port: u16) -> bool {
path = format!("{}", &req.uri().path());
Ok(response)
};
let ws_stream = match accept_hdr_async(stream, callback).await {
let ws_stream = match accept_hdr_async(stream.compat(), callback).await {
Ok(ws) => ws,
Err(e) => {
Err(_) => {
return;
}
};
let (reader, writer) = ws_stream.split();
if path.starts_with("/community/") {
let community_id = path.split("/").nth(2).unwrap();
if let Some(community) = community_manager::get_community(community_id).await {
let community_conn: Arc<CommunityConnection> =
Arc::from(CommunityConnection::new(ws_stream, community));
Arc::from(CommunityConnection::new(reader, writer, community));
loop {
let msg_result = {
let mut session_lock = community_conn.session.lock().await;
let mut session_lock = community_conn.receiver.write().await;
session_lock.next().await
};
@ -40,13 +47,16 @@ pub async fn start(port: u16) -> bool {
Some(Ok(msg)) => {
if msg.is_text() {
let text = msg.into_text().unwrap();
community_conn.clone().handle_message(text).await;
community_conn
.clone()
.handle_message(text.to_string())
.await;
} else if msg.is_close() {
community_conn.handle_close().await;
return;
}
}
Some(Err(e)) => {
Some(Err(_)) => {
community_conn.handle_close().await;
return;
}

View file

@ -7,18 +7,6 @@ pub struct Contact {
pub user_id: Option<Uuid>,
pub user_name: Option<String>,
pub last_message_at: Option<i64>,
pub user_status: UserStatus,
pub about: Option<String>,
}
#[derive(Debug, Clone)]
pub enum UserStatus {
Online,
Do_Not_Disturb,
WC,
Away,
User_Offline,
Iota_Offline,
}
impl Default for Contact {
@ -31,30 +19,16 @@ impl Default for Contact {
user_id: None,
user_name: None,
last_message_at: Some(now),
user_status: UserStatus::User_Offline,
about: None,
}
}
}
impl Contact {
pub fn new_with_time(last_message_at: i64, user_id: Uuid) -> Self {
Contact {
user_id: Some(user_id),
user_name: None,
last_message_at: Some(last_message_at),
user_status: UserStatus::User_Offline,
about: None,
}
}
pub fn new(user_id: Uuid) -> Self {
Contact {
user_id: Some(user_id),
user_name: None,
last_message_at: None,
user_status: UserStatus::User_Offline,
about: None,
}
}
pub fn set_last_message_at(&mut self, p0: i64) {
@ -74,12 +48,6 @@ impl Contact {
}
obj
}
pub fn from_string(s: &str) -> Contact {
let parsed: JsonValue = JsonValue::from(s);
Self::from_json(&parsed)
}
pub fn from_json(o: &JsonValue) -> Contact {
let user_id = o["user_id"].as_str().and_then(|s| Uuid::parse_str(s).ok());
@ -91,51 +59,6 @@ impl Contact {
user_id,
user_name,
last_message_at,
user_status: UserStatus::User_Offline, // default
about: None,
}
}
pub fn info(&self) -> JsonValue {
let mut obj = self.to_json();
if let Some(id) = &self.user_id {
obj["user_id"] = JsonValue::from(id.to_string());
}
if let Some(name) = &self.user_name {
obj["user_name"] = JsonValue::from(name.as_str());
}
obj
}
// getters & setters
pub fn get_about(&self) -> Option<&String> {
self.about.as_ref()
}
pub fn set_about(&mut self, about: String) {
self.about = Some(about);
}
pub fn get_user_id(&self) -> Option<Uuid> {
self.user_id
}
pub fn set_user_id(&mut self, id: Uuid) {
self.user_id = Some(id);
}
pub fn get_user_name(&self) -> Option<&String> {
self.user_name.as_ref()
}
pub fn set_user_name(&mut self, name: String) {
self.user_name = Some(name);
}
pub fn get_user_status(&self) -> &UserStatus {
&self.user_status
}
pub fn set_user_status(&mut self, status: UserStatus) {
self.user_status = status;
}
}

View file

@ -75,13 +75,6 @@ pub fn get_users() -> Vec<UserProfile> {
USERS.lock().unwrap().clone()
}
pub fn add_user(up: UserProfile) {
let mut users = USERS.lock().unwrap();
users.retain(|u| u.user_id != up.user_id);
users.push(up);
*UNIQUE.lock().unwrap() = true;
}
pub fn remove_user(user_id: Uuid) {
let mut users = USERS.lock().unwrap();
users.retain(|u| u.user_id != user_id);

View file

@ -45,11 +45,7 @@ impl ConfigUtil {
pub fn update(&mut self) {
if self.unique {
let _ = self.save();
}
}
pub fn save(&self) {
save_file("", "config.json", &self.config.to_string());
}
}
}