New Calling structure using https://livekit.io/ (: Incomplete, Untested

:)
This commit is contained in:
Alex Emmet 2025-11-24 22:42:02 +00:00
commit 48ead50947
13 changed files with 1167 additions and 568 deletions

2
.gitignore vendored
View file

@ -8,7 +8,7 @@ target
# MSVC Windows builds of rustc generate these, which store debugging information # MSVC Windows builds of rustc generate these, which store debugging information
*.pdb *.pdb
*.env
# Generated by cargo mutants # Generated by cargo mutants
# Contains mutation testing data # Contains mutation testing data
**/mutants.out*/W **/mutants.out*/W

988
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -45,3 +45,7 @@ walkdir = "2.5.0"
warp = "*" warp = "*"
x448 = { version = "*" } x448 = { version = "*" }
x509 = "*" x509 = "*"
log = "0.4"
livekit = "0.7.25"
livekit-api = "0.4.10"
dotenv = "0.15.0"

View file

@ -1,283 +0,0 @@
use async_tungstenite::{WebSocketReceiver, WebSocketSender, tungstenite::Message};
use std::{any::Any, sync::Arc};
use tokio::sync::{
RwLock,
mpsc::{UnboundedSender, unbounded_channel},
};
use tokio_util::compat::Compat;
use tungstenite::Utf8Bytes;
use uuid::Uuid;
use crate::{
calls::call_manager,
data::communication::{CommunicationType, CommunicationValue, DataTypes},
util::print::{PrintType, line, line_err},
};
use json::JsonValue;
pub struct CallConnection {
pub sender: Arc<RwLock<WebSocketSender<Compat<tokio::net::TcpStream>>>>,
pub receiver: Arc<RwLock<WebSocketReceiver<Compat<tokio::net::TcpStream>>>>,
pub tx: UnboundedSender<Utf8Bytes>,
pub user_id: Arc<RwLock<Option<Uuid>>>,
pub call_id: Arc<RwLock<Option<Uuid>>>,
}
impl CallConnection {
pub async fn new(
sender: WebSocketSender<Compat<tokio::net::TcpStream>>,
receiver: WebSocketReceiver<Compat<tokio::net::TcpStream>>,
) -> Arc<Self> {
let (tx, mut rx) = unbounded_channel::<Utf8Bytes>();
let conn = Arc::new(Self {
sender: Arc::new(RwLock::new(sender)),
receiver: Arc::new(RwLock::new(receiver)),
tx,
user_id: Arc::new(RwLock::new(None)),
call_id: Arc::new(RwLock::new(None)),
});
// Spawn sender task to forward tx → session
let sender_clone = Arc::clone(&conn.sender);
tokio::spawn(async move {
while let Some(msg) = rx.recv().await {
let mut sess = sender_clone.write().await;
let _ = sess.send(Message::Text(msg)).await;
}
});
conn
}
/// Handle a single incoming message
pub async fn handle_message(self: Arc<Self>, msg: Utf8Bytes) {
let mut cv = CommunicationValue::from_json(&msg);
if let Some(sender) = *self.user_id.read().await {
cv = cv.with_sender(sender);
}
if cv.is_type(CommunicationType::webrtc_sdp)
| cv.is_type(CommunicationType::webrtc_ice)
| cv.is_type(CommunicationType::watch_stream)
{
line(
PrintType::CallIn,
&format!(
"{:?} : {} -> {} ",
&cv.type_id(),
&cv.get_sender(),
&cv.get_data(DataTypes::receiver_id).unwrap()
),
);
} else if !cv.is_type(CommunicationType::ping) {
line(PrintType::CallIn, &cv.to_json().to_string());
}
match cv.comm_type {
CommunicationType::identification => self.handle_identification(cv).await,
CommunicationType::ping => self.handle_ping(cv).await,
CommunicationType::client_changed => self.handle_client_changed(cv).await,
CommunicationType::start_stream | CommunicationType::end_stream => {
self.handle_stream_toggle(cv).await
}
CommunicationType::webrtc_sdp
| CommunicationType::webrtc_ice
| CommunicationType::watch_stream => self.handle_direct_relay(cv).await,
_ => {}
}
}
async fn handle_identification(&self, cv: CommunicationValue) {
let user_str = cv
.get_data(DataTypes::user_id)
.and_then(|v| v.as_str())
.unwrap_or("");
let call_str = cv
.get_data(DataTypes::call_id)
.and_then(|v| v.as_str())
.unwrap_or("");
let secret_sha = cv
.get_data(DataTypes::call_secret_sha)
.and_then(|v| v.as_str())
.unwrap_or("");
let (Ok(uid), Ok(cid)) = (Uuid::parse_str(user_str), Uuid::parse_str(call_str)) else {
return;
};
{
*self.user_id.write().await = Some(uid);
*self.call_id.write().await = Some(cid);
}
let group = call_manager::get_or_create_group(cid, secret_sha).await;
if let None = group {
self.send_message(&CommunicationValue::new(
CommunicationType::error_invalid_secret,
))
.await;
return;
}
let group = group.unwrap();
// Build broadcast
let broadcast = CommunicationValue::new(CommunicationType::client_connected)
.with_id(cv.get_id().clone())
.add_data_str(DataTypes::user_id, uid.to_string())
.add_data_str(DataTypes::call_state, "muted".to_string());
{
group
.lock()
.await
.broadcast(&broadcast.to_json().to_string())
.await;
}
{
group.lock().await.add_member(uid, self.tx.clone());
}
// Build response
let mut response = CommunicationValue::new(CommunicationType::identification_response)
.with_id(cv.get_id().clone());
let mut users = JsonValue::new_object();
{
for caller in group.lock().await.callers.values() {
let mut user_info = JsonValue::new_object();
let _ = user_info.insert("state", JsonValue::from("muted"));
let _ = user_info.insert("streaming", JsonValue::from(false));
let _ = users.insert(&caller.user_id.read().await.to_string(), user_info);
}
}
response = response.add_data(DataTypes::about, users);
self.send_message(&response).await;
}
async fn handle_ping(&self, cv: CommunicationValue) {
let resp = CommunicationValue::new(CommunicationType::pong).with_id(cv.get_id().clone());
self.send_message(&resp).await;
}
async fn handle_client_changed(&self, cv: CommunicationValue) {
let uid = match *self.user_id.read().await {
Some(id) => id,
None => return,
};
let cid = match *self.call_id.read().await {
Some(id) => id,
None => return,
};
if let Some(JsonValue::String(_state_str)) = cv.get_data(DataTypes::call_state) {
let Some(group_lock) = call_manager::get_group(cid).await else {
return;
};
let mut group = group_lock.lock().await;
if let Some(_caller) = group.caller_state_mut(&uid) {
// caller_state_change(caller, &state_str);
}
let mut bc = cv.clone();
bc = bc.add_data(DataTypes::sender_id, JsonValue::String(uid.to_string()));
group.broadcast(&bc.to_json().to_string()).await;
}
}
async fn handle_stream_toggle(&self, cv: CommunicationValue) {
let uid = match *self.user_id.read().await {
Some(id) => id,
None => return,
};
let cid = match *self.call_id.read().await {
Some(id) => id,
None => return,
};
if let Some(group_lock) = call_manager::get_group(cid).await {
let group = group_lock.lock().await;
let _streaming = cv.comm_type == CommunicationType::start_stream;
let mut bc = cv.clone();
bc = bc.add_data(DataTypes::sender_id, JsonValue::String(uid.to_string()));
group.broadcast(&bc.to_json().to_string()).await;
}
}
async fn handle_direct_relay(&self, cv: CommunicationValue) {
let uid = match *self.user_id.read().await {
Some(id) => id,
None => return,
};
let cid = match *self.call_id.read().await {
Some(id) => id,
None => return,
};
if let Some(JsonValue::String(receiver_str)) = cv.get_data(DataTypes::receiver_id) {
if let Ok(receiver_id) = Uuid::parse_str(&receiver_str) {
if let Some(group) = call_manager::get_group(cid).await {
let mut bc = cv.clone();
bc = bc.add_data(DataTypes::sender_id, JsonValue::String(uid.to_string()));
group
.lock()
.await
.send_to(&receiver_id, &bc.to_json().to_string())
.await;
line(
PrintType::CallOut,
&format!("Forwarded WebRTC message to receiver: {}", receiver_str),
);
} else {
line_err(PrintType::CallOut, "Failed to find the group for the call.");
}
} else {
line_err(PrintType::CallOut, "Invalid receiver_id in WebRTC message.");
}
} else {
line_err(PrintType::CallOut, "Missing receiver_id in WebRTC message.");
}
}
pub async fn send_message(&self, cv: &CommunicationValue) {
if !cv.is_type(CommunicationType::pong) {
line(PrintType::CallOut, &cv.to_json().to_string());
}
let mut session = self.sender.write().await;
if let Err(e) = session
.send(Message::Text(Utf8Bytes::from(cv.to_json().to_string())))
.await
{
line_err(
PrintType::CallOut,
&format!("Failed to send message to client: {}", e),
);
}
}
pub async fn close(&self) {
let (uid, cid) = { (*self.user_id.read().await, *self.call_id.read().await) };
if let (Some(uid), Some(cid)) = (uid, cid) {
if let Some(group) = call_manager::get_group(cid).await {
group
.lock()
.await
.broadcast(
&CommunicationValue::new(CommunicationType::client_disconnected)
.add_data_str(DataTypes::user_id, uid.to_string())
.to_json()
.to_string(),
)
.await;
group.lock().await.disconnect_member(uid).await;
}
call_manager::remove_inactive().await;
}
let mut session = self.sender.write().await;
let _ = session.close(None).await;
}
pub async fn handle_close(&self) {
self.close().await;
}
}

View file

@ -1,60 +1,27 @@
use crate::calls::caller::Caller; use std::sync::Arc;
use std::{collections::HashMap, sync::Arc};
use tokio::sync::mpsc::UnboundedSender; use tokio::sync::RwLock;
use tungstenite::Utf8Bytes;
use uuid::Uuid; use uuid::Uuid;
use crate::calls::caller::Caller;
pub struct CallGroup { pub struct CallGroup {
pub callers: HashMap<Uuid, Arc<Caller>>, pub call_id: Uuid,
pub secret_hash: String, pub members: RwLock<Vec<Arc<Caller>>>,
} }
impl CallGroup { impl CallGroup {
pub fn new(secret_hash: String) -> Self { pub fn new(call_id: Uuid, user: Arc<Caller>) -> Self {
Self { CallGroup {
callers: HashMap::new(), call_id,
secret_hash: secret_hash, members: RwLock::new(vec![user]),
} }
} }
pub fn add_member(&mut self, user_id: Uuid, tx: UnboundedSender<Utf8Bytes>) { pub async fn add_member(self: Arc<Self>, member: Uuid, inviter: Uuid) {
self.callers self.members
.insert(user_id, Arc::new(Caller::new(user_id, tx))); .write()
} .await
pub fn remove_member(&mut self, user_id: Uuid) { .push(Arc::new(Caller::new(member, inviter, self.call_id)));
self.callers.remove(&user_id);
}
pub fn get_member(&self, user_id: &Uuid) -> Option<&Arc<Caller>> {
self.callers.get(user_id)
}
pub async fn disconnect_member(&mut self, user_id: Uuid) {
if let Some(caller) = self.callers.get(&user_id) {
caller.disconnect().await;
}
}
pub async fn is_empty(&self) -> bool {
for caller in self.callers.values() {
if caller.is_connected().await {
return false;
}
}
true
}
pub async fn send_to(&self, user_id: &Uuid, message: &str) {
if let Some(caller) = self.callers.get(user_id) {
caller.send(Utf8Bytes::from(message.to_string())).await;
}
}
pub async fn broadcast(&self, message: &str) {
for caller in self.callers.values() {
caller.send(Utf8Bytes::from(message.to_string())).await;
}
}
pub fn caller_state_mut(&mut self, user_id: &Uuid) -> Option<&Arc<Caller>> {
self.callers.get(user_id)
} }
} }

View file

@ -1,87 +1,81 @@
use crate::calls::call_group::CallGroup;
use futures::lock::Mutex; // Used only to protect CallGroup contents
use once_cell::sync::Lazy;
use std::collections::HashMap;
use std::sync::Arc; use std::sync::Arc;
use tokio::sync::RwLock; // Used to protect the global HashMap
use once_cell::sync::Lazy;
use tokio::sync::RwLock;
use uuid::Uuid; use uuid::Uuid;
// FIX 1: Simplify the global state. Only use RwLock to protect the HashMap. use crate::calls::{call_group::CallGroup, caller::Caller};
// The inner Arc<Mutex<CallGroup>> protects the contents of each group.
pub static CALL_GROUPS: Lazy<RwLock<HashMap<Uuid, Arc<Mutex<CallGroup>>>>> =
Lazy::new(|| RwLock::new(HashMap::new()));
/// Retrieves a CallGroup by ID, if it exists. static CALL_GROUPS: Lazy<RwLock<Vec<Arc<CallGroup>>>> = Lazy::new(|| RwLock::new(Vec::new()));
pub async fn get_group(call_id: Uuid) -> Option<Arc<Mutex<CallGroup>>> {
// Acquire read lock (very fast, non-blocking for other readers)
CALL_GROUPS
.read()
.await
.get(&call_id) // Use get(), as we only need to clone the Arc
.cloned()
}
/// Retrieves an existing group or creates a new one, performing a secret check. pub async fn get_call_invites(user_id: Uuid) -> Vec<Arc<Caller>> {
pub async fn get_or_create_group(call_id: Uuid, secret: &str) -> Option<Arc<Mutex<CallGroup>>> { let mut callers = Vec::new();
// Phase 1: Check existence under a READ lock. for cg in CALL_GROUPS.read().await.iter() {
{ for member in cg.members.read().await.iter() {
let groups = CALL_GROUPS.read().await; if member.user_id == user_id {
if let Some(group_arc) = groups.get(&call_id) { callers.push(member.clone());
// Found it. Release the global READ lock and check the secret.
// This await on the group's lock only blocks access to that *specific* group.
let group_lock = group_arc.lock().await;
if group_lock.secret_hash.eq(secret) {
return Some(group_arc.clone());
} else {
return None; // Secret mismatch
} }
} }
} // READ lock automatically released here. }
callers
}
// Phase 2: Not found, acquire a WRITE lock to create (only held for insertion). pub async fn get_call_groups(user_id: Uuid) -> Vec<Arc<CallGroup>> {
let mut groups = CALL_GROUPS.write().await; let mut call_groups = Vec::new();
for cg in CALL_GROUPS.read().await.iter() {
for member in cg.members.read().await.iter() {
if member.user_id == user_id {
call_groups.push(cg.clone());
}
}
}
call_groups
}
// FIX 2: Double-check (race condition prevention) - A group might have been created pub async fn get_call_token(user_id: Uuid, call_id: Uuid) -> Option<String> {
// between the read lock release and the write lock acquisition. let call_groups = CALL_GROUPS.read().await;
if let Some(group_arc) = groups.get(&call_id) { for cg in call_groups.iter() {
// Already created by another task, check secret again. if cg.call_id == call_id {
let group_lock = group_arc.lock().await; for member in cg.members.read().await.iter() {
if group_lock.secret_hash.eq(secret) { if member.user_id == user_id {
return Some(group_arc.clone()); return Some(member.create_token());
} else { }
}
return None; return None;
} }
} }
let caller = Arc::new(Caller::new(user_id, user_id, call_id));
// Still not found, proceed with creation. let call_group = CallGroup::new(call_id, caller.clone());
let new_group = Arc::new(Mutex::new(CallGroup::new(secret.to_string()))); {
groups.insert(call_id, new_group.clone()); CALL_GROUPS.write().await.push(Arc::new(call_group));
}
// WRITE lock automatically released here. Some(caller.create_token())
Some(new_group)
} }
/// Removes inactive groups. pub async fn add_invite(call_id: Uuid, inviter_id: Uuid, invitee_id: Uuid) -> bool {
pub async fn remove_inactive() { let call_groups = CALL_GROUPS.read().await;
// Collect keys to remove under a read lock first. for cg in call_groups.iter() {
let groups_to_check: Vec<Uuid> = CALL_GROUPS.read().await.keys().cloned().collect(); if cg.call_id == call_id {
for member in cg.members.read().await.iter() {
if member.user_id == inviter_id {
cg.clone().add_member(inviter_id, invitee_id).await;
return true;
}
}
return false;
}
}
false
}
let mut rem = Vec::new(); pub async fn get_call_group_by_user(user_id: Uuid) -> Option<Arc<CallGroup>> {
for call_id in groups_to_check { let call_groups = CALL_GROUPS.read().await;
// Retrieve the group Arc outside the global lock for cg in call_groups.iter() {
if let Some(group_arc) = get_group(call_id).await { for member in cg.members.read().await.iter() {
// FIX 3: Check for emptiness outside the global lock if member.user_id == user_id {
if group_arc.lock().await.is_empty().await { return Some(cg.clone());
rem.push(call_id);
} }
} }
} }
None
// Acquire write lock only to perform the removals.
if !rem.is_empty() {
let mut groups = CALL_GROUPS.write().await;
for call_id in rem {
groups.remove(&call_id);
}
}
} }

21
src/calls/call_util.rs Normal file
View file

@ -0,0 +1,21 @@
use livekit_api::access_token;
use std::env;
use uuid::Uuid;
pub fn create_token(
user_id: Uuid,
call_id: Uuid,
) -> Result<String, access_token::AccessTokenError> {
let api_key = env::var("LIVEKIT_API_KEY").expect("LIVEKIT_API_KEY is not set");
let api_secret = env::var("LIVEKIT_API_SECRET").expect("LIVEKIT_API_SECRET is not set");
let token = access_token::AccessToken::with_api_key(&api_key, &api_secret)
.with_identity(&user_id.to_string())
.with_grants(access_token::VideoGrants {
room_join: true,
room: call_id.to_string(),
..Default::default()
})
.to_jwt();
return token;
}

View file

@ -1,49 +1,26 @@
use std::sync::Arc;
use futures::lock::Mutex;
use tokio::sync::{RwLock, mpsc::UnboundedSender};
use tungstenite::Utf8Bytes;
use uuid::Uuid; use uuid::Uuid;
use crate::calls::call_util;
pub struct Caller { pub struct Caller {
pub user_id: RwLock<Uuid>, pub user_id: Uuid,
pub tx: Mutex<Option<UnboundedSender<Utf8Bytes>>>, pub call_id: Uuid,
pub user_state: RwLock<CallUserState>, pub inviters: Vec<Uuid>,
pub streaming: RwLock<bool>,
}
#[derive(Clone)]
pub enum CallUserState {
Active,
Muted,
Deafed,
Disconnected,
} }
impl Caller { impl Caller {
pub fn new(user_id: Uuid, tx: UnboundedSender<Utf8Bytes>) -> Self { pub fn new(user_id: Uuid, call_id: Uuid, inviter_id: Uuid) -> Self {
Self { Caller {
user_id: RwLock::new(user_id), user_id,
tx: Mutex::new(Some(tx)), call_id,
user_state: RwLock::new(CallUserState::Active), inviters: vec![inviter_id],
streaming: RwLock::new(false),
} }
} }
pub fn create_token(&self) -> String {
pub async fn send(&self, msg: impl Into<Utf8Bytes>) { if let Ok(token) = call_util::create_token(self.user_id, self.call_id) {
if let Some(tx) = &*self.tx.lock().await { token
let _ = tx.send(msg.into());
}
}
pub async fn disconnect(self: &Arc<Self>) {
*self.user_state.write().await = CallUserState::Disconnected;
*self.tx.lock().await = None;
}
pub async fn is_connected(&self) -> bool {
if let CallUserState::Disconnected = *self.user_state.read().await {
false
} else { } else {
true String::new()
} }
} }
} }

View file

@ -1,4 +1,4 @@
pub mod call_connection;
pub mod call_group; pub mod call_group;
pub mod call_manager; pub mod call_manager;
pub mod call_util;
pub mod caller; pub mod caller;

View file

@ -20,7 +20,6 @@ pub enum DataTypes {
user_state, user_state,
user_states, user_states,
user_pings, user_pings,
call_state,
screen_share, screen_share,
private_key_hash, private_key_hash,
accepted, accepted,
@ -36,10 +35,7 @@ pub enum DataTypes {
shared_secret_sign, shared_secret_sign,
shared_secret, shared_secret,
call_id, call_id,
call_name, call_token,
call_secret_sha,
call_secret,
shared_call_secret,
start_date, start_date,
end_date, end_date,
receiver_id, receiver_id,
@ -96,7 +92,6 @@ impl DataTypes {
"userstate" => DataTypes::user_state, "userstate" => DataTypes::user_state,
"userstates" => DataTypes::user_states, "userstates" => DataTypes::user_states,
"userpings" => DataTypes::user_pings, "userpings" => DataTypes::user_pings,
"callstate" => DataTypes::call_state,
"screenshare" => DataTypes::screen_share, "screenshare" => DataTypes::screen_share,
"privatekeyhash" => DataTypes::private_key_hash, "privatekeyhash" => DataTypes::private_key_hash,
"accepted" => DataTypes::accepted, "accepted" => DataTypes::accepted,
@ -112,10 +107,7 @@ impl DataTypes {
"sharedsecretsign" => DataTypes::shared_secret_sign, "sharedsecretsign" => DataTypes::shared_secret_sign,
"sharedsecret" => DataTypes::shared_secret, "sharedsecret" => DataTypes::shared_secret,
"callid" => DataTypes::call_id, "callid" => DataTypes::call_id,
"callname" => DataTypes::call_name, "calltoken" => DataTypes::call_token,
"callsecretsha" => DataTypes::call_secret_sha,
"callsecret" => DataTypes::call_secret,
"sharedcallsecret" => DataTypes::shared_call_secret,
"startdate" => DataTypes::start_date, "startdate" => DataTypes::start_date,
"enddate" => DataTypes::end_date, "enddate" => DataTypes::end_date,
"receiverid" => DataTypes::receiver_id, "receiverid" => DataTypes::receiver_id,
@ -209,10 +201,8 @@ pub enum CommunicationType {
start_stream, start_stream,
end_stream, end_stream,
watch_stream, watch_stream,
get_call, call_get,
new_call,
call_invite, call_invite,
end_call,
function, function,
update, update,
} }
@ -263,10 +253,8 @@ impl CommunicationType {
"startstream" => CommunicationType::start_stream, "startstream" => CommunicationType::start_stream,
"endstream" => CommunicationType::end_stream, "endstream" => CommunicationType::end_stream,
"watchstream" => CommunicationType::watch_stream, "watchstream" => CommunicationType::watch_stream,
"getcall" => CommunicationType::get_call, "callget" => CommunicationType::call_get,
"newcall" => CommunicationType::new_call,
"callinvite" => CommunicationType::call_invite, "callinvite" => CommunicationType::call_invite,
"endcall" => CommunicationType::end_call,
"function" => CommunicationType::function, "function" => CommunicationType::function,
"update" => CommunicationType::update, "update" => CommunicationType::update,
_ => CommunicationType::error, _ => CommunicationType::error,

View file

@ -7,23 +7,24 @@ mod util;
use async_tungstenite::accept_hdr_async; use async_tungstenite::accept_hdr_async;
use futures::StreamExt; use futures::StreamExt;
use livekit_api::services::room::{CreateRoomOptions, RoomClient};
use std::sync::Arc; use std::sync::Arc;
use tokio::net::TcpListener; use tokio::net::TcpListener;
use tokio_util::compat::TokioAsyncReadCompatExt; use tokio_util::compat::TokioAsyncReadCompatExt;
use tungstenite::handshake::server::{Request, Response}; use tungstenite::handshake::server::{Request, Response};
use crate::{ use crate::{
calls::call_connection::CallConnection, calls::call_util,
omega::omega_connection::OmegaConnection, omega::omega_connection::OmegaConnection,
rho::{client_connection::ClientConnection, iota_connection::IotaConnection}, rho::{client_connection::ClientConnection, iota_connection::IotaConnection},
util::config_util::CONFIG, util::{
util::print::{PrintType, line, line_err, print_start_message}, config_util::CONFIG,
print::{PrintType, line, line_err},
},
}; };
#[tokio::main] #[tokio::main]
async fn main() { async fn main() {
print_start_message();
tokio::spawn(async move { tokio::spawn(async move {
OmegaConnection::new().connect().await; OmegaConnection::new().connect().await;
}); });
@ -121,39 +122,6 @@ async fn main() {
} }
} }
} }
} else if path == "/ws/call/" {
line(PrintType::CallIn, "New Call connection");
let call_conn: Arc<CallConnection> =
Arc::from(CallConnection::new(sender, receiver).await);
loop {
let msg_result = {
let mut session_lock = call_conn.receiver.write().await;
session_lock.next().await
};
match msg_result {
Some(Ok(msg)) => {
if msg.is_text() {
let text = msg.into_text().unwrap();
call_conn.clone().handle_message(text).await;
} else if msg.is_close() {
line(PrintType::CallIn, "Call disconnected");
call_conn.handle_close().await;
return;
}
}
Some(Err(e)) => {
line_err(PrintType::CallIn, &format!("WebSocket error: {}", e));
call_conn.handle_close().await;
return;
}
None => {
line(PrintType::CallIn, "Call stream ended");
call_conn.handle_close().await;
return;
}
}
}
} }
}); });
} }

View file

@ -141,7 +141,7 @@ impl ClientConnection {
} }
// Handle get call requests // Handle get call requests
if cv.is_type(CommunicationType::get_call) { if cv.is_type(CommunicationType::call_get) {
self.handle_get_call(cv).await; self.handle_get_call(cv).await;
return; return;
} }
@ -303,13 +303,14 @@ impl ClientConnection {
} }
}; };
// Get call group - placeholder implementation let invited =
// let call_group = CallManager::get_call_group(call_id, &call_secret_sha.unwrap(), true).await; call_manager::add_invite(call_id, self.user_id.read().await.unwrap(), receiver_id)
// if call_group.is_none() { .await;
// self.send_error_response(&cv.get_id(), CommunicationType::error) if invited {
// .await; self.send_error_response(&cv.get_id(), CommunicationType::error)
// return; .await;
// } return;
}
// Find target RhoConnection // Find target RhoConnection
let target_rho = match rho_manager::get_rho_con_for_user(receiver_id).await { let target_rho = match rho_manager::get_rho_con_for_user(receiver_id).await {
@ -328,20 +329,16 @@ impl ClientConnection {
}; };
// Create and send call distribution message // Create and send call distribution message
let distribute = CommunicationValue::new(CommunicationType::new_call) let forward = CommunicationValue::new(CommunicationType::call_invite)
.with_receiver(receiver_id) .with_receiver(receiver_id)
.with_sender(sender_id) .with_sender(sender_id)
.add_data_str(DataTypes::call_id, call_id.to_string()) .add_data_str(DataTypes::call_id, call_id.to_string())
.add_data_str(DataTypes::receiver_id, receiver_id.to_string()) .add_data_str(DataTypes::receiver_id, receiver_id.to_string())
.add_data_str(DataTypes::sender_id, sender_id.to_string()); .add_data_str(DataTypes::sender_id, sender_id.to_string());
target_rho.message_to_client(distribute).await; target_rho.message_to_client(forward).await;
// Handle call group invitation logic here let response = CommunicationValue::new(CommunicationType::success).with_id(cv.get_id());
// This would require implementing CallGroup::Caller and related functionality
// Send success response
let response = CommunicationValue::new(CommunicationType::call_invite).with_id(cv.get_id());
self.send_message(&response).await; self.send_message(&response).await;
} }
@ -368,26 +365,17 @@ impl ClientConnection {
} }
}; };
// Get call group if let Some(token) = call_manager::get_call_token(user_id, call_id).await {
let mut response = CommunicationValue::new(CommunicationType::get_call) let response = CommunicationValue::new(CommunicationType::call_get)
.with_id(cv.get_id()) .with_id(cv.get_id())
.with_receiver(user_id); .with_receiver(user_id)
.add_data_str(DataTypes::call_token, token);
if let Some(_call_group) = call_manager::get_group(call_id).await { self.send_message(&response).await;
/*response = response
.add_data_str(DataTypes::call_state, call_group.call_state.to_string())
.add_data_str(DataTypes::start_date, call_group.started_at.to_string());
if call_group.lock(). != 0 {
response =
response.add_data_str(DataTypes::end_date, call_group.ended_at.to_string());
}
*/
} else { } else {
response = response.add_data_str(DataTypes::call_state, "DESTROYED".to_string()); self.send_error_response(&cv.get_id(), CommunicationType::error)
.await;
return;
} }
self.send_message(&response).await;
} }
/// Forward message to Iota /// Forward message to Iota

View file

@ -1,3 +1,5 @@
use crate::calls::call_group::CallGroup;
use crate::calls::call_manager;
use crate::util::print::PrintType; use crate::util::print::PrintType;
use crate::util::print::line; use crate::util::print::line;
use crate::util::print::line_err; use crate::util::print::line_err;
@ -5,12 +7,14 @@ use async_tungstenite::WebSocketReceiver;
use async_tungstenite::WebSocketSender; use async_tungstenite::WebSocketSender;
use async_tungstenite::tungstenite::Message; use async_tungstenite::tungstenite::Message;
use json::JsonValue; use json::JsonValue;
use json::parse;
use std::{ use std::{
collections::HashMap, collections::HashMap,
sync::{Arc, Weak}, sync::{Arc, Weak},
}; };
use tokio::sync::RwLock; use tokio::sync::RwLock;
use tokio_util::compat::Compat; use tokio_util::compat::Compat;
use tower::retry::backoff::InvalidBackoff;
use tungstenite::Utf8Bytes; use tungstenite::Utf8Bytes;
use uuid::Uuid; use uuid::Uuid;
@ -278,22 +282,44 @@ impl IotaConnection {
/// Handle GET_CHATS message /// Handle GET_CHATS message
async fn handle_get_chats(&self, cv: CommunicationValue) { async fn handle_get_chats(&self, cv: CommunicationValue) {
let receiver_id = cv.get_receiver(); let receiver_id = cv.get_receiver();
let interested_ids: Vec<Uuid> = Vec::new(); let mut interested_ids: Vec<Uuid> = Vec::new();
let calls: Vec<Arc<CallGroup>> = call_manager::get_call_groups(receiver_id).await;
let mut invites: HashMap<Uuid, Vec<Uuid>> = HashMap::new();
for call in calls {
for inviter in call.members.read().await.iter() {
let inviter_id = inviter.user_id;
if let Some(call_ids) = invites.get_mut(&inviter_id) {
call_ids.push(call.call_id);
} else {
invites.insert(inviter_id, vec![call.call_id]);
}
}
}
// Process contacts and add call information // Process contacts and add call information
// Enriched Contacts data:
// [{"user_id": "uuid", "calls": ["call_id"]}, {"user_id": "uuid"}]
let mut enriched_contacts = JsonValue::new_array();
// Contacts data:
// [{"user_id": "uuid"}, {"user_id": "uuid"}]
if let Some(contacts_data) = cv.get_data(DataTypes::user_ids) { if let Some(contacts_data) = cv.get_data(DataTypes::user_ids) {
/*let contacts: Vec<(Uuid, String)> = parse_contacts(contacts_data); if let JsonValue::Array(user_ids) = contacts_data {
for contact in &contacts { for user_id in user_ids {
interested_ids.push(contact.user_id); if let JsonValue::String(user_id_str) = user_id {
if let Ok(user_id) = Uuid::parse_str(user_id_str) {
interested_ids.push(user_id);
let mut enriched_contact = JsonValue::new_object();
let _ = enriched_contact.insert("user_id", user_id.to_string());
let _ = enriched_contact.insert("calls", JsonValue::new_array());
let _ = enriched_contacts.push(enriched_contact);
}
}
}
} else {
enriched_contacts = contacts_data.clone();
} }
// Get call invites for receiver
let invites = CallManager::get_call_invites(receiver_id).await;
// Enrich contacts with call information
let enriched_contacts = enrich_with_calls(contacts, invites);
cv = cv.add_data(DataTypes::user_ids, enriched_contacts.into());*/
} }
// Notify OmegaConnection about user states // Notify OmegaConnection about user states
@ -305,7 +331,8 @@ impl IotaConnection {
} }
// Forward to client // Forward to client
self.forward_to_client(cv).await; self.forward_to_client(cv.add_data(DataTypes::user_ids, enriched_contacts))
.await;
} }
/// Forward message to client /// Forward message to client