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                room: Some(&room),
 72                ..Default::default()
 73            },
 74            proto::RoomParticipantIdentity {
 75                room: room.clone(),
 76                identity,
 77            },
 78        );
 79        async move {
 80            let _: proto::RemoveParticipantResponse = response.await?;
 81            Ok(())
 82        }
 83    }
 84
 85    pub fn room_token(&self, room: &str, identity: &str) -> Result<String> {
 86        token::create(
 87            &self.key,
 88            &self.secret,
 89            Some(identity),
 90            token::VideoGrant {
 91                room: Some(room),
 92                room_join: Some(true),
 93                can_publish: Some(true),
 94                can_subscribe: Some(true),
 95                ..Default::default()
 96            },
 97        )
 98    }
 99
100    fn request<Req, Res>(
101        &self,
102        path: &str,
103        grant: token::VideoGrant,
104        body: Req,
105    ) -> impl Future<Output = Result<Res>>
106    where
107        Req: Message,
108        Res: Default + Message,
109    {
110        let client = self.http.clone();
111        let token = token::create(&self.key, &self.secret, None, grant);
112        let url = format!("{}/{}", self.url, path);
113        log::info!("Request {}: {:?}", url, body);
114        async move {
115            let token = token?;
116            let response = client
117                .post(&url)
118                .header(CONTENT_TYPE, "application/protobuf")
119                .bearer_auth(token)
120                .body(body.encode_to_vec())
121                .send()
122                .await?;
123
124            if response.status().is_success() {
125                log::info!("Response {}: {:?}", url, response.status());
126                Ok(Res::decode(response.bytes().await?)?)
127            } else {
128                log::error!("Response {}: {:?}", url, response.status());
129                Err(anyhow!(
130                    "POST {} failed with status code {:?}, {:?}",
131                    url,
132                    response.status(),
133                    response.text().await
134                ))
135            }
136        }
137    }
138}