use crate::*; use rustls_pki_types::Der; use wasm_bindgen::prelude::*; use wasm_bindgen_futures::JsFuture; use hyper::rt::Executor; use js_sys::ArrayBuffer; use std::future::Future; use wisp_mux::{extensions::udp::UdpProtocolExtensionBuilder, WispError}; #[wasm_bindgen] extern "C" { #[wasm_bindgen(js_namespace = console, js_name = debug)] pub fn console_debug(s: &str); #[wasm_bindgen(js_namespace = console, js_name = log)] pub fn console_log(s: &str); #[wasm_bindgen(js_namespace = console, js_name = error)] pub fn console_error(s: &str); } macro_rules! debug { ($($t:tt)*) => (utils::console_debug(&format_args!($($t)*).to_string())) } macro_rules! log { ($($t:tt)*) => (utils::console_log(&format_args!($($t)*).to_string())) } #[allow(unused_macros)] macro_rules! error { ($($t:tt)*) => (utils::console_error(&format_args!($($t)*).to_string())) } macro_rules! jerr { ($expr:expr) => { JsError::new($expr) }; } macro_rules! jval { ($expr:expr) => { JsValue::from($expr) }; } pub trait ReplaceErr { type Ok; fn replace_err(self, err: &str) -> Result; fn replace_err_jv(self, err: &str) -> Result; } impl ReplaceErr for Result { type Ok = T; fn replace_err(self, err: &str) -> Result<::Ok, JsError> { self.map_err(|x| jerr!(&format!("{}, original error: {:?}", err, x))) } fn replace_err_jv(self, err: &str) -> Result<::Ok, JsValue> { self.map_err(|x| jval!(&format!("{}, original error: {:?}", err, x))) } } impl ReplaceErr for Option { type Ok = T; fn replace_err(self, err: &str) -> Result<::Ok, JsError> { self.ok_or_else(|| jerr!(err)) } fn replace_err_jv(self, err: &str) -> Result<::Ok, JsValue> { self.ok_or_else(|| jval!(err)) } } // the... BOOLINATOR! impl ReplaceErr for bool { type Ok = (); fn replace_err(self, err: &str) -> Result<(), JsError> { if !self { Err(jerr!(err)) } else { Ok(()) } } fn replace_err_jv(self, err: &str) -> Result<(), JsValue> { if !self { Err(jval!(err)) } else { Ok(()) } } } // the... BOOLINATOR! pub trait Boolinator { fn flatten(self, err: &str) -> Result<(), JsError>; } impl Boolinator for Result { fn flatten(self, err: &str) -> Result<(), JsError> { self.replace_err(err)?.replace_err(err) } } pub trait UriExt { fn get_redirect(&self, location: &HeaderValue) -> Result; } impl UriExt for Uri { fn get_redirect(&self, location: &HeaderValue) -> Result { let new_uri = location.to_str()?.parse::()?; let mut new_parts: http::uri::Parts = new_uri.into(); if new_parts.scheme.is_none() { new_parts.scheme = self.scheme().cloned(); } if new_parts.authority.is_none() { new_parts.authority = self.authority().cloned(); } Ok(Uri::from_parts(new_parts)?) } } #[derive(Clone)] pub struct WasmExecutor; impl Executor for WasmExecutor where F: Future + Send + 'static, F::Output: Send + 'static, { fn execute(&self, future: F) { wasm_bindgen_futures::spawn_local(async move { let _ = future.await; }); } } pub fn entries_of_object(obj: &Object) -> Vec> { js_sys::Object::entries(obj) .to_vec() .iter() .filter_map(|val| { Array::from(val) .to_vec() .iter() .map(|val| val.as_string()) .collect::>>() }) .collect::>>() } pub fn define_property_obj(value: JsValue, writable: bool) -> Result { let entries: Array = [ Array::of2(&jval!("value"), &value), Array::of2(&jval!("writable"), &jval!(writable)), ] .iter() .collect::(); Object::from_entries(&entries) } pub fn is_redirect(code: u16) -> bool { [301, 302, 303, 307, 308].contains(&code) } pub fn is_null_body(code: u16) -> bool { [101, 204, 205, 304].contains(&code) } pub fn get_is_secure(url: &Uri) -> Result { let url_scheme_str = url.scheme_str().replace_err("URL must have a scheme")?; match url_scheme_str { "https" | "wss" => Ok(true), _ => Ok(false), } } pub fn get_url_port(url: &Uri) -> Result { if let Some(port) = url.port() { Ok(port.as_u16()) } else if get_is_secure(url)? { Ok(443) } else { Ok(80) } } pub async fn make_mux( url: &str, ) -> Result< ( ClientMux, impl Future> + Send, ), WispError, > { let (wtx, wrx) = WebSocketWrapper::connect(url, vec![]).map_err(|_| WispError::WsImplSocketClosed)?; wtx.wait_for_open().await; Ok( ClientMux::create(wrx, wtx, Some(&[Box::new(UdpProtocolExtensionBuilder())])) .await? .with_no_required_extensions(), ) } pub fn spawn_mux_fut( mux: Arc>, fut: impl Future> + Send + 'static, url: String, ) { wasm_bindgen_futures::spawn_local(async move { debug!("epoxy: mux future started"); if let Err(e) = fut.await { log!("epoxy: error in mux future, restarting: {:?}", e); while let Err(e) = replace_mux(mux.clone(), &url).await { log!("epoxy: failed to restart mux future: {:?}", e); wasmtimer::tokio::sleep(std::time::Duration::from_millis(500)).await; } } debug!("epoxy: mux future exited"); }); } pub async fn replace_mux(mux: Arc>, url: &str) -> Result<(), WispError> { let (mux_replace, fut) = make_mux(url).await?; let mut mux_write = mux.write().await; let _ = mux_write.close().await; *mux_write = mux_replace; drop(mux_write); spawn_mux_fut(mux, fut, url.into()); Ok(()) } pub async fn jval_to_u8_array(val: JsValue) -> Result { JsFuture::from( web_sys::Request::new_with_str_and_init( "/", web_sys::RequestInit::new().method("POST").body(Some(&val)), )? .array_buffer()?, ) .await? .dyn_into::() .map(|x| Uint8Array::new(&x)) } pub async fn jval_to_u8_array_req(val: JsValue) -> Result<(Uint8Array, web_sys::Request), JsValue> { let req = web_sys::Request::new_with_str_and_init( "/", web_sys::RequestInit::new().method("POST").body(Some(&val)), )?; Ok(( JsFuture::from(req.array_buffer()?) .await? .dyn_into::() .map(|x| Uint8Array::new(&x))?, req, )) } pub fn object_to_trustanchor(obj: JsValue) -> Result, JsValue> { let subject: Uint8Array = Reflect::get(&obj, &jval!("subject"))?.dyn_into()?; let pub_key_info: Uint8Array = Reflect::get(&obj, &jval!("subject_public_key_info"))?.dyn_into()?; let name_constraints: Option = Reflect::get(&obj, &jval!("name_constraints")) .and_then(|x| x.dyn_into()) .ok(); Ok(TrustAnchor { subject: Der::from(subject.to_vec()), subject_public_key_info: Der::from(pub_key_info.to_vec()), name_constraints: name_constraints.map(|x| Der::from(x.to_vec())), }) }