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