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 ..Default::default()
72 },
73 proto::RoomParticipantIdentity { room, identity },
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 {
87 room: Some(room),
88 room_join: Some(true),
89 can_publish: Some(true),
90 can_subscribe: Some(true),
91 ..Default::default()
92 },
93 )
94 }
95
96 fn request<Req, Res>(
97 &self,
98 path: &str,
99 grant: token::VideoGrant,
100 body: Req,
101 ) -> impl Future<Output = Result<Res>>
102 where
103 Req: Message,
104 Res: Default + Message,
105 {
106 let client = self.http.clone();
107 let token = token::create(&self.key, &self.secret, None, grant);
108 let url = format!("{}/{}", self.url, path);
109 async move {
110 let token = token?;
111 let response = client
112 .post(&url)
113 .header(CONTENT_TYPE, "application/protobuf")
114 .bearer_auth(token)
115 .body(body.encode_to_vec())
116 .send()
117 .await?;
118 if response.status().is_success() {
119 Ok(Res::decode(response.bytes().await?)?)
120 } else {
121 Err(anyhow!(
122 "POST {} failed with status code {:?}, {:?}",
123 url,
124 response.status(),
125 response.text().await
126 ))
127 }
128 }
129 }
130}