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