use std::cell::RefCell;
use std::pin::Pin;
use std::rc::Rc;
use std::task::{Context, Poll};
use futures::channel::{mpsc, oneshot};
use futures::Sink;
use wasm_bindgen::closure::Closure;
use wasm_bindgen::{JsCast, JsValue};
use web_sys::{BinaryType, MessageEvent, WebSocket};
use crate::connection::{connect, ConnectOptions, H2Connection};
use crate::errors::{ErrorCode, H2Error};
use crate::pool::{H2Pool, PoolConnection};
use crate::transport::{ByteSink, ByteStream, Transport, TransportError};
pub async fn connect_websocket(
url: &str,
protocols: &[&str],
options: ConnectOptions,
) -> Result<H2Connection, JsValue> {
let ws = open(url, protocols).await?;
let transport = websocket_transport(&ws);
let (conn, driver) = connect(transport, options);
wasm_bindgen_futures::spawn_local(driver);
Ok(conn)
}
pub fn connect_pool(
url: &str,
protocols: &[&str],
options: ConnectOptions,
max_connections: usize,
) -> H2Pool {
let url = url.to_string();
let protocols: Vec<String> = protocols.iter().map(|p| (*p).to_string()).collect();
H2Pool::new(
move || {
let url = url.clone();
let protocols = protocols.clone();
let options = options.clone();
async move {
let refs: Vec<&str> = protocols.iter().map(String::as_str).collect();
match connect_websocket(&url, &refs, options).await {
Ok(conn) => Ok(Rc::new(conn) as Rc<dyn PoolConnection>),
Err(e) => Err(H2Error::new(ErrorCode::ConnectError, format!("{e:?}"))),
}
}
},
max_connections,
)
}
async fn open(url: &str, protocols: &[&str]) -> Result<WebSocket, JsValue> {
let ws = if protocols.is_empty() {
WebSocket::new(url)?
} else {
let arr = js_sys::Array::new();
for p in protocols {
arr.push(&JsValue::from_str(p));
}
WebSocket::new_with_str_sequence(url, &JsValue::from(arr))?
};
ws.set_binary_type(BinaryType::Arraybuffer);
let (tx, rx) = oneshot::channel::<Result<(), JsValue>>();
let tx = Rc::new(RefCell::new(Some(tx)));
let onopen = {
let tx = tx.clone();
Closure::<dyn FnMut()>::wrap(Box::new(move || {
if let Some(tx) = tx.borrow_mut().take() {
let _ = tx.send(Ok(()));
}
}))
};
let onerror = {
let tx = tx.clone();
Closure::<dyn FnMut()>::wrap(Box::new(move || {
if let Some(tx) = tx.borrow_mut().take() {
let _ = tx.send(Err(JsValue::from_str("WebSocket connection failed")));
}
}))
};
ws.set_onopen(Some(onopen.as_ref().unchecked_ref()));
ws.set_onerror(Some(onerror.as_ref().unchecked_ref()));
let result = rx
.await
.map_err(|_| JsValue::from_str("WebSocket open canceled"))?;
ws.set_onopen(None);
ws.set_onerror(None);
drop(onopen);
drop(onerror);
result.map(|()| ws)
}
pub fn websocket_transport(ws: &WebSocket) -> Transport {
ws.set_binary_type(BinaryType::Arraybuffer);
let (in_tx, in_rx) = mpsc::unbounded::<Vec<u8>>();
let in_tx = Rc::new(RefCell::new(Some(in_tx)));
let onmessage = {
let in_tx = in_tx.clone();
Closure::<dyn FnMut(MessageEvent)>::wrap(Box::new(move |ev: MessageEvent| {
if let Ok(buf) = ev.data().dyn_into::<js_sys::ArrayBuffer>() {
let bytes = js_sys::Uint8Array::new(&buf).to_vec();
if !bytes.is_empty() {
if let Some(tx) = in_tx.borrow().as_ref() {
let _ = tx.unbounded_send(bytes);
}
}
}
}))
};
let onclose = {
let in_tx = in_tx.clone();
Closure::<dyn FnMut()>::wrap(Box::new(move || {
in_tx.borrow_mut().take(); }))
};
ws.set_onmessage(Some(onmessage.as_ref().unchecked_ref()));
ws.set_onclose(Some(onclose.as_ref().unchecked_ref()));
onmessage.forget();
onclose.forget();
let reader: ByteStream = Box::pin(in_rx);
let writer: ByteSink = Box::pin(WsSink { ws: ws.clone() });
Transport::new(reader, writer)
}
struct WsSink {
ws: WebSocket,
}
impl Sink<Vec<u8>> for WsSink {
type Error = TransportError;
fn poll_ready(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<Result<(), TransportError>> {
Poll::Ready(Ok(()))
}
fn start_send(self: Pin<&mut Self>, item: Vec<u8>) -> Result<(), TransportError> {
self.ws
.send_with_u8_array(&item)
.map_err(|e| TransportError(format!("{e:?}")))
}
fn poll_flush(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<Result<(), TransportError>> {
Poll::Ready(Ok(()))
}
fn poll_close(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<Result<(), TransportError>> {
let _ = self.ws.close();
Poll::Ready(Ok(()))
}
}