api.rs

  1use crate::{proto, token};
  2use anyhow::{anyhow, Result};
  3use async_trait::async_trait;
  4use prost::Message;
  5use reqwest::header::CONTENT_TYPE;
  6use std::{future::Future, sync::Arc, time::Duration};
  7
  8#[async_trait]
  9pub trait Client: Send + Sync {
 10    fn url(&self) -> &str;
 11    async fn create_room(&self, name: String) -> Result<()>;
 12    async fn delete_room(&self, name: String) -> Result<()>;
 13    async fn remove_participant(&self, room: String, identity: String) -> Result<()>;
 14    fn room_token(&self, room: &str, identity: &str) -> Result<String>;
 15}
 16
 17#[derive(Clone)]
 18pub struct LiveKitClient {
 19    http: reqwest::Client,
 20    url: Arc<str>,
 21    key: Arc<str>,
 22    secret: Arc<str>,
 23}
 24
 25impl LiveKitClient {
 26    pub fn new(mut url: String, key: String, secret: String) -> Self {
 27        if url.ends_with('/') {
 28            url.pop();
 29        }
 30
 31        Self {
 32            http: reqwest::ClientBuilder::new()
 33                .timeout(Duration::from_secs(5))
 34                .build()
 35                .unwrap(),
 36            url: url.into(),
 37            key: key.into(),
 38            secret: secret.into(),
 39        }
 40    }
 41
 42    fn request<Req, Res>(
 43        &self,
 44        path: &str,
 45        grant: token::VideoGrant,
 46        body: Req,
 47    ) -> impl Future<Output = Result<Res>>
 48    where
 49        Req: Message,
 50        Res: Default + Message,
 51    {
 52        let client = self.http.clone();
 53        let token = token::create(&self.key, &self.secret, None, grant);
 54        let url = format!("{}/{}", self.url, path);
 55        log::info!("Request {}: {:?}", url, body);
 56        async move {
 57            let token = token?;
 58            let response = client
 59                .post(&url)
 60                .header(CONTENT_TYPE, "application/protobuf")
 61                .bearer_auth(token)
 62                .body(body.encode_to_vec())
 63                .send()
 64                .await?;
 65
 66            if response.status().is_success() {
 67                log::info!("Response {}: {:?}", url, response.status());
 68                Ok(Res::decode(response.bytes().await?)?)
 69            } else {
 70                log::error!("Response {}: {:?}", url, response.status());
 71                Err(anyhow!(
 72                    "POST {} failed with status code {:?}, {:?}",
 73                    url,
 74                    response.status(),
 75                    response.text().await
 76                ))
 77            }
 78        }
 79    }
 80}
 81
 82#[async_trait]
 83impl Client for LiveKitClient {
 84    fn url(&self) -> &str {
 85        &self.url
 86    }
 87
 88    async fn create_room(&self, name: String) -> Result<()> {
 89        let _: proto::Room = self
 90            .request(
 91                "twirp/livekit.RoomService/CreateRoom",
 92                token::VideoGrant {
 93                    room_create: Some(true),
 94                    ..Default::default()
 95                },
 96                proto::CreateRoomRequest {
 97                    name,
 98                    ..Default::default()
 99                },
100            )
101            .await?;
102        Ok(())
103    }
104
105    async fn delete_room(&self, name: String) -> Result<()> {
106        let _: proto::DeleteRoomResponse = self
107            .request(
108                "twirp/livekit.RoomService/DeleteRoom",
109                token::VideoGrant {
110                    room_create: Some(true),
111                    ..Default::default()
112                },
113                proto::DeleteRoomRequest { room: name },
114            )
115            .await?;
116        Ok(())
117    }
118
119    async fn remove_participant(&self, room: String, identity: String) -> Result<()> {
120        let _: proto::RemoveParticipantResponse = self
121            .request(
122                "twirp/livekit.RoomService/RemoveParticipant",
123                token::VideoGrant::to_admin(&room),
124                proto::RoomParticipantIdentity {
125                    room: room.clone(),
126                    identity,
127                },
128            )
129            .await?;
130        Ok(())
131    }
132
133    fn room_token(&self, room: &str, identity: &str) -> Result<String> {
134        token::create(
135            &self.key,
136            &self.secret,
137            Some(identity),
138            token::VideoGrant::to_join(room),
139        )
140    }
141}