better compat with fetch api

This commit is contained in:
Toshit Chawda 2024-03-10 13:13:50 -07:00
parent 53a399856f
commit bed942eb75
8 changed files with 99 additions and 33 deletions

2
Cargo.lock generated
View file

@ -1849,7 +1849,7 @@ checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8"
[[package]] [[package]]
name = "wisp-mux" name = "wisp-mux"
version = "1.2.1" version = "1.2.2"
dependencies = [ dependencies = [
"async_io_stream", "async_io_stream",
"bytes", "bytes",

View file

@ -1,6 +1,6 @@
[package] [package]
name = "epoxy-client" name = "epoxy-client"
version = "1.4.0" version = "1.4.1"
edition = "2021" edition = "2021"
license = "LGPL-3.0-only" license = "LGPL-3.0-only"
@ -19,7 +19,7 @@ futures-util = "0.3.30"
js-sys = "0.3.66" js-sys = "0.3.66"
webpki-roots = "0.26.0" webpki-roots = "0.26.0"
tokio-rustls = "0.25.0" tokio-rustls = "0.25.0"
web-sys = { version = "0.3.66", features = ["TextEncoder", "Response", "ResponseInit", "WebSocket", "BinaryType", "MessageEvent"] } web-sys = { version = "0.3.66", features = ["Request", "RequestInit", "Headers", "Response", "ResponseInit", "WebSocket", "BinaryType", "MessageEvent"] }
wasm-streams = "0.4.0" wasm-streams = "0.4.0"
tokio-util = { version = "0.7.10", features = ["io"] } tokio-util = { version = "0.7.10", features = ["io"] }
async-compression = { version = "0.4.5", features = ["tokio", "gzip", "brotli"] } async-compression = { version = "0.4.5", features = ["tokio", "gzip", "brotli"] }

View file

@ -58,16 +58,24 @@ onmessage = async (msg) => {
}; };
if (should_feature_test) { if (should_feature_test) {
let formdata = new FormData();
formdata.append("a", "b");
for (const url of [ for (const url of [
["https://httpbin.org/get", {}], ["https://httpbin.org/get", {}],
["https://httpbin.org/gzip", {}], ["https://httpbin.org/gzip", {}],
["https://httpbin.org/brotli", {}], ["https://httpbin.org/brotli", {}],
["https://httpbin.org/redirect/11", {}], ["https://httpbin.org/redirect/11", {}],
["https://httpbin.org/redirect/1", { redirect: "manual" }], ["https://httpbin.org/redirect/1", { redirect: "manual" }],
["https://httpbin.org/post", { method: "POST", body: new URLSearchParams("a=b") }],
["https://httpbin.org/post", { method: "POST", body: formdata }],
["https://httpbin.org/post", { method: "POST", body: "a" }],
["https://httpbin.org/post", { method: "POST", body: (new TextEncoder()).encode("abc") }],
["https://httpbin.org/get", { headers: {"a": "b", "b": "c"} }],
["https://httpbin.org/get", { headers: new Headers({"a": "b", "b": "c"}) }]
]) { ]) {
let resp = await epoxy_client.fetch(url[0], url[1]); let resp = await epoxy_client.fetch(url[0], url[1]);
console.warn(url, resp, Object.fromEntries(resp.headers)); console.warn(url, resp, Object.fromEntries(resp.headers));
console.warn(await resp.text()); log(await resp.text());
} }
} else if (should_multiparallel_test) { } else if (should_multiparallel_test) {
const num_tests = 10; const num_tests = 10;
@ -183,7 +191,7 @@ onmessage = async (msg) => {
msg => { console.log(msg); log(decoder.decode(msg)) }, msg => { console.log(msg); log(decoder.decode(msg)) },
"google.com:443", "google.com:443",
); );
await ws.send((new TextEncoder()).encode("GET / HTTP 1.1\r\nHost: google.com\r\nConnection: close\r\n\r\n")); await ws.send("GET / HTTP 1.1\r\nHost: google.com\r\nConnection: close\r\n\r\n");
await (new Promise((res, _) => setTimeout(res, 500))); await (new Promise((res, _) => setTimeout(res, 500)));
await ws.close(); await ws.close();
} else if (should_udp_test) { } else if (should_udp_test) {
@ -198,7 +206,7 @@ onmessage = async (msg) => {
); );
while (true) { while (true) {
log("sending `data`"); log("sending `data`");
await ws.send((new TextEncoder()).encode("data")); await ws.send("data");
await (new Promise((res, _) => setTimeout(res, 50))); await (new Promise((res, _) => setTimeout(res, 50)));
} }
} else if (should_reconnect_test) { } else if (should_reconnect_test) {

View file

@ -1,6 +1,6 @@
{ {
"name": "@mercuryworkshop/epoxy-tls", "name": "@mercuryworkshop/epoxy-tls",
"version": "1.4.0", "version": "1.4.1",
"description": "A wasm library for using raw encrypted tls/ssl/https/websocket streams on the browser", "description": "A wasm library for using raw encrypted tls/ssl/https/websocket streams on the browser",
"scripts": { "scripts": {
"build": "./build.sh" "build": "./build.sh"

View file

@ -32,7 +32,6 @@ use tokio_util::{
io::{ReaderStream, StreamReader}, io::{ReaderStream, StreamReader},
}; };
use wasm_bindgen::{intern, prelude::*}; use wasm_bindgen::{intern, prelude::*};
use web_sys::TextEncoder;
use wisp_mux::{ClientMux, MuxStreamIo, StreamType}; use wisp_mux::{ClientMux, MuxStreamIo, StreamType};
type HttpBody = http_body_util::Full<Bytes>; type HttpBody = http_body_util::Full<Bytes>;
@ -58,6 +57,7 @@ fn init() {
// utils.rs // utils.rs
intern("value"); intern("value");
intern("writable"); intern("writable");
intern("POST");
// main.rs // main.rs
intern("method"); intern("method");
@ -67,6 +67,7 @@ fn init() {
intern("url"); intern("url");
intern("redirected"); intern("redirected");
intern("rawHeaders"); intern("rawHeaders");
intern("Content-Type");
} }
fn cert_to_jval(cert: &TrustAnchor) -> Result<JsValue, JsValue> { fn cert_to_jval(cert: &TrustAnchor) -> Result<JsValue, JsValue> {
@ -310,31 +311,24 @@ impl EpoxyClient {
Err(_) => true, Err(_) => true,
}; };
let mut body_content_type: Option<String> = None;
let body_jsvalue: Option<JsValue> = Reflect::get(&options, &jval!("body")).ok(); let body_jsvalue: Option<JsValue> = Reflect::get(&options, &jval!("body")).ok();
let body = if let Some(val) = body_jsvalue { let body_bytes: Bytes = match body_jsvalue {
if val.is_string() { Some(buf) => {
let str = val let (body, req) = utils::jval_to_u8_array_req(buf)
.as_string() .await
.replace_err("Failed to get string from body")?; .replace_err("Invalid body")?;
let encoder = body_content_type = req.headers().get("Content-Type").ok().flatten();
TextEncoder::new().replace_err("Failed to create TextEncoder for body")?; Bytes::from(body.to_vec())
let encoded = encoder.encode_with_input(str.as_ref());
Some(encoded)
} else {
Some(Uint8Array::new(&val).to_vec())
} }
} else {
None
};
let body_bytes: Bytes = match body {
Some(vec) => Bytes::from(vec),
None => Bytes::new(), None => Bytes::new(),
}; };
let headers = Reflect::get(&options, &jval!("headers")) let headers = Reflect::get(&options, &jval!("headers"))
.map(|val| { .map(|val| {
if val.is_truthy() { if web_sys::Headers::instanceof(&val) {
Some(utils::entries_of_object(&Object::from_entries(&val).ok()?))
} else if val.is_truthy() {
Some(utils::entries_of_object(&Object::from(val))) Some(utils::entries_of_object(&Object::from(val)))
} else { } else {
None None
@ -352,6 +346,9 @@ impl EpoxyClient {
if body_bytes.is_empty() { if body_bytes.is_empty() {
headers_map.insert("Content-Length", HeaderValue::from_static("0")); headers_map.insert("Content-Length", HeaderValue::from_static("0"));
} }
if let Some(content_type) = body_content_type {
headers_map.insert("Content-Type", HeaderValue::from_str(&content_type)?);
}
if let Some(headers) = headers { if let Some(headers) = headers {
for hdr in headers { for hdr in headers {

View file

@ -49,7 +49,11 @@ impl EpxTlsStream {
.call0(&Object::default()) .call0(&Object::default())
.replace_err("Failed to call onopen")?; .replace_err("Failed to call onopen")?;
Ok(Self { tx, onerror, url: url.to_string() }) Ok(Self {
tx,
onerror,
url: url.to_string(),
})
} }
.await; .await;
if let Err(ret) = ret { if let Err(ret) = ret {
@ -61,9 +65,17 @@ impl EpxTlsStream {
} }
#[wasm_bindgen] #[wasm_bindgen]
pub async fn send(&mut self, payload: Uint8Array) -> Result<(), JsError> { pub async fn send(&mut self, payload: JsValue) -> Result<(), JsError> {
let onerr = self.onerror.clone(); let onerr = self.onerror.clone();
let ret = self.tx.write_all(&payload.to_vec()).await; let ret = self
.tx
.write_all(
&utils::jval_to_u8_array(payload)
.await
.replace_err("Invalid payload")?
.to_vec(),
)
.await;
if let Err(ret) = ret { if let Err(ret) = ret {
let _ = onerr.call1(&JsValue::null(), &jval!(format!("{}", ret))); let _ = onerr.call1(&JsValue::null(), &jval!(format!("{}", ret)));
Err(ret.into()) Err(ret.into())

View file

@ -71,9 +71,17 @@ impl EpxUdpStream {
} }
#[wasm_bindgen] #[wasm_bindgen]
pub async fn send(&mut self, payload: Uint8Array) -> Result<(), JsError> { pub async fn send(&mut self, payload: JsValue) -> Result<(), JsError> {
let onerr = self.onerror.clone(); let onerr = self.onerror.clone();
let ret = self.tx.send(payload.to_vec()).await; let ret = self
.tx
.send(
utils::jval_to_u8_array(payload)
.await
.replace_err("Invalid payload")?
.to_vec(),
)
.await;
if let Err(ret) = ret { if let Err(ret) = ret {
let _ = onerr.call1(&JsValue::null(), &jval!(format!("{}", ret))); let _ = onerr.call1(&JsValue::null(), &jval!(format!("{}", ret)));
Err(ret.into()) Err(ret.into())

View file

@ -1,8 +1,10 @@
use crate::*; use crate::*;
use wasm_bindgen::prelude::*; use wasm_bindgen::prelude::*;
use wasm_bindgen_futures::JsFuture;
use hyper::rt::Executor; use hyper::rt::Executor;
use js_sys::ArrayBuffer;
use std::future::Future; use std::future::Future;
use wisp_mux::{CloseReason, WispError}; use wisp_mux::{CloseReason, WispError};
@ -188,7 +190,15 @@ pub fn get_url_port(url: &Uri) -> Result<u16, JsError> {
} }
} }
pub async fn make_mux(url: &str) -> Result<(ClientMux<WebSocketWrapper>, impl Future<Output = Result<(), WispError>>), WispError> { pub async fn make_mux(
url: &str,
) -> Result<
(
ClientMux<WebSocketWrapper>,
impl Future<Output = Result<(), WispError>>,
),
WispError,
> {
let (wtx, wrx) = WebSocketWrapper::connect(url, vec![]) let (wtx, wrx) = WebSocketWrapper::connect(url, vec![])
.await .await
.map_err(|_| WispError::WsImplSocketClosed)?; .map_err(|_| WispError::WsImplSocketClosed)?;
@ -198,7 +208,11 @@ pub async fn make_mux(url: &str) -> Result<(ClientMux<WebSocketWrapper>, impl Fu
Ok(mux) Ok(mux)
} }
pub fn spawn_mux_fut(mux: Arc<RwLock<ClientMux<WebSocketWrapper>>>, fut: impl Future<Output = Result<(), WispError>> + 'static, url: String) { pub fn spawn_mux_fut(
mux: Arc<RwLock<ClientMux<WebSocketWrapper>>>,
fut: impl Future<Output = Result<(), WispError>> + 'static,
url: String,
) {
wasm_bindgen_futures::spawn_local(async move { wasm_bindgen_futures::spawn_local(async move {
if let Err(e) = fut.await { if let Err(e) = fut.await {
error!("epoxy: error in mux future, restarting: {:?}", e); error!("epoxy: error in mux future, restarting: {:?}", e);
@ -223,3 +237,30 @@ pub async fn replace_mux(
spawn_mux_fut(mux, fut, url.into()); spawn_mux_fut(mux, fut, url.into());
Ok(()) Ok(())
} }
pub async fn jval_to_u8_array(val: JsValue) -> Result<Uint8Array, JsValue> {
JsFuture::from(
web_sys::Request::new_with_str_and_init(
"/",
web_sys::RequestInit::new().method("POST").body(Some(&val)),
)?
.array_buffer()?,
)
.await?
.dyn_into::<ArrayBuffer>()
.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::<ArrayBuffer>()
.map(|x| Uint8Array::new(&x))?,
req,
))
}