use std::fmt::Debug;
use async_trait::async_trait;
use axum::{
extract::{
Path, State,
ws::{WebSocket, WebSocketUpgrade},
},
http::Method,
response::{IntoResponse, Response},
routing::any,
};
use opentalk_roomserver_types::{
client_parameters::ClientParameters, signaling::signaling_context::SignalingClientContext,
};
use opentalk_types_common::{rooms::RoomId, roomserver::Token};
use tracing::{Instrument as _, Span};
use super::Router;
use crate::v1::cors;
pub(crate) fn routes<B: SignalingBackend + 'static>(state: B) -> Router<B> {
Router::new()
.route("/signaling/{token}", any(open_signaling_socket::<B>))
.layer(cors::cors_layer(
state,
[Method::GET, Method::OPTIONS, Method::HEAD],
))
}
#[utoipa::path(
get,
path = "/signaling/{token}",
responses(
(status = StatusCode::SWITCHING_PROTOCOLS, description = "Successfully upgraded connection to WebSocket"),
(status = StatusCode::NOT_FOUND, description = "The requested room does not exist"),
(status = StatusCode::UNAUTHORIZED, description = "The token was invalid"),
(status = StatusCode::INTERNAL_SERVER_ERROR, description = "An internal server error occurred"),
),
params(
("token" = Token, Path, description = "The roomserver token that authorizes the user")
),
security(),
)]
#[tracing::instrument(name = "/signaling/{token}", level = "info", skip_all, fields(opentalk.room_id = "unknown"))]
async fn open_signaling_socket<B: SignalingBackend + 'static>(
State(ctx): State<B>,
Path(token): Path<Token>,
ws: WebSocketUpgrade,
) -> Result<Response, B::Error> {
tracing::debug!("Received signaling connection request");
let signaling_context = ctx.consume_token(token).await?;
let room_id = signaling_context.room_id;
ctx.ensure_room_exists(room_id).await?;
if signaling_context
.client_parameters
.kind
.is_guest_or_callin()
{
ctx.reject_if_guest_access_not_allowed(room_id).await?;
}
let span = Span::current();
span.record("opentalk.room_id", room_id.to_string());
let response = ws.on_upgrade(move |socket| {
handle_socket(socket, ctx, room_id, signaling_context.client_parameters).instrument(span)
});
Ok(response)
}
async fn handle_socket<B: SignalingBackend + 'static>(
socket: WebSocket,
ctx: B,
room_id: RoomId,
client_parameters: ClientParameters,
) {
tracing::debug!("Upgrade to websocket connection");
if let Err(e) = ctx
.accept_client_stream(socket, room_id, client_parameters)
.await
{
tracing::info!("Failed to accept client stream: {e:?}");
}
}
#[async_trait]
pub trait SignalingBackend: Clone + Send + Sync + std::fmt::Debug {
type Error: Debug + IntoResponse;
async fn ensure_room_exists(&self, room_id: RoomId) -> Result<(), Self::Error>;
async fn consume_token(&self, token: Token) -> Result<SignalingClientContext, Self::Error>;
async fn reject_if_guest_access_not_allowed(&self, room_id: RoomId) -> Result<(), Self::Error>;
async fn room_id(&self, token: Token) -> Option<RoomId>;
async fn allowed_origins(&self, room_id: RoomId) -> Result<Vec<String>, Self::Error>;
async fn accept_client_stream(
&self,
socket: WebSocket,
room_id: RoomId,
client_parameters: ClientParameters,
) -> Result<(), Self::Error>;
}