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