cloudflare_calls_rs/
lib.rs

1use 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        // check if the response code is 201
72        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        // set  demo envs
193        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}