1use std::fmt::Debug;
2
3#[cfg(not(wasm_browser))]
4use super::internal::cookies::CookieJar;
5#[cfg(not(wasm_browser))]
6use std::sync::Arc;
7use std::time::Duration;
8
9static DEFAULT_USER_AGENT: &str = concat!("pubky.org", "@", env!("CARGO_PKG_VERSION"),);
10
11static DEFAULT_RELAYS: &[&str] = &["https://pkarr.pubky.org/", "https://pkarr.pubky.app/"];
12
13#[macro_export]
14macro_rules! handle_http_error {
15 ($res:expr) => {
16 if let Err(status) = $res.error_for_status_ref() {
17 return match $res.text().await {
18 Ok(text) => Err(anyhow::anyhow!("{status}. Error message: {text}")),
19 _ => Err(anyhow::anyhow!("{status}")),
20 };
21 }
22 };
23}
24
25#[derive(Debug, Default, Clone)]
26pub struct ClientBuilder {
27 pkarr: pkarr::ClientBuilder,
28 http_request_timeout: Option<Duration>,
29 max_record_age: Option<Duration>,
32}
33
34impl ClientBuilder {
35 #[cfg(not(wasm_browser))]
36 pub fn testnet(&mut self) -> &mut Self {
40 self.pkarr
41 .bootstrap(&[format!(
42 "localhost:{}",
43 pubky_common::constants::testnet_ports::BOOTSTRAP
44 )])
45 .relays(&[format!(
46 "http://localhost:{}",
47 pubky_common::constants::testnet_ports::PKARR_RELAY
48 )])
49 .expect("relays urls infallible");
50
51 self
52 }
53
54 pub fn pkarr<F>(&mut self, f: F) -> &mut Self
56 where
57 F: FnOnce(&mut pkarr::ClientBuilder) -> &mut pkarr::ClientBuilder,
58 {
59 f(&mut self.pkarr);
60
61 self
62 }
63
64 pub fn request_timeout(&mut self, timeout: Duration) -> &mut Self {
66 self.http_request_timeout = Some(timeout);
67
68 self
69 }
70
71 pub fn max_record_age(&mut self, max_age: Duration) -> &mut Self {
74 self.max_record_age = Some(max_age);
75 self
76 }
77
78 pub fn build(&self) -> Result<Client, BuildError> {
80 let pkarr = self.pkarr.build()?;
81
82 #[cfg(not(wasm_browser))]
83 let cookie_store = Arc::new(CookieJar::default());
84
85 let user_agent = DEFAULT_USER_AGENT;
87
88 #[cfg(not(wasm_browser))]
89 let mut http_builder = reqwest::ClientBuilder::from(pkarr.clone())
90 .cookie_provider(cookie_store.clone())
92 .user_agent(user_agent);
93
94 #[cfg(wasm_browser)]
95 let http_builder = reqwest::Client::builder().user_agent(user_agent);
96
97 #[cfg(not(wasm_browser))]
98 let mut icann_http_builder = reqwest::Client::builder()
99 .cookie_provider(cookie_store.clone())
101 .user_agent(user_agent);
102
103 #[cfg(not(wasm_browser))]
105 if let Some(timeout) = self.http_request_timeout {
106 http_builder = http_builder.timeout(timeout);
107
108 icann_http_builder = icann_http_builder.timeout(timeout);
109 }
110
111 let max_record_age = self.max_record_age.unwrap_or(Duration::from_secs(60 * 60));
115
116 Ok(Client {
117 pkarr,
118 http: http_builder.build().expect("config expected to not error"),
119
120 #[cfg(not(wasm_browser))]
121 icann_http: icann_http_builder
122 .build()
123 .expect("config expected to not error"),
124 #[cfg(not(wasm_browser))]
125 cookie_store,
126
127 #[cfg(wasm_browser)]
128 testnet: false,
129
130 max_record_age,
131 })
132 }
133}
134
135#[derive(Debug, thiserror::Error)]
136pub enum BuildError {
137 #[error(transparent)]
138 PkarrBuildError(#[from] pkarr::errors::BuildError),
140}
141
142#[derive(Clone, Debug)]
144pub struct Client {
145 pub(crate) http: reqwest::Client,
146 pub(crate) pkarr: pkarr::Client,
147
148 #[cfg(not(wasm_browser))]
149 pub(crate) cookie_store: std::sync::Arc<CookieJar>,
150 #[cfg(not(wasm_browser))]
151 pub(crate) icann_http: reqwest::Client,
152
153 #[cfg(wasm_browser)]
154 pub(crate) testnet: bool,
155
156 pub(crate) max_record_age: Duration,
158}
159
160impl Client {
161 pub fn builder() -> ClientBuilder {
163 let mut builder = ClientBuilder::default();
164 builder.pkarr(|pkarr| pkarr.relays(DEFAULT_RELAYS).expect("infallible"));
165 builder
166 }
167
168 pub fn pkarr(&self) -> &pkarr::Client {
172 &self.pkarr
173 }
174}
175
176#[cfg(not(wasm_browser))]
177#[cfg(test)]
178mod test {
179 use super::*;
180
181 #[tokio::test]
182 async fn test_fetch() {
183 let client = Client::builder().build().unwrap();
184 let response = client.get("https://google.com/").send().await.unwrap();
185 assert_eq!(response.status(), 200);
186 }
187}