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