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