use std::sync::{Arc, Mutex};
use async_trait::async_trait;
use wasm_bindgen::prelude::*;
use wasm_bindgen_futures::JsFuture;
use web_sys::{Request, RequestInit, RequestMode, Response, Headers};
use agentlink_core::http::{HttpClient, HttpMethod, HttpRequest, HttpResponse};
use agentlink_core::error::SdkResult;
pub struct WasmHttpClient {
base_url: String,
auth_token: Arc<Mutex<Option<String>>>,
}
impl WasmHttpClient {
pub fn new(base_url: impl Into<String>) -> Self {
Self {
base_url: base_url.into(),
auth_token: Arc::new(Mutex::new(None)),
}
}
fn method_to_string(method: HttpMethod) -> &'static str {
match method {
HttpMethod::Get => "GET",
HttpMethod::Post => "POST",
HttpMethod::Put => "PUT",
HttpMethod::Delete => "DELETE",
HttpMethod::Patch => "PATCH",
}
}
}
#[async_trait(?Send)]
impl HttpClient for WasmHttpClient {
async fn request(&self, request: HttpRequest) -> SdkResult<HttpResponse> {
let url = if request.url.starts_with("http") {
request.url.clone()
} else {
format!("{}{}", self.base_url, request.url)
};
let opts = RequestInit::new();
opts.set_method(Self::method_to_string(request.method));
opts.set_mode(RequestMode::Cors);
let headers = Headers::new().map_err(|e| {
agentlink_core::error::SdkError::Http(format!("Failed to create headers: {:?}", e))
})?;
headers.append("Accept", "application/json").map_err(|e| {
agentlink_core::error::SdkError::Http(format!("Failed to set header: {:?}", e))
})?;
if let Some(token) = self.auth_token.lock().unwrap().as_ref() {
headers.append("Authorization", &format!("Bearer {}", token)).map_err(|e| {
agentlink_core::error::SdkError::Http(format!("Failed to set auth header: {:?}", e))
})?;
}
for (key, value) in request.headers {
headers.append(&key, &value).map_err(|e| {
agentlink_core::error::SdkError::Http(format!("Failed to set header: {:?}", e))
})?;
}
opts.set_headers(&headers);
if let Some(body) = request.body {
opts.set_body(&JsValue::from_str(&body));
}
let request = Request::new_with_str_and_init(&url, &opts).map_err(|e| {
agentlink_core::error::SdkError::Http(format!("Failed to create request: {:?}", e))
})?;
let window = web_sys::window().ok_or_else(|| {
agentlink_core::error::SdkError::Http("No window available".to_string())
})?;
let resp_value = JsFuture::from(window.fetch_with_request(&request)).await.map_err(|e| {
agentlink_core::error::SdkError::Http(format!("Fetch failed: {:?}", e))
})?;
let resp: Response = resp_value.dyn_into().map_err(|e| {
agentlink_core::error::SdkError::Http(format!("Invalid response: {:?}", e))
})?;
let status = resp.status();
let text = JsFuture::from(resp.text().map_err(|e| {
agentlink_core::error::SdkError::Http(format!("Failed to get text: {:?}", e))
})?).await.map_err(|e| {
agentlink_core::error::SdkError::Http(format!("Failed to read body: {:?}", e))
})?;
let body = text.as_string().unwrap_or_default();
let header_iter = js_sys::try_iter(&resp.headers()).ok().flatten();
let header_vec = if let Some(iter) = header_iter {
iter.filter_map(|entry| {
let entry = entry.ok()?;
let arr = js_sys::Array::from(&entry);
let key = arr.get(0).as_string()?;
let value = arr.get(1).as_string()?;
Some((key, value))
}).collect()
} else {
Vec::new()
};
Ok(HttpResponse {
status,
headers: header_vec,
body,
})
}
fn set_auth_token(&mut self, token: String) {
*self.auth_token.lock().unwrap() = Some(token);
}
fn auth_token(&self) -> Option<String> {
self.auth_token.lock().unwrap().clone()
}
}
impl Clone for WasmHttpClient {
fn clone(&self) -> Self {
Self {
base_url: self.base_url.clone(),
auth_token: Arc::new(Mutex::new(
self.auth_token.lock().unwrap().clone()
)),
}
}
}