mirror of
https://gitlab.com/comunic/comunicapiv3
synced 2025-01-06 10:58:50 +00:00
319 lines
9.4 KiB
Rust
319 lines
9.4 KiB
Rust
//! # RTC Relay controller
|
|
//!
|
|
//! @author Pierre Hubert
|
|
|
|
use actix::{ActorContext, Addr, AsyncContext, Handler, StreamHandler};
|
|
use actix::prelude::*;
|
|
use actix_web_actors::ws::{Message, ProtocolError};
|
|
use serde::{Deserialize, Serialize};
|
|
use serde_json::Value;
|
|
|
|
use crate::data::call_signal::{CallSignal, NewRtcRelayMessage};
|
|
use crate::data::config::conf;
|
|
use crate::data::error::{ExecError, Res};
|
|
use crate::helpers::events_helper;
|
|
use crate::helpers::events_helper::Event;
|
|
|
|
struct RtcRelayActor {}
|
|
|
|
#[allow(non_snake_case)]
|
|
#[derive(Deserialize)]
|
|
struct RTCSocketMessage {
|
|
title: String,
|
|
callHash: Option<String>,
|
|
peerId: Option<String>,
|
|
data: Option<serde_json::Value>,
|
|
}
|
|
|
|
#[derive(Message)]
|
|
#[rtype(result = "()")]
|
|
enum RTCMessages {
|
|
Close,
|
|
SendMessage(serde_json::Value),
|
|
}
|
|
|
|
#[allow(non_snake_case)]
|
|
#[derive(Serialize)]
|
|
struct CallsConfig {
|
|
allowVideo: bool,
|
|
iceServers: Vec<String>,
|
|
}
|
|
|
|
#[derive(Serialize)]
|
|
struct CallsConfigWrapper {
|
|
title: String,
|
|
data: CallsConfig,
|
|
}
|
|
|
|
#[derive(Serialize)]
|
|
struct CallUserSignalData {
|
|
r#type: String,
|
|
data: serde_json::Value,
|
|
}
|
|
|
|
#[allow(non_snake_case)]
|
|
#[derive(Serialize)]
|
|
struct CallUserSignal {
|
|
title: String,
|
|
callHash: String,
|
|
peerId: String,
|
|
data: CallUserSignalData,
|
|
}
|
|
|
|
#[allow(non_snake_case)]
|
|
#[derive(Serialize)]
|
|
struct OfferRequest {
|
|
title: String,
|
|
callHash: String,
|
|
peerId: String,
|
|
}
|
|
|
|
type CloseCallStream = OfferRequest;
|
|
|
|
/// Current WebSocket connection
|
|
static mut ACTIVE_RTC_CONNECTION: Option<Addr<RtcRelayActor>> = None;
|
|
|
|
impl actix::Actor for RtcRelayActor {
|
|
type Context = actix_web_actors::ws::WebsocketContext<Self>;
|
|
|
|
fn started(&mut self, ctx: &mut Self::Context) {
|
|
// Replace known address of actor
|
|
unsafe {
|
|
ACTIVE_RTC_CONNECTION = Some(ctx.address());
|
|
}
|
|
|
|
println!("Started new WebSocket connection to RTC relay!");
|
|
|
|
// Send calls configuration to server
|
|
ctx.text(serde_json::to_string(&CallsConfigWrapper {
|
|
title: "config".to_string(),
|
|
data: CallsConfig {
|
|
allowVideo: conf().rtc_relay.as_ref().unwrap().allow_video,
|
|
iceServers: conf().rtc_relay.as_ref().unwrap().ice_servers.clone(),
|
|
},
|
|
}).unwrap())
|
|
}
|
|
|
|
fn stopped(&mut self, _ctx: &mut Self::Context) {
|
|
println!("Closed connection to RTC relay.");
|
|
|
|
// Propagate information
|
|
if let Err(e) = events_helper::propagate_event(&Event::ClosedRTCRelayWebSocket) {
|
|
eprintln!("Failed to propagate rtc closed event! {:#?}", e);
|
|
}
|
|
}
|
|
}
|
|
|
|
impl StreamHandler<Result<actix_web_actors::ws::Message, actix_web_actors::ws::ProtocolError>> for RtcRelayActor {
|
|
fn handle(&mut self, msg: Result<Message, ProtocolError>, ctx: &mut Self::Context) {
|
|
let msg = match msg {
|
|
Err(_) => {
|
|
ctx.stop();
|
|
return;
|
|
}
|
|
Ok(msg) => msg
|
|
};
|
|
|
|
if conf().verbose_mode {
|
|
println!("RTC RELAY WS MESSAGE: {:?}", msg);
|
|
}
|
|
|
|
// Handle messages
|
|
match msg {
|
|
Message::Text(txt) => {
|
|
match serde_json::from_str::<RTCSocketMessage>(&txt) {
|
|
Err(e) => {
|
|
eprintln!("Failed to parse a message from RTC proxy! {:#?}", e);
|
|
}
|
|
|
|
Ok(msg) => {
|
|
if let Err(e) = process_message_from_relay(&msg) {
|
|
eprintln!("Failed to process signal from RTC Relay! {:#?}", e);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Message::Binary(_) => {
|
|
eprintln!("RTC WS Message::Binary");
|
|
ctx.stop();
|
|
}
|
|
Message::Continuation(_) => {
|
|
eprintln!("RTC WS Message::Continuation");
|
|
ctx.stop();
|
|
}
|
|
Message::Ping(data) => {
|
|
ctx.pong(&data);
|
|
}
|
|
Message::Pong(_) => {}
|
|
Message::Close(_) => {
|
|
eprintln!("RTC WS Message::Close");
|
|
ctx.stop();
|
|
}
|
|
Message::Nop => {}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Process a message from the server
|
|
fn process_message_from_relay(msg: &RTCSocketMessage) -> Res {
|
|
match msg.title.as_str() {
|
|
"signal" => {
|
|
events_helper::propagate_event(&Event::NewRTCRelayMessage(&NewRtcRelayMessage {
|
|
call_hash: msg.callHash.clone().unwrap_or(String::new()),
|
|
peer_id: msg.peerId.clone().unwrap_or("0".to_string()),
|
|
data: serde_json::to_string(msg.data.as_ref().unwrap_or(&Value::Null))
|
|
.unwrap_or("failed to serialize signal data".to_string()),
|
|
}))?;
|
|
}
|
|
|
|
title => {
|
|
eprintln!("Unknown message '{}' from RTC proxy!", title);
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
impl Handler<RTCMessages> for RtcRelayActor {
|
|
type Result = ();
|
|
|
|
fn handle(&mut self, msg: RTCMessages, ctx: &mut Self::Context) -> Self::Result {
|
|
match msg {
|
|
|
|
// Close connection
|
|
RTCMessages::Close => {
|
|
ctx.close(None)
|
|
}
|
|
|
|
// Send message
|
|
RTCMessages::SendMessage(msg) => {
|
|
match serde_json::to_string(&msg) {
|
|
Ok(txt) => {
|
|
if conf().verbose_mode {
|
|
println!("API => RTC WS : {}", txt);
|
|
}
|
|
|
|
ctx.text(txt)
|
|
}
|
|
Err(e) => {
|
|
eprintln!("Failed to send message to RTC relay ! {:#?}", e);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Get current actix connection
|
|
fn get_active_connection() -> Option<Addr<RtcRelayActor>> {
|
|
let conn;
|
|
unsafe {
|
|
conn = ACTIVE_RTC_CONNECTION.clone();
|
|
}
|
|
conn
|
|
}
|
|
|
|
/// Check out whether a relay is currently connected to the API or not
|
|
pub fn is_connected() -> bool {
|
|
if let Some(conn) = get_active_connection() {
|
|
return conn.connected();
|
|
}
|
|
|
|
false
|
|
}
|
|
|
|
/// Establish a new connection with the RTC relay
|
|
///
|
|
/// Debug with
|
|
/// ```js
|
|
/// ws = new WebSocket("ws://0.0.0.0:3000/rtc_proxy/ws");
|
|
/// ws.onmessage = (msg) => console.log("WS msg", msg);
|
|
/// ws.onopen = () => console.log("Socket is open !");
|
|
/// ws.onerror = (e) => console.log("WS ERROR !", e);
|
|
/// ws.onclose = (e) => console.log("WS CLOSED!");
|
|
/// ```
|
|
pub async fn open_ws(req: actix_web::HttpRequest,
|
|
stream: actix_web::web::Payload) -> Result<actix_web::HttpResponse, actix_web::Error> {
|
|
let ip = req.peer_addr().unwrap();
|
|
|
|
// Check if video calls are enabled
|
|
if conf().rtc_relay.is_none() {
|
|
eprintln!("A relay from {} tried to connect to the server but the relay is disabled!", ip);
|
|
return Ok(actix_web::HttpResponse::BadRequest().body("RTC Relay not configured!"));
|
|
}
|
|
|
|
let conf = conf().rtc_relay.as_ref().unwrap();
|
|
|
|
// Check remote IP address
|
|
if !ip.ip().to_string().eq(&conf.ip) {
|
|
eprintln!("A relay from {} tried to connect to the server but the IP address is not authorized!", ip);
|
|
return Ok(actix_web::HttpResponse::Unauthorized().body("Access denied!"));
|
|
}
|
|
|
|
// Check the token
|
|
if !req.query_string().eq(&format!("token={}", &conf.token)) {
|
|
eprintln!("A relay from {} tried to connect with an invalid access token!", ip);
|
|
return Ok(actix_web::HttpResponse::Unauthorized().body("Invalid token!"));
|
|
}
|
|
|
|
// Close previous connection (if any)
|
|
if let Some(conn) = get_active_connection() {
|
|
conn.do_send(RTCMessages::Close);
|
|
}
|
|
|
|
// Start the actor
|
|
actix_web_actors::ws::start(RtcRelayActor {}, &req, stream)
|
|
}
|
|
|
|
/// Send a message to the relay
|
|
fn send_message_to_relay<T>(e: T) -> Res where T: Serialize {
|
|
let conn = get_active_connection()
|
|
.ok_or(ExecError::boxed_new("Connection to RTC relay missing!"))?;
|
|
|
|
conn.do_send(RTCMessages::SendMessage(serde_json::to_value(e)?));
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Events handler
|
|
pub fn handle_event(e: &events_helper::Event) -> Res {
|
|
match e {
|
|
Event::NewUserCallSignal(signal) => {
|
|
send_message_to_relay(CallUserSignal {
|
|
title: "signal".to_string(),
|
|
callHash: signal.call_hash.to_string(),
|
|
peerId: signal.user_id.as_ref().map(|i| i.id()).unwrap_or(0).to_string(),
|
|
data: CallUserSignalData {
|
|
r#type: match signal.signal {
|
|
CallSignal::SDP(_, _, _) => "SDP",
|
|
CallSignal::Candidate(_, _) => "CANDIDATE",
|
|
}.to_string(),
|
|
data: serde_json::from_str(&signal.raw_data)?,
|
|
},
|
|
})?;
|
|
}
|
|
|
|
Event::UserRequestedCallOffer(request) => {
|
|
send_message_to_relay(OfferRequest {
|
|
title: "request_offer".to_string(),
|
|
callHash: request.call_hash.to_string(),
|
|
peerId: request.user_id.id().to_string(),
|
|
})?;
|
|
}
|
|
|
|
Event::CloseCallStream(request) => {
|
|
send_message_to_relay(CloseCallStream {
|
|
title: "close_conn".to_string(),
|
|
callHash: request.call_hash.to_string(),
|
|
peerId: match &request.peer_id {
|
|
None => "0".to_string(),
|
|
Some(id) => id.id().to_string()
|
|
},
|
|
})?;
|
|
}
|
|
|
|
_ => {}
|
|
}
|
|
|
|
Ok(())
|
|
} |