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