clerk_fapi_rs/
clerk_http_client.rs

1use log::{debug, error, warn};
2use parking_lot::RwLock;
3use reqwest::header::{HeaderMap, HeaderValue};
4use reqwest::{Client as ReqwestClient, Request, Response};
5use std::sync::Arc;
6
7use crate::{clerk_state::ClerkState, configuration::ClientKind};
8
9/// Custom client wrapper that behaves like reqwest::Client but adds Clerk-specific functionality
10#[derive(Debug)]
11pub struct ClerkHttpClient {
12    inner: ReqwestClient,
13    state: Arc<RwLock<ClerkState>>,
14    client_kind: ClientKind,
15    dev_browser_token_id: RwLock<Option<String>>,
16}
17
18impl std::fmt::Display for ClerkHttpClient {
19    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
20        write!(f, "ClerkHttpClient")
21    }
22}
23
24/// ClerkHttpClient that mimics ReqwestClient with pre and post hooks
25/// To be able to run Clerk in non browser environment we need to
26/// identify our selves to Clerk with Client Authorization header
27/// We inject the header to the request and parse returned header
28/// and keep the ClerkState updated with most current value
29impl ClerkHttpClient {
30    /// Creates a new ClerkHttpClient
31    pub fn new(
32        client: ReqwestClient,
33        state: Arc<RwLock<ClerkState>>,
34        client_kind: ClientKind,
35    ) -> Self {
36        Self {
37            inner: client,
38            state,
39            client_kind,
40            dev_browser_token_id: RwLock::new(None),
41        }
42    }
43
44    /// When running in browser one needs "DevBrowser auth" when
45    /// running against Clerk development environment
46    pub fn set_dev_browser_token_id(&self, token_id: String) {
47        let mut write_guard = self.dev_browser_token_id.write();
48        *write_guard = Some(token_id);
49    }
50
51    /// Process the request before sending
52    fn process_request(&self, mut req: Request) -> Request {
53        // When running in non standard browser we need to tell Clerk
54        // API that with the _is_native query parameter
55        let url = req.url_mut();
56        if self.client_kind == ClientKind::NonBrowser {
57            url.query_pairs_mut().append_pair("_is_native", "1");
58        }
59
60        let token_id = {
61            let read_guard = self.dev_browser_token_id.read();
62            read_guard.clone()
63        };
64
65        if let Some(dev_browser_token_id) = token_id {
66            url.query_pairs_mut()
67                .append_pair("__clerk_db_jwt", &dev_browser_token_id);
68        }
69
70        {
71            let mut state = self.state.write();
72            match state.authorization_header() {
73                Some(auth) => {
74                    if let Ok(value) = HeaderValue::from_str(auth.as_str()) {
75                        req.headers_mut().insert("Authorization", value);
76                    } else {
77                        error!("ClerkHttpClient: Failed to parse authorization header");
78                    }
79                }
80                None => {
81                    debug!("ClerkHttpClient: No authorization header available");
82                }
83            }
84        }
85
86        req
87    }
88
89    fn process_response(&self, resp: &Response) {
90        if let Some(auth_header) = resp.headers().get("Authorization") {
91            if let Ok(auth_str) = auth_header.to_str() {
92                let mut state = self.state.write();
93                state.set_authorization_header(Some(auth_str.to_string()));
94            } else {
95                error!("ClerkHttpClient: Failed to parse authorization header");
96            }
97        } else {
98            // TODO: figure out if we need to clear the header value?
99            //
100        }
101    }
102
103    pub async fn execute(&self, request: Request) -> Result<Response, reqwest::Error> {
104        // FOR DEBUG
105        // let method = request.method().clone();
106        // let url = request.url().clone();
107        // END FOR DEBUG
108
109        let processed_request = self.process_request(request);
110        let response = self.inner.execute(processed_request).await?;
111
112        // FOR DEBUG
113        // let status = response.status();
114        // let version = response.version();
115        // let headers = response.headers().clone();
116        // let resp_text = response.text().await?;
117        // println!("[DEBUG] Request {} {} -> Response {}", method, url, status);
118        // println!("[DEBUG] Response body: {}", resp_text);
119        // let mut builder = http::Response::builder().status(status).version(version);
120        // let builder_headers = builder.headers_mut().unwrap();
121        // for (key, value) in headers.iter() {
122        //     builder_headers.insert(key, value.clone());
123        // }
124        // let response = Response::from(builder.body(resp_text).unwrap());
125        // END FOR DEBUG
126
127        self.process_response(&response);
128        Ok(response)
129    }
130
131    pub fn request<U: reqwest::IntoUrl>(
132        &self,
133        method: reqwest::Method,
134        url: U,
135    ) -> reqwest::RequestBuilder {
136        self.inner.request(method, url)
137    }
138
139    pub fn get<U: reqwest::IntoUrl>(&self, url: U) -> reqwest::RequestBuilder {
140        self.inner.get(url)
141    }
142
143    pub fn post<U: reqwest::IntoUrl>(&self, url: U) -> reqwest::RequestBuilder {
144        self.inner.post(url)
145    }
146
147    pub fn put<U: reqwest::IntoUrl>(&self, url: U) -> reqwest::RequestBuilder {
148        self.inner.put(url)
149    }
150
151    pub fn patch<U: reqwest::IntoUrl>(&self, url: U) -> reqwest::RequestBuilder {
152        self.inner.patch(url)
153    }
154
155    pub fn delete<U: reqwest::IntoUrl>(&self, url: U) -> reqwest::RequestBuilder {
156        self.inner.delete(url)
157    }
158
159    pub fn head<U: reqwest::IntoUrl>(&self, url: U) -> reqwest::RequestBuilder {
160        self.inner.head(url)
161    }
162}