Skip to main content

smbcloud_network/
network.rs

1use {
2    log::error,
3    reqwest::{RequestBuilder, Response, StatusCode},
4    serde::de::DeserializeOwned,
5    smbcloud_model::{
6        account::SmbAuthorization,
7        error_codes::{ErrorCode, ErrorResponse},
8        login::AccountStatus,
9    },
10};
11
12#[cfg(not(target_arch = "wasm32"))]
13use log::debug;
14
15//use std::time::Duration;
16#[cfg(debug_assertions)]
17const LOG_RESPONSE_BODY: bool = true; // You know what to do here.
18#[cfg(not(debug_assertions))]
19const LOG_RESPONSE_BODY: bool = false;
20
21/// Check if there is an active internet connection.
22///
23/// Native clients can afford a preflight connectivity check. Browser/wasm
24/// clients should skip it and rely on the real request, otherwise the check
25/// itself becomes a separate cross-origin failure surface.
26#[cfg(not(target_arch = "wasm32"))]
27pub async fn check_internet_connection() -> bool {
28    debug!("Checking internet connection");
29    let client = reqwest::Client::builder()
30        //.timeout(Duration::from_secs(5)) Does not work on wasm32
31        .build();
32
33    if let Err(e) = client {
34        error!("Failed to create client for connectivity check: {:?}", e);
35        return false;
36    }
37
38    if let Ok(client) = client {
39        match client.get("https://dns.google").send().await {
40            Ok(response) => {
41                debug!(
42                    "Internet connection check successful: {}",
43                    response.status()
44                );
45                response.status().is_success()
46            }
47            Err(e) => {
48                error!("Internet connection check failed: {:?}", e);
49                false
50            }
51        }
52    } else {
53        false
54    }
55}
56
57#[cfg(target_arch = "wasm32")]
58pub async fn check_internet_connection() -> bool {
59    true
60}
61
62pub async fn parse_error_response<T: DeserializeOwned>(
63    response: Response,
64) -> Result<T, ErrorResponse> {
65    let error_response_body = match response.text().await {
66        Ok(body) => body,
67        Err(e) => {
68            error!("Failed to get response body: {:?}", e);
69            return Err(ErrorResponse::Error {
70                error_code: ErrorCode::NetworkError,
71                message: ErrorCode::NetworkError.message(None).to_string(),
72            });
73        }
74    };
75
76    if LOG_RESPONSE_BODY {
77        println!();
78        println!("Parse Error >>>>");
79        println!("{:?}", serde_json::to_string_pretty(&error_response_body));
80        println!("Parse Error >>>>");
81        println!();
82    }
83
84    let error_response = match serde_json::from_str(&error_response_body) {
85        Ok(error_response) => error_response,
86        Err(e) => {
87            error!("Failed to parse error response: {:?}", e);
88            return Err(ErrorResponse::Error {
89                error_code: ErrorCode::ParseError,
90                message: ErrorCode::ParseError.message(None).to_string(),
91            });
92        }
93    };
94    // The parsing itself is succeed.
95    // Why is this an ErrorResponse.
96    Err(error_response)
97}
98
99pub async fn request_login(builder: RequestBuilder) -> Result<AccountStatus, ErrorResponse> {
100    let response = builder.send().await;
101    let response = match response {
102        Ok(response) => response,
103        Err(e) => {
104            error!("request_login: Failed to get response: {:?}", e);
105            return Err(ErrorResponse::Error {
106                error_code: ErrorCode::NetworkError,
107                message: ErrorCode::NetworkError.message(None).to_string(),
108            });
109        }
110    };
111
112    if LOG_RESPONSE_BODY {
113        println!();
114        println!("request_login: Parse >>>>");
115        println!("{:?}", &response.status());
116        println!("request_login: Parse >>>>");
117        println!();
118    }
119
120    match (response.status(), response.headers().get("Authorization")) {
121        (StatusCode::OK, Some(token)) => {
122            // Login successful, let's get the access token for real.
123            let access_token = match token.to_str() {
124                Ok(token) => token.to_string(),
125                Err(_) => {
126                    return Err(ErrorResponse::Error {
127                        error_code: ErrorCode::NetworkError,
128                        message: ErrorCode::NetworkError.message(None).to_string(),
129                    });
130                }
131            };
132            Ok(AccountStatus::Ready { access_token })
133        }
134        (StatusCode::OK, None) => {
135            // Silent login from oauth. Need improvement.
136            let error_response = match parse_error_response::<ErrorResponse>(response).await {
137                Ok(error) => error,
138                Err(_) => return Ok(AccountStatus::NotFound),
139            };
140            match error_response {
141                ErrorResponse::Error {
142                    error_code,
143                    message: _,
144                } => match error_code {
145                    ErrorCode::EmailNotVerified => Ok(AccountStatus::Incomplete {
146                        status: smbcloud_model::account::ErrorCode::EmailUnverified,
147                    }),
148                    ErrorCode::PasswordNotSet => Ok(AccountStatus::Incomplete {
149                        status: smbcloud_model::account::ErrorCode::PasswordNotSet,
150                    }),
151                    ErrorCode::Unknown => Ok(AccountStatus::Ready {
152                        access_token: "tokenization".to_string(),
153                    }),
154                    _ => Ok(AccountStatus::NotFound),
155                },
156            }
157        }
158        (StatusCode::NOT_FOUND, _) => {
159            // Account not found
160            Ok(AccountStatus::NotFound)
161        }
162        (StatusCode::UNPROCESSABLE_ENTITY, _) => {
163            let body_text = match response.text().await {
164                Ok(text) => text,
165                Err(_) => {
166                    return Err(ErrorResponse::Error {
167                        error_code: ErrorCode::NetworkError,
168                        message: ErrorCode::NetworkError.message(None).to_string(),
169                    });
170                }
171            };
172
173            let result: SmbAuthorization = match serde_json::from_str(&body_text) {
174                Ok(res) => res,
175                Err(_) => {
176                    // Try generic JSON for a useful message.
177                    let message = serde_json::from_str::<serde_json::Value>(&body_text)
178                        .ok()
179                        .and_then(|v| {
180                            v.get("message")
181                                .and_then(serde_json::Value::as_str)
182                                .map(ToOwned::to_owned)
183                        })
184                        .unwrap_or_else(|| ErrorCode::NetworkError.message(None).to_string());
185                    return Err(ErrorResponse::Error {
186                        error_code: ErrorCode::NetworkError,
187                        message,
188                    });
189                }
190            };
191
192            let error_code = match result.error_code {
193                Some(code) => code,
194                None => {
195                    // No error_code but we have a message — return it as Incomplete with a
196                    // generic unverified status so the client can display the message.
197                    return Err(ErrorResponse::Error {
198                        error_code: ErrorCode::NetworkError,
199                        message: result.message,
200                    });
201                }
202            };
203            Ok(AccountStatus::Incomplete { status: error_code })
204        }
205        (StatusCode::UNAUTHORIZED, _) => {
206            let body_text = match response.text().await {
207                Ok(text) => text,
208                Err(_) => {
209                    return Err(ErrorResponse::Error {
210                        error_code: ErrorCode::NetworkError,
211                        message: ErrorCode::NetworkError.message(None).to_string(),
212                    });
213                }
214            };
215
216            // Try parsing as SmbAuthorization first.
217            if let Ok(result) = serde_json::from_str::<SmbAuthorization>(&body_text) {
218                let error_code = match result.error_code {
219                    Some(code) => code,
220                    None => {
221                        // Response parsed but no error_code — surface the server's message.
222                        return Err(ErrorResponse::Error {
223                            error_code: ErrorCode::Unauthorized,
224                            message: result.message,
225                        });
226                    }
227                };
228                return Err(ErrorResponse::Error {
229                    error_code: ErrorCode::Unauthorized,
230                    message: error_code.to_string(),
231                });
232            }
233
234            // Fallback: try to extract message or error from a generic JSON object.
235            if let Ok(generic) = serde_json::from_str::<serde_json::Value>(&body_text) {
236                let extracted = generic
237                    .get("message")
238                    .or_else(|| generic.get("error"))
239                    .and_then(serde_json::Value::as_str)
240                    .unwrap_or("Sign in failed.");
241                return Err(ErrorResponse::Error {
242                    error_code: ErrorCode::Unauthorized,
243                    message: extracted.to_string(),
244                });
245            }
246
247            Err(ErrorResponse::Error {
248                error_code: ErrorCode::Unauthorized,
249                message: "Sign in failed.".to_string(),
250            })
251        }
252        (status, _) => parse_error_response(response)
253            .await
254            .map_err(|_| ErrorResponse::Error {
255                error_code: ErrorCode::NetworkError,
256                message: format!("Unexpected login response status: {}", status),
257            }),
258    }
259}
260
261pub async fn request<R: DeserializeOwned>(builder: RequestBuilder) -> Result<R, ErrorResponse> {
262    // Check internet connection before making the request
263    if !check_internet_connection().await {
264        error!("No internet connection available");
265        return Err(ErrorResponse::Error {
266            error_code: ErrorCode::NetworkError,
267            message: "No internet connection. Please check your network settings and try again."
268                .to_string(),
269        });
270    }
271
272    let response = builder.send().await;
273    let response = match response {
274        Ok(response) => response,
275        Err(e) => {
276            error!("Failed to get response: {:?}", e);
277            return Err(ErrorResponse::Error {
278                error_code: ErrorCode::NetworkError,
279                message: ErrorCode::NetworkError.message(None).to_string(),
280            });
281        }
282    };
283    let response = match response.status() {
284        reqwest::StatusCode::OK | reqwest::StatusCode::CREATED => response,
285        status => {
286            error!("Failed to get response: {:?}", status);
287            // This should handle parsing the error response.
288            return parse_error_response(response).await;
289        }
290    };
291
292    let response_body = match response.text().await {
293        Ok(body) => body,
294        Err(e) => {
295            error!("Failed to get response body: {:?}", e);
296            return Err(ErrorResponse::Error {
297                error_code: ErrorCode::NetworkError,
298                message: ErrorCode::NetworkError.message(None).to_string(),
299            });
300        }
301    };
302
303    if LOG_RESPONSE_BODY {
304        println!();
305        println!("Parse >>>>");
306        println!("{:?}", serde_json::to_string_pretty(&response_body));
307        println!("Parse >>>>");
308        println!();
309    }
310
311    let response = match serde_json::from_str::<R>(&response_body) {
312        Ok(response) => response,
313        Err(e) => {
314            error!("Failed to parse response: {:?}", e);
315            return Err(ErrorResponse::Error {
316                error_code: ErrorCode::ParseError,
317                message: e.to_string(),
318            });
319        }
320    };
321
322    Ok(response)
323}