api.rs

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