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