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