agentlink-wasm 0.1.1

AgentLink SDK WASM - WebAssembly bindings for browser/Node.js
//! WASM HTTP Client Implementation
//!
//! Uses browser's Fetch API via web-sys.

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;

/// WASM HTTP client using Fetch API
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);

        // Create headers
        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))
        })?;

        // Add auth token if available
        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))
            })?;
        }

        // Add request headers
        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);

        // Add body if present
        if let Some(body) = request.body {
            opts.set_body(&JsValue::from_str(&body));
        }

        // Create request
        let request = Request::new_with_str_and_init(&url, &opts).map_err(|e| {
            agentlink_core::error::SdkError::Http(format!("Failed to create request: {:?}", e))
        })?;

        // Get window
        let window = web_sys::window().ok_or_else(|| {
            agentlink_core::error::SdkError::Http("No window available".to_string())
        })?;

        // Fetch
        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();

        // Get body text
        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();

        // Convert headers
        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()
            )),
        }
    }
}