cloudflare_calls_rs/
lib.rs1use anyhow::Result;
2use reqwest::{Method, Response};
3use schema::{
4 NewSessionRequest, NewSessionResponse, SessionDescription, SessionState, TrackObject,
5 TracksRequest, TracksResponse,
6};
7use serde::Serialize;
8
9pub mod schema;
10
11#[derive(Clone)]
12pub struct CallsApp {
13 pub app_id: String,
14 app_secret: String,
15 client: reqwest::Client,
16 base_path: String,
17}
18
19impl Default for CallsApp {
20 fn default() -> Self {
21 let app_id = std::env::var("APP_ID").unwrap();
22 let app_secret = std::env::var("APP_SECRET").unwrap();
23 let base_path = "https://rtc.live.cloudflare.com/v1".to_string();
24 let base_path = format!("{}/apps/{}", base_path, app_id);
25
26 CallsApp {
27 app_id,
28 app_secret,
29 client: reqwest::Client::new(),
30 base_path,
31 }
32 }
33}
34
35impl CallsApp {
36 pub async fn build_request<T: Serialize>(
37 &self,
38 path: &str,
39 body: &T,
40 method: Method,
41 ) -> Result<Response> {
42 let url = format!("{}/{}", self.base_path, path);
43
44 self.client
45 .request(method, &url)
46 .bearer_auth(&self.app_secret)
47 .json(&body)
48 .send()
49 .await
50 .map_err(|e| e.into())
51 }
52
53 pub async fn new_session(
54 &self,
55 new_session: &SessionDescription,
56 ) -> Result<NewSessionResponse> {
57 let url = "sessions/new".to_string();
58
59 if new_session.sdp_type != schema::SdpType::Offer {
60 return Err(anyhow::anyhow!(
61 "Session description must be of type offer, when creating session"
62 ));
63 }
64
65 let body = NewSessionRequest {
66 session_description: new_session.clone(),
67 };
68
69 let response = self.build_request(&url, &body, Method::POST).await?;
70
71 if !response.status().is_success() {
73 return Err(anyhow::anyhow!("Failed to create new session"));
74 }
75
76 response
77 .json::<NewSessionResponse>()
78 .await
79 .map_err(|e| e.into())
80 }
81
82 pub async fn add_tracks(
83 &self,
84 session_id: &str,
85 track_objects: Vec<TrackObject>,
86 offer_sdp: Option<String>,
87 ) -> Result<TracksResponse> {
88 let url = format!("sessions/{}/tracks/new", session_id);
89
90 let body = match offer_sdp {
91 Some(offer) => TracksRequest {
92 session_description: Some(SessionDescription {
93 sdp_type: schema::SdpType::Offer,
94 sdp: offer,
95 }),
96 tracks: track_objects,
97 },
98 None => TracksRequest {
99 session_description: None,
100 tracks: track_objects,
101 },
102 };
103
104 let response = self.build_request(&url, &body, Method::POST).await?;
105
106 if !response.status().is_success() {
107 let text = response.text().await?;
108 return Err(anyhow::anyhow!(text));
109 }
110
111 response
112 .json::<TracksResponse>()
113 .await
114 .map_err(|e| e.into())
115 }
116
117 pub async fn renegotiate(
118 &self,
119 session_id: &str,
120 session_description: SessionDescription,
121 ) -> Result<()> {
122 let url = format!("sessions/{}/renegotiate", session_id);
123 let body = NewSessionRequest {
124 session_description,
125 };
126
127 let response = self.build_request(&url, &body, Method::PUT).await?;
128
129 if !response.status().is_success() {
130 let text = response.text().await?;
131 return Err(anyhow::anyhow!(text));
132 }
133
134 Ok(())
135 }
136
137 pub async fn get_session_state(&self, session_id: &str) -> Result<SessionState> {
138 let url = format!("sessions/{}", session_id);
139
140 let response = self.build_request(&url, &(), Method::GET).await?;
141
142 if !response.status().is_success() {
143 let text = response.text().await?;
144 return Err(anyhow::anyhow!(text));
145 }
146
147 response.json::<SessionState>().await.map_err(|e| e.into())
148 }
149
150 pub async fn close_tracks(
151 &self,
152 session_id: &str,
153 track_objects: Vec<TrackObject>,
154 sdp: Option<String>,
155 ) -> Result<TracksResponse> {
156 let url = format!("sessions/{}/tracks/close", session_id);
157
158 let body = match sdp {
159 Some(sdp) => TracksRequest {
160 session_description: Some(SessionDescription {
161 sdp_type: schema::SdpType::Offer,
162 sdp,
163 }),
164 tracks: track_objects,
165 },
166 None => TracksRequest {
167 session_description: None,
168 tracks: track_objects,
169 },
170 };
171
172 let response = self.build_request(&url, &body, Method::POST).await?;
173
174 if !response.status().is_success() {
175 let text = response.text().await?;
176 return Err(anyhow::anyhow!(text));
177 }
178
179 response
180 .json::<TracksResponse>()
181 .await
182 .map_err(|e| e.into())
183 }
184}
185
186#[cfg(test)]
187mod tests {
188 use super::*;
189
190 #[test]
191 fn it_works() {
192 std::env::set_var("APP_ID", "123");
194 std::env::set_var("APP_SECRET", "456");
195
196 let app = CallsApp::default();
197
198 assert_eq!(app.app_id, "123");
199 }
200}