use std::rc::Rc;
use impulse_utils::prelude::*;
use leptos::prelude::*;
use wasm_bindgen::JsCast;
use wasm_bindgen::prelude::*;
use wasm_bindgen_futures::JsFuture;
use wasm_bindgen_futures::spawn_local;
use web_sys::{
ReadableStream, ReadableStreamDefaultReader, WebTransport, WebTransportBidirectionalStream, WebTransportCloseInfo,
WebTransportOptions, WebTransportSendStream, WritableStream, WritableStreamDefaultWriter,
};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum WebTransportState {
Connecting,
Open,
Closed,
Failed,
}
struct WebTransportInner {
transport: WebTransport,
}
impl Drop for WebTransportInner {
fn drop(&mut self) {
self.transport.close();
}
}
#[derive(Clone)]
pub struct WebTransportHandle {
pub state: ReadSignal<WebTransportState>,
inner: Rc<WebTransportInner>,
}
pub fn use_webtransport(url: impl AsRef<str>) -> CResult<WebTransportHandle> {
let url = url.as_ref();
let transport = WebTransport::new(url)
.map_err(|e| ClientError::from_str(format!("Failed to construct WebTransport for {url}: {e:?}")))?;
Ok(setup_handle(transport))
}
pub fn use_webtransport_with_options(
url: impl AsRef<str>,
options: &WebTransportOptions,
) -> CResult<WebTransportHandle> {
let url = url.as_ref();
let transport = WebTransport::new_with_options(url, options)
.map_err(|e| ClientError::from_str(format!("Failed to construct WebTransport for {url}: {e:?}")))?;
Ok(setup_handle(transport))
}
fn setup_handle(transport: WebTransport) -> WebTransportHandle {
let (state, set_state) = signal(WebTransportState::Connecting);
let ready: js_sys::Promise = transport.ready().unchecked_into();
let closed: js_sys::Promise = transport.closed().unchecked_into();
spawn_local(async move {
if let Err(e) = JsFuture::from(ready).await {
log::error!("WebTransport ready failed: {e:?}");
set_state.set(WebTransportState::Failed);
return;
}
set_state.set(WebTransportState::Open);
match JsFuture::from(closed).await {
Ok(_) => set_state.set(WebTransportState::Closed),
Err(e) => {
log::warn!("WebTransport closed with error: {e:?}");
set_state.set(WebTransportState::Failed);
}
}
});
WebTransportHandle {
state,
inner: Rc::new(WebTransportInner { transport }),
}
}
impl WebTransportHandle {
pub fn raw(&self) -> &WebTransport {
&self.inner.transport
}
pub fn close(&self) {
self.inner.transport.close();
}
pub fn close_with_info(&self, info: &WebTransportCloseInfo) {
self.inner.transport.close_with_close_info(info);
}
pub async fn send_datagram(&self, data: &[u8]) -> CResult<()> {
let writable: WritableStream = self.inner.transport.datagrams().writable();
let writer: WritableStreamDefaultWriter = writable
.get_writer()
.map_err(|e| ClientError::from_str(format!("Failed to acquire datagram writer: {e:?}")))?;
let chunk: JsValue = js_sys::Uint8Array::from(data).into();
let promise: js_sys::Promise = writer.write_with_chunk(&chunk).unchecked_into();
let result = JsFuture::from(promise).await;
writer.release_lock();
result
.map(|_| ())
.map_err(|e| ClientError::from_str(format!("Failed to send datagram: {e:?}")))
}
pub async fn open_bidirectional_stream(&self) -> CResult<WebTransportBidirectionalStream> {
let promise: js_sys::Promise = self.inner.transport.create_bidirectional_stream().unchecked_into();
let val = JsFuture::from(promise)
.await
.map_err(|e| ClientError::from_str(format!("Failed to open bidirectional stream: {e:?}")))?;
val
.dyn_into::<WebTransportBidirectionalStream>()
.map_err(|_| ClientError::from_str("Unexpected bidirectional stream type"))
}
pub async fn open_unidirectional_stream(&self) -> CResult<WebTransportSendStream> {
let promise: js_sys::Promise = self.inner.transport.create_unidirectional_stream().unchecked_into();
let val = JsFuture::from(promise)
.await
.map_err(|e| ClientError::from_str(format!("Failed to open unidirectional stream: {e:?}")))?;
val
.dyn_into::<WebTransportSendStream>()
.map_err(|_| ClientError::from_str("Unexpected unidirectional stream type"))
}
pub fn datagram_signal(&self) -> CResult<ReadSignal<Option<Vec<u8>>>> {
let (sig, set_sig) = signal::<Option<Vec<u8>>>(None);
let readable: ReadableStream = self.inner.transport.datagrams().readable();
let reader = ReadableStreamDefaultReader::new(&readable)
.map_err(|e| ClientError::from_str(format!("Failed to acquire datagram reader: {e:?}")))?;
spawn_local(async move {
loop {
match JsFuture::from(reader.read()).await {
Ok(result) => {
let done = js_sys::Reflect::get(&result, &JsValue::from_str("done"))
.ok()
.and_then(|v| v.as_bool())
.unwrap_or(true);
if done {
break;
}
if let Ok(value) = js_sys::Reflect::get(&result, &JsValue::from_str("value")) {
let arr = js_sys::Uint8Array::new(&value);
let mut buf = vec![0u8; arr.length() as usize];
arr.copy_to(&mut buf);
set_sig.set(Some(buf));
}
}
Err(e) => {
log::warn!("Datagram read error: {e:?}");
break;
}
}
}
});
Ok(sig)
}
}