//! # 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, peerId: Option, data: Option, } #[derive(Message)] #[rtype(result = "()")] enum RTCMessages { Close, SendMessage(serde_json::Value), } #[allow(non_snake_case)] #[derive(Serialize)] struct CallsConfig { allowVideo: bool, iceServers: Vec, } #[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> = None; impl actix::Actor for RtcRelayActor { type Context = actix_web_actors::ws::WebsocketContext; 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> for RtcRelayActor { fn handle(&mut self, msg: Result, 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::(&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 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> { 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 { 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(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(()) }