use std::cell::RefCell;
use std::rc::Rc;
use futures_channel::mpsc::{self, UnboundedReceiver};
use futures_channel::oneshot;
use futures_util::StreamExt;
use wasm_bindgen::closure::Closure;
use wasm_bindgen::{JsCast, JsValue};
use wasm_bindgen_futures::JsFuture;
use super::{Request, Response, WsMessage};
use crate::error::{Error, Result};
fn js_err(v: JsValue) -> Error {
let msg = v
.as_string()
.or_else(|| {
v.dyn_ref::<js_sys::Error>()
.map(|e| String::from(e.message()))
})
.unwrap_or_else(|| format!("{v:?}"));
Error::BadResponse(format!("wasm: {msg}"))
}
pub async fn fetch(req: &Request) -> Result<Response> {
let init = web_sys::RequestInit::new();
init.set_method(&req.method);
init.set_redirect(if req.follow_redirects {
web_sys::RequestRedirect::Follow
} else {
web_sys::RequestRedirect::Manual
});
if !req.body.is_empty() {
let buf = js_sys::Uint8Array::from(req.body.as_slice());
init.set_body(&buf.into());
}
let headers = web_sys::Headers::new().map_err(js_err)?;
for (k, v) in &req.headers {
headers.append(k, v).map_err(js_err)?;
}
init.set_headers(&headers);
let web_req = web_sys::Request::new_with_str_and_init(&req.url, &init).map_err(js_err)?;
let resp_val = JsFuture::from(fetch_promise(&web_req)?)
.await
.map_err(js_err)?;
let web_resp: web_sys::Response = resp_val
.dyn_into()
.map_err(|_| Error::BadResponse("fetch did not resolve to a Response".into()))?;
let status = web_resp.status();
let reason = web_resp.status_text();
let headers = read_headers(&web_resp.headers());
let buf = JsFuture::from(web_resp.array_buffer().map_err(js_err)?)
.await
.map_err(js_err)?;
let body = js_sys::Uint8Array::new(&buf).to_vec();
Ok(Response {
status,
reason,
headers,
body,
})
}
fn fetch_promise(req: &web_sys::Request) -> Result<js_sys::Promise> {
if let Some(win) = web_sys::window() {
return Ok(win.fetch_with_request(req));
}
let scope: web_sys::WorkerGlobalScope = js_sys::global()
.dyn_into()
.map_err(|_| Error::BadResponse("no fetch: neither Window nor WorkerGlobalScope".into()))?;
Ok(scope.fetch_with_request(req))
}
fn read_headers(headers: &web_sys::Headers) -> Vec<(String, String)> {
let mut out = Vec::new();
if let Ok(Some(iter)) = js_sys::try_iter(headers.as_ref()) {
for entry in iter.flatten() {
let pair = js_sys::Array::from(&entry);
let name = pair.get(0).as_string().unwrap_or_default();
let value = pair.get(1).as_string().unwrap_or_default();
out.push((name, value));
}
}
out
}
struct Handlers {
_onopen: Closure<dyn FnMut(web_sys::Event)>,
_onmessage: Closure<dyn FnMut(web_sys::MessageEvent)>,
_onerror: Closure<dyn FnMut(web_sys::Event)>,
_onclose: Closure<dyn FnMut(web_sys::CloseEvent)>,
}
pub struct WebSocket {
ws: web_sys::WebSocket,
rx: UnboundedReceiver<Result<WsMessage>>,
_handlers: Handlers,
}
pub struct WsSink {
ws: web_sys::WebSocket,
}
pub struct WsStream {
rx: UnboundedReceiver<Result<WsMessage>>,
_handlers: Handlers,
}
fn detach_and_close(ws: &web_sys::WebSocket) {
ws.set_onopen(None);
ws.set_onmessage(None);
ws.set_onerror(None);
ws.set_onclose(None);
let _ = ws.close();
}
impl WebSocket {
pub async fn connect(url: &str) -> Result<WebSocket> {
Self::connect_with_subprotocols(url, &[]).await
}
pub async fn connect_with_subprotocols(url: &str, subprotocols: &[&str]) -> Result<WebSocket> {
let ws = if subprotocols.is_empty() {
web_sys::WebSocket::new(url)
} else {
let arr = js_sys::Array::new();
for p in subprotocols {
arr.push(&JsValue::from_str(p));
}
web_sys::WebSocket::new_with_str_sequence(url, &arr)
}
.map_err(js_err)?;
ws.set_binary_type(web_sys::BinaryType::Arraybuffer);
let (msg_tx, msg_rx) = mpsc::unbounded::<Result<WsMessage>>();
let (open_tx, open_rx) = oneshot::channel::<Result<()>>();
let open_slot = Rc::new(RefCell::new(Some(open_tx)));
let onopen = {
let slot = open_slot.clone();
Closure::<dyn FnMut(web_sys::Event)>::new(move |_e: web_sys::Event| {
if let Some(tx) = slot.borrow_mut().take() {
let _ = tx.send(Ok(()));
}
})
};
ws.set_onopen(Some(onopen.as_ref().unchecked_ref()));
let onmessage = {
let tx = msg_tx.clone();
Closure::<dyn FnMut(web_sys::MessageEvent)>::new(move |e: web_sys::MessageEvent| {
let data = e.data();
let msg = match data.as_string() {
Some(text) => WsMessage::Text(text),
None => WsMessage::Binary(js_sys::Uint8Array::new(&data).to_vec()),
};
let _ = tx.unbounded_send(Ok(msg));
})
};
ws.set_onmessage(Some(onmessage.as_ref().unchecked_ref()));
let onerror = {
let slot = open_slot.clone();
let tx = msg_tx.clone();
Closure::<dyn FnMut(web_sys::Event)>::new(move |_e: web_sys::Event| {
if let Some(otx) = slot.borrow_mut().take() {
let _ = otx.send(Err(Error::BadResponse(
"websocket connection failed".into(),
)));
} else {
let _ = tx.unbounded_send(Err(Error::BadResponse("websocket error".into())));
}
})
};
ws.set_onerror(Some(onerror.as_ref().unchecked_ref()));
let onclose = {
let tx = msg_tx.clone();
Closure::<dyn FnMut(web_sys::CloseEvent)>::new(move |_e: web_sys::CloseEvent| {
tx.close_channel();
})
};
ws.set_onclose(Some(onclose.as_ref().unchecked_ref()));
match open_rx.await {
Ok(Ok(())) => {}
Ok(Err(e)) => {
detach_and_close(&ws);
return Err(e);
}
Err(_) => {
detach_and_close(&ws);
return Err(Error::BadResponse(
"websocket closed before it opened".into(),
));
}
}
Ok(WebSocket {
ws,
rx: msg_rx,
_handlers: Handlers {
_onopen: onopen,
_onmessage: onmessage,
_onerror: onerror,
_onclose: onclose,
},
})
}
pub async fn recv(&mut self) -> Option<Result<WsMessage>> {
self.rx.next().await
}
pub fn send_text(&self, text: &str) -> Result<()> {
self.ws.send_with_str(text).map_err(js_err)
}
pub fn send_binary(&self, data: &[u8]) -> Result<()> {
self.ws.send_with_u8_array(data).map_err(js_err)
}
pub fn send(&self, msg: &WsMessage) -> Result<()> {
match msg {
WsMessage::Text(t) => self.send_text(t),
WsMessage::Binary(b) => self.send_binary(b),
}
}
pub fn close(&self) -> Result<()> {
self.ws.close().map_err(js_err)
}
pub fn close_with(&self, code: u16, reason: &str) -> Result<()> {
self.ws
.close_with_code_and_reason(code, reason)
.map_err(js_err)
}
pub fn split(self) -> (WsSink, WsStream) {
(
WsSink {
ws: self.ws.clone(),
},
WsStream {
rx: self.rx,
_handlers: self._handlers,
},
)
}
}
impl WsSink {
pub fn send_text(&self, text: &str) -> Result<()> {
self.ws.send_with_str(text).map_err(js_err)
}
pub fn send_binary(&self, data: &[u8]) -> Result<()> {
self.ws.send_with_u8_array(data).map_err(js_err)
}
pub fn send(&self, msg: &WsMessage) -> Result<()> {
match msg {
WsMessage::Text(t) => self.send_text(t),
WsMessage::Binary(b) => self.send_binary(b),
}
}
pub fn close(&self) -> Result<()> {
self.ws.close().map_err(js_err)
}
}
impl WsStream {
pub async fn recv(&mut self) -> Option<Result<WsMessage>> {
self.rx.next().await
}
}