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