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 {
70 room_admin: Some(true),
71 room: Some(&room),
72 ..Default::default()
73 },
74 proto::RoomParticipantIdentity {
75 room: room.clone(),
76 identity,
77 },
78 );
79 async move {
80 let _: proto::RemoveParticipantResponse = response.await?;
81 Ok(())
82 }
83 }
84
85 pub fn room_token(&self, room: &str, identity: &str) -> Result<String> {
86 token::create(
87 &self.key,
88 &self.secret,
89 Some(identity),
90 token::VideoGrant {
91 room: Some(room),
92 room_join: Some(true),
93 can_publish: Some(true),
94 can_subscribe: Some(true),
95 ..Default::default()
96 },
97 )
98 }
99
100 fn request<Req, Res>(
101 &self,
102 path: &str,
103 grant: token::VideoGrant,
104 body: Req,
105 ) -> impl Future<Output = Result<Res>>
106 where
107 Req: Message,
108 Res: Default + Message,
109 {
110 let client = self.http.clone();
111 let token = token::create(&self.key, &self.secret, None, grant);
112 let url = format!("{}/{}", self.url, path);
113 log::info!("Request {}: {:?}", url, body);
114 async move {
115 let token = token?;
116 let response = client
117 .post(&url)
118 .header(CONTENT_TYPE, "application/protobuf")
119 .bearer_auth(token)
120 .body(body.encode_to_vec())
121 .send()
122 .await?;
123
124 if response.status().is_success() {
125 log::info!("Response {}: {:?}", url, response.status());
126 Ok(Res::decode(response.bytes().await?)?)
127 } else {
128 log::error!("Response {}: {:?}", url, response.status());
129 Err(anyhow!(
130 "POST {} failed with status code {:?}, {:?}",
131 url,
132 response.status(),
133 response.text().await
134 ))
135 }
136 }
137 }
138}