1
0
mirror of https://gitlab.com/comunic/comunicapiv3 synced 2025-03-14 01:42:37 +00:00
comunicapiv3/src/controllers/calls_controller.rs

426 lines
14 KiB
Rust
Raw Normal View History

2020-07-13 11:49:14 +02:00
//! # Calls controller
//!
//! @author Pierre Hubert
use std::collections::HashMap;
2021-02-11 18:29:29 +01:00
use webrtc_sdp::attribute_type::SdpAttribute;
2021-02-10 17:32:14 +01:00
use crate::api_data::call_member_info::CallMemberInfo;
2021-02-12 13:52:47 +01:00
use crate::api_data::call_peer_interrupted_streaming::CallPeerInterruptedStreamingAPI;
2021-02-11 19:21:24 +01:00
use crate::api_data::call_peer_ready::CallPeerReadyAPI;
2021-02-10 17:16:52 +01:00
use crate::api_data::joined_call_message::JoinedCallMessage;
2021-02-10 18:04:03 +01:00
use crate::api_data::left_call_message::LeftCallMessage;
2021-02-11 19:07:30 +01:00
use crate::api_data::new_call_signal::NewCallSignalAPI;
2021-02-08 18:24:28 +01:00
use crate::api_data::user_calls_config::UserCallsConfig;
2020-07-13 11:49:14 +02:00
use crate::controllers::routes::RequestResult;
2021-02-10 17:16:52 +01:00
use crate::controllers::user_ws_controller;
2021-02-05 09:11:30 +01:00
use crate::data::base_request_handler::BaseRequestHandler;
2021-02-12 13:52:47 +01:00
use crate::data::call_signal::{CallSignal, CloseCallStream, IceCandidate, NewUserCallSignal, SdpType, UserCallOfferRequest};
2021-02-08 18:24:28 +01:00
use crate::data::config::conf;
2021-02-10 18:04:03 +01:00
use crate::data::conversation::ConvID;
2021-02-10 17:16:52 +01:00
use crate::data::error::{ExecError, Res};
2020-07-13 11:49:14 +02:00
use crate::data::http_request_handler::HttpRequestHandler;
2021-02-11 18:29:29 +01:00
use crate::data::user::UserID;
2021-02-10 18:04:03 +01:00
use crate::data::user_ws_connection::{ActiveCall, UserWsConnection};
2021-02-10 17:16:52 +01:00
use crate::data::user_ws_message::UserWsMessage;
2021-02-08 18:24:28 +01:00
use crate::data::user_ws_request_handler::UserWsRequestHandler;
2021-02-10 17:16:52 +01:00
use crate::helpers::{calls_helper, conversations_helper, events_helper};
use crate::helpers::events_helper::Event;
2020-07-13 11:49:14 +02:00
2021-02-11 18:29:29 +01:00
impl UserWsRequestHandler {
/// Get the ID of a call included in a WebSocket request
fn post_call_id(&mut self, name: &str) -> Res<ConvID> {
let conv_id = self.post_u64(name)?;
if !self.get_conn().is_having_call_with_conversation(&conv_id) {
self.forbidden("You do not belong to this call!".to_string())?;
}
Ok(conv_id)
}
/// Get the ID of a peer for a call included in the WebSocket request
fn post_call_peer_id(&mut self, call_id: &ConvID, name: &str) -> Res<UserID> {
let peer_id = UserID::new(self.post_u64(name)?);
if peer_id == self.user_id_ref()? {
return Ok(peer_id);
}
let mut found = false;
user_ws_controller::foreach_connection(|p| {
if !found && p.user_id == peer_id && p.is_having_call_with_conversation(call_id) {
found = true;
}
Ok(())
})?;
if !found {
self.forbidden("This peer is not a member of the call !".to_string())?;
}
Ok(peer_id)
}
2021-02-11 19:21:24 +01:00
2021-02-12 13:52:47 +01:00
/// Get call information, if available
fn call_info(&mut self, call_id: &ConvID) -> Res<ActiveCall> {
let call = self.get_conn()
.active_call
.clone()
.ok_or(ExecError::new("No call found !"))?;
if &call.conv_id != call_id {
self.bad_request("The call active is not the one requested!".to_string())?;
}
Ok(call)
}
2021-02-11 19:21:24 +01:00
/// Update call information
fn update_call<F>(&mut self, update: F) -> Res where F: FnOnce(&mut ActiveCall) {
self.update_conn(|conn| {
if let Some(call) = &mut conn.active_call {
update(call);
}
})?;
Ok(())
}
2021-02-11 18:29:29 +01:00
}
2020-07-13 11:49:14 +02:00
/// Get legacy call configuration
pub fn get_legacy_config(r: &mut HttpRequestHandler) -> RequestResult {
let mut map = HashMap::new();
map.insert("enabled", false);
r.set_response(map)
2021-02-08 18:24:28 +01:00
}
/// Get calls configuration
pub fn get_config(r: &mut UserWsRequestHandler) -> RequestResult {
// Check whether the user is the member of a call or not
if let None = r.get_conn().active_call {
r.forbidden("You do not belong to any call yet!".to_string())?;
}
2021-02-08 18:24:28 +01:00
if let Some(conf) = conf().rtc_relay.as_ref()
{
return r.set_response(UserCallsConfig::new(conf));
}
r.internal_error(ExecError::boxed_new("Missing calls configuration!"))
2021-02-10 17:16:52 +01:00
}
/// Check out whether a conversation is having a call or not
pub fn is_conversation_having_call(conv_id: &ConvID) -> bool {
let mut found = false;
let res = user_ws_controller::foreach_connection(|f| {
if found || f.is_having_call_with_conversation(conv_id) {
found = true;
}
Ok(())
});
if let Err(e) = res {
eprintln!("Failed to check if a conversation is having call! Conversation: {} / Error: {:#?}", conv_id, e);
}
found
}
2021-02-10 17:16:52 +01:00
/// Join a call
pub fn join_call(r: &mut UserWsRequestHandler) -> RequestResult {
let conv_id = r.post_conv_id("convID")?;
// Check if the conversation can have a call
let conv = conversations_helper::get_single(conv_id, r.user_id_ref()?)?;
if !calls_helper::can_have_call(&conv) {
r.forbidden("This conversation can not be used to make calls!".to_string())?;
}
2021-02-10 18:26:19 +01:00
// Remove any other active call with current WebSocket
if let Some(call) = &r.get_conn().active_call {
make_user_leave_call(&call.conv_id, r.get_conn())?;
}
// Remove any other active connection to current call of current user
user_ws_controller::foreach_connection(|conn| {
if &conn.user_id != r.user_id_ref()? || conn.session.eq(&r.get_conn().session) {
return Ok(());
}
if let Some(call) = &conn.active_call {
if call.conv_id == conv_id {
make_user_leave_call(&call.conv_id, conn)?;
}
}
Ok(())
})?;
2021-02-10 17:16:52 +01:00
r.update_conn(|r| r.active_call = Some(ActiveCall {
conv_id,
ready: false,
}))?;
// Propagate event
2021-02-10 18:04:03 +01:00
events_helper::propagate_event(&Event::UserJoinedCall(&conv_id, r.user_id_ref()?))?;
2021-02-10 17:16:52 +01:00
Ok(())
}
2021-02-10 18:04:03 +01:00
/// Leave a call
pub fn leave_call(r: &mut UserWsRequestHandler) -> RequestResult {
// Warning ! For some technical reasons, we do not check if the user
// really belongs to the conversation, so be careful when manipulating
// conversation ID here
let conv_id = r.post_u64("convID")?;
// Check if the user was not in the conversation
if !r.get_conn().is_having_call_with_conversation(&conv_id) {
return r.success("Left call!");
}
// Make the user leave the call
make_user_leave_call(&conv_id, r.get_conn())?;
r.success("Left call!")
}
2021-02-10 17:32:14 +01:00
/// Get the list of members of a call
pub fn get_members_list(r: &mut UserWsRequestHandler) -> RequestResult {
let conv_id = r.post_call_id("callID")?;
let mut list = vec![];
user_ws_controller::foreach_connection(|conn| {
if conn.is_having_call_with_conversation(&conv_id) {
list.push(CallMemberInfo::new(&conn.user_id, &conn.active_call.as_ref().unwrap()));
}
Ok(())
})?;
r.set_response(list)
}
2021-02-11 18:29:29 +01:00
/// Get the hash associated to a call
pub fn gen_call_hash(call_id: &ConvID, peer_id: &UserID) -> String {
format!("{}-{}", call_id, peer_id.id())
}
/// Handles client signal
pub fn on_client_signal(r: &mut UserWsRequestHandler) -> RequestResult {
let call_id = r.post_call_id("callID")?;
let peer_id = r.post_call_peer_id(&call_id, "peerID")?;
let sig_type = r.post_string("type")?;
let data: serde_json::Value = serde_json::from_str(&r.post_string("data")?)?;
let data = data
.as_object()
.ok_or(ExecError::boxed_new("Signal data is not an object !"))?;
let signal = match sig_type.as_str() {
"SDP" => {
// Get SDP type
let sdp_type = SdpType::from_str(data.get("type")
.unwrap_or(&serde_json::Value::Null)
.as_str()
.ok_or(ExecError::boxed_new("Missing SDP type !"))?
)?;
let sdp = data
.get("sdp")
.unwrap_or(&serde_json::Value::Null)
.as_str()
.ok_or(ExecError::boxed_new("Failed to get sdp message data!"))?;
let parsed_sdp = webrtc_sdp::parse_sdp(sdp, false)?;
CallSignal::SDP(sdp.to_string(), sdp_type, parsed_sdp)
}
"CANDIDATE" => {
let candidate = data
.get("candidate")
.unwrap_or(&serde_json::Value::Null)
.as_str()
.ok_or(ExecError::boxed_new("Failed to get candidate message data!"))?
.to_string();
let sdp_m_line_index = data
.get("sdpMLineIndex")
.unwrap_or(&serde_json::Value::Null)
.as_u64()
.ok_or(ExecError::boxed_new("Failed to get sdp_mline_index data!"))?;
let sdp_mid = data
.get("sdpMid")
.unwrap_or(&serde_json::Value::Null)
.as_str()
.ok_or(ExecError::boxed_new("Failed to get sdpMid message data!"))?
.to_string();
let parsed_candidate: SdpAttribute = candidate.trim().parse()?;
CallSignal::Candidate(IceCandidate {
candidate,
sdp_m_line_index,
sdp_mid,
}, parsed_candidate)
}
_ => {
r.forbidden("Invalid signal type!".to_string())?;
unreachable!()
}
};
events_helper::propagate_event(&Event::NewUserCallSignal(&NewUserCallSignal {
call_hash: gen_call_hash(&call_id, &peer_id),
user_id: if r.user_id_ref()? == &peer_id { None } else { Some(r.user_id()?) },
signal,
raw_data: r.post_string("data")?,
}))?;
r.success("Signal sent")
}
2021-02-11 19:21:24 +01:00
/// Mark user ready for streaming
pub fn mark_user_ready(r: &mut UserWsRequestHandler) -> Res {
let call_id = r.post_call_id("callID")?;
let user_id = r.user_id()?;
r.update_call(|call| call.ready = true)?;
user_ws_controller::send_message_to_specific_connections(
|c| c.user_id != &user_id && c.is_having_call_with_conversation(&call_id),
|_| UserWsMessage::no_id_message("call_peer_ready", CallPeerReadyAPI::new(&call_id, r.user_id_ref()?)),
None::<fn(&_) -> _>,
)?;
r.success("Information propagated.")
}
2021-02-12 13:32:36 +01:00
/// Request an offer from the server
pub fn request_offer(r: &mut UserWsRequestHandler) -> Res {
let call_id = r.post_call_id("callID")?;
// The ID of the user we stream the audio / video from
let peer_id = r.post_call_peer_id(&call_id, "peerID")?;
if !peer_id.is_valid() || peer_id == r.user_id_ref()? {
r.forbidden("You can not request an offer for yourself!".to_string())?;
}
events_helper::propagate_event(&Event::UserRequestedCallOffer(&UserCallOfferRequest {
call_hash: gen_call_hash(&call_id, &peer_id),
user_id: r.user_id()?,
}))?;
r.success("Request sent")
}
2021-02-12 13:52:47 +01:00
/// Notify the user stopped to stream
pub fn stop_streaming(r: &mut UserWsRequestHandler) -> Res {
let call_id = r.post_call_id("callID")?;
let user_id = r.user_id()?;
// Propagate notification only if required
if r.call_info(&call_id)?.ready {
r.update_call(|c| c.ready = false)?;
// Notify all other users
user_ws_controller::send_message_to_specific_connections(
|c| c.is_having_call_with_conversation(&call_id) && c.user_id != &user_id,
|_| UserWsMessage::no_id_message("call_peer_interrupted_streaming", CallPeerInterruptedStreamingAPI::new(&call_id, &user_id)),
None::<fn(&_) -> _>,
)?;
}
// Notify proxy
events_helper::propagate_event(&Event::CloseCallStream(&CloseCallStream {
call_hash: gen_call_hash(&call_id, &user_id),
peer_id: None,
}))?;
r.success("ok")
}
2021-02-10 17:32:14 +01:00
2021-02-10 18:04:03 +01:00
/// Make the user leave the call
pub fn make_user_leave_call(conv_id: &ConvID, connection: &UserWsConnection) -> Res {
connection.clone().replace(|c| c.active_call = None);
// Notify user (if possible)
if connection.session.connected() {
user_ws_controller::send_to_client(connection, &UserWsMessage::no_id_message("call_closed", conv_id)?)?;
}
// TODO : call main stream (sender)
// TODO : close receiver streams (other users streams)
// Create a notification
events_helper::propagate_event(&Event::UserLeftCall(conv_id, &connection.user_id))?;
Ok(())
}
2021-02-10 17:16:52 +01:00
/// Events handler
pub fn handle_event(e: &events_helper::Event) -> Res {
match e {
Event::UserJoinedCall(conv_id, user_id) => {
user_ws_controller::send_message_to_specific_connections(
|c| c.is_having_call_with_conversation(conv_id) && &c.user_id != user_id,
|_| UserWsMessage::no_id_message("user_joined_call", JoinedCallMessage::new(conv_id, user_id)),
None::<fn(&_) -> _>,
)?;
}
2021-02-10 18:04:03 +01:00
Event::UserLeftCall(conv_id, user_id) => {
user_ws_controller::send_message_to_specific_connections(
|c| c.is_having_call_with_conversation(conv_id),
|_| UserWsMessage::no_id_message("user_left_call", LeftCallMessage::new(conv_id, user_id)),
None::<fn(&_) -> _>,
)?;
}
Event::UserWsClosed(c) => {
if let Some(call) = c.active_call.clone() {
make_user_leave_call(&call.conv_id, c)?;
}
}
2021-02-11 19:07:30 +01:00
Event::NewRTCRelayMessage(msg) => {
// Get call hash
let split: Vec<&str> = msg.call_hash.split("-").collect();
if split.len() != 2 {
return Ok(());
}
let call_id = split[0].parse::<u64>()?;
let peer_id = UserID::new(split[1].parse::<u64>()?);
let target_user = UserID::new(msg.peer_id.parse::<u64>()?);
let target_user = match target_user.is_valid() {
true => target_user,
false => peer_id.clone()
};
user_ws_controller::send_message_to_specific_connections(
|c| c.user_id == target_user && c.is_having_call_with_conversation(&call_id),
|_| UserWsMessage::no_id_message("new_call_signal", NewCallSignalAPI::new(&call_id, &peer_id, &msg.data)?),
None::<fn(&_) -> _>,
)?;
}
2021-02-10 17:16:52 +01:00
_ => {}
}
Ok(())
2020-07-13 11:49:14 +02:00
}