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 {
 70                room_admin: Some(true),
 71                ..Default::default()
 72            },
 73            proto::RoomParticipantIdentity { room, identity },
 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 {
 87                room: Some(room),
 88                room_join: Some(true),
 89                can_publish: Some(true),
 90                can_subscribe: Some(true),
 91                ..Default::default()
 92            },
 93        )
 94    }
 95
 96    fn request<Req, Res>(
 97        &self,
 98        path: &str,
 99        grant: token::VideoGrant,
100        body: Req,
101    ) -> impl Future<Output = Result<Res>>
102    where
103        Req: Message,
104        Res: Default + Message,
105    {
106        let client = self.http.clone();
107        let token = token::create(&self.key, &self.secret, None, grant);
108        let url = format!("{}/{}", self.url, path);
109        async move {
110            let token = token?;
111            let response = client
112                .post(&url)
113                .header(CONTENT_TYPE, "application/protobuf")
114                .bearer_auth(token)
115                .body(body.encode_to_vec())
116                .send()
117                .await?;
118            if response.status().is_success() {
119                Ok(Res::decode(response.bytes().await?)?)
120            } else {
121                Err(anyhow!(
122                    "POST {} failed with status code {:?}, {:?}",
123                    url,
124                    response.status(),
125                    response.text().await
126                ))
127            }
128        }
129    }
130}