use wasm_bindgen::prelude::*;
use wasm_bindgen_futures::future_to_promise;
use js_sys::Promise;
use agentlink_core::{
http::{HttpClient, HttpClientExt},
mqtt::{MqttClient, MqttConfig, MqttQoS, MqttMessage},
protocols::auth::{LoginRequest, LoginResponse},
};
use crate::http::WasmHttpClient;
use crate::mqtt::WasmMqttClient;
#[wasm_bindgen(start)]
pub fn start() {
console_error_panic_hook::set_once();
wasm_logger::init(wasm_logger::Config::default());
}
#[wasm_bindgen]
pub struct JsHttpClient {
inner: WasmHttpClient,
}
#[wasm_bindgen]
impl JsHttpClient {
#[wasm_bindgen(constructor)]
pub fn new(base_url: String) -> Self {
Self {
inner: WasmHttpClient::new(base_url),
}
}
#[wasm_bindgen(js_name = setAuthToken)]
pub fn set_auth_token(&mut self, token: String) {
self.inner.set_auth_token(token);
}
#[wasm_bindgen(js_name = getAuthToken)]
pub fn get_auth_token(&self) -> Option<String> {
self.inner.auth_token().map(|s| s.to_string())
}
#[wasm_bindgen(js_name = get)]
pub fn get(&self, path: String) -> Promise {
let client = self.inner.clone();
future_to_promise(async move {
let result: Result<JsValue, JsValue> = client
.get::<serde_json::Value>(&path)
.await
.map(|v| serde_wasm_bindgen::to_value(&v).unwrap())
.map_err(|e| JsValue::from_str(&e.to_string()));
result
})
}
#[wasm_bindgen(js_name = post)]
pub fn post(&self, path: String, body: JsValue) -> Promise {
let client = self.inner.clone();
future_to_promise(async move {
let body: serde_json::Value = serde_wasm_bindgen::from_value(body)
.map_err(|e| JsValue::from_str(&format!("Invalid JSON: {:?}", e)))?;
let result: Result<JsValue, JsValue> = client
.post::<serde_json::Value, _>(&path, &body)
.await
.map(|v| serde_wasm_bindgen::to_value(&v).unwrap())
.map_err(|e| JsValue::from_str(&e.to_string()));
result
})
}
#[wasm_bindgen(js_name = put)]
pub fn put(&self, path: String, body: JsValue) -> Promise {
let client = self.inner.clone();
future_to_promise(async move {
let body: serde_json::Value = serde_wasm_bindgen::from_value(body)
.map_err(|e| JsValue::from_str(&format!("Invalid JSON: {:?}", e)))?;
let result: Result<JsValue, JsValue> = client
.put::<serde_json::Value, _>(&path, &body)
.await
.map(|v| serde_wasm_bindgen::to_value(&v).unwrap())
.map_err(|e| JsValue::from_str(&e.to_string()));
result
})
}
#[wasm_bindgen(js_name = delete)]
pub fn delete(&self, path: String) -> Promise {
let client = self.inner.clone();
future_to_promise(async move {
let result: Result<JsValue, JsValue> = client
.delete::<serde_json::Value>(&path)
.await
.map(|v| serde_wasm_bindgen::to_value(&v).unwrap())
.map_err(|e| JsValue::from_str(&e.to_string()));
result
})
}
}
#[wasm_bindgen]
pub struct JsMqttClient {
inner: WasmMqttClient,
}
#[wasm_bindgen]
impl JsMqttClient {
#[wasm_bindgen(constructor)]
pub fn new() -> Self {
Self {
inner: WasmMqttClient::new(),
}
}
#[wasm_bindgen(js_name = connect)]
pub fn connect(&self, broker_url: String, client_id: String, username: Option<String>) -> Promise {
let client = self.inner.clone();
let config = MqttConfig {
broker_url,
client_id,
username,
password: None,
keep_alive_secs: 30,
clean_session: true,
};
future_to_promise(async move {
client.connect(config).await
.map(|_| JsValue::UNDEFINED)
.map_err(|e| JsValue::from_str(&e.to_string()))
})
}
#[wasm_bindgen(js_name = disconnect)]
pub fn disconnect(&self) -> Promise {
let client = self.inner.clone();
future_to_promise(async move {
client.disconnect().await
.map(|_| JsValue::UNDEFINED)
.map_err(|e| JsValue::from_str(&e.to_string()))
})
}
#[wasm_bindgen(js_name = subscribe)]
pub fn subscribe(&self, topic: String, qos: u8) -> Promise {
let qos = match qos {
0 => MqttQoS::AtMostOnce,
1 => MqttQoS::AtLeastOnce,
2 => MqttQoS::ExactlyOnce,
_ => MqttQoS::AtLeastOnce,
};
let client = self.inner.clone();
future_to_promise(async move {
client.subscribe(&topic, qos).await
.map(|_| JsValue::UNDEFINED)
.map_err(|e| JsValue::from_str(&e.to_string()))
})
}
#[wasm_bindgen(js_name = unsubscribe)]
pub fn unsubscribe(&self, topic: String) -> Promise {
let client = self.inner.clone();
future_to_promise(async move {
client.unsubscribe(&topic).await
.map(|_| JsValue::UNDEFINED)
.map_err(|e| JsValue::from_str(&e.to_string()))
})
}
#[wasm_bindgen(js_name = publish)]
pub fn publish(&self, topic: String, payload: Vec<u8>, qos: u8) -> Promise {
let qos = match qos {
0 => MqttQoS::AtMostOnce,
1 => MqttQoS::AtLeastOnce,
2 => MqttQoS::ExactlyOnce,
_ => MqttQoS::AtLeastOnce,
};
let message = MqttMessage::new(topic, payload).with_qos(qos);
let client = self.inner.clone();
future_to_promise(async move {
client.publish(message).await
.map(|_| JsValue::UNDEFINED)
.map_err(|e| JsValue::from_str(&e.to_string()))
})
}
#[wasm_bindgen(js_name = getConnectionState)]
pub fn get_connection_state(&self) -> String {
match self.inner.connection_state() {
agentlink_core::mqtt::MqttConnectionState::Disconnected => "disconnected".to_string(),
agentlink_core::mqtt::MqttConnectionState::Connecting => "connecting".to_string(),
agentlink_core::mqtt::MqttConnectionState::Connected => "connected".to_string(),
agentlink_core::mqtt::MqttConnectionState::Reconnecting => "reconnecting".to_string(),
agentlink_core::mqtt::MqttConnectionState::Disconnecting => "disconnecting".to_string(),
agentlink_core::mqtt::MqttConnectionState::Failed => "failed".to_string(),
}
}
#[wasm_bindgen(js_name = onEvent)]
pub fn on_event(&self, callback: js_sys::Function) {
self.inner.on_event(move |event| {
let event_obj = match event {
agentlink_core::mqtt::MqttEvent::Connected => {
js_sys::Object::new()
}
agentlink_core::mqtt::MqttEvent::Disconnected => {
js_sys::Object::new()
}
agentlink_core::mqtt::MqttEvent::MessageReceived(msg) => {
let obj = js_sys::Object::new();
js_sys::Reflect::set(&obj, &"topic".into(), &msg.topic.into()).unwrap();
js_sys::Reflect::set(&obj, &"payload".into(), &js_sys::Uint8Array::from(&msg.payload[..])).unwrap();
obj
}
_ => js_sys::Object::new(),
};
let _ = callback.call1(&JsValue::NULL, &event_obj);
});
}
}
#[wasm_bindgen(js_name = getVersion)]
pub fn get_version() -> String {
agentlink_core::VERSION.to_string()
}
#[wasm_bindgen(js_name = loginWithEmailCode)]
pub fn login_with_email_code(base_url: String, email: String, code: String) -> Promise {
future_to_promise(async move {
let client = WasmHttpClient::new(base_url);
let request = LoginRequest {
identifier: email,
code,
};
let result: Result<JsValue, JsValue> = client
.post::<LoginResponse, _>("/auth/login/email-code", &request)
.await
.map(|v| serde_wasm_bindgen::to_value(&v).unwrap())
.map_err(|e| JsValue::from_str(&e.to_string()));
result
})
}