remove non esmodules builds, use a js snippet

This commit is contained in:
Toshit Chawda 2024-07-27 20:52:05 -07:00
parent 1a01197764
commit 98526aa347
No known key found for this signature in database
GPG key ID: 91480ED99E2B3D9D
8 changed files with 154 additions and 160 deletions

13
Cargo.lock generated
View file

@ -227,12 +227,6 @@ version = "0.21.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
[[package]]
name = "base64"
version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]] [[package]]
name = "bitflags" name = "bitflags"
version = "1.3.2" version = "1.3.2"
@ -512,7 +506,6 @@ version = "2.1.0"
dependencies = [ dependencies = [
"async-compression", "async-compression",
"async-trait", "async-trait",
"base64 0.22.1",
"bytes", "bytes",
"cfg-if", "cfg-if",
"event-listener", "event-listener",
@ -591,7 +584,7 @@ name = "fastwebsockets"
version = "0.8.0" version = "0.8.0"
source = "git+https://github.com/r58Playz/fastwebsockets#9152ec2e28512feeb93d3aba3b516f07355025b6" source = "git+https://github.com/r58Playz/fastwebsockets#9152ec2e28512feeb93d3aba3b516f07355025b6"
dependencies = [ dependencies = [
"base64 0.21.7", "base64",
"bytes", "bytes",
"http-body-util", "http-body-util",
"hyper 1.4.1", "hyper 1.4.1",
@ -840,7 +833,7 @@ version = "7.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "765c9198f173dd59ce26ff9f95ef0aafd0a0fe01fb9d72841bc5066a4c06511d" checksum = "765c9198f173dd59ce26ff9f95ef0aafd0a0fe01fb9d72841bc5066a4c06511d"
dependencies = [ dependencies = [
"base64 0.21.7", "base64",
"byteorder", "byteorder",
"flate2", "flate2",
"nom", "nom",
@ -1811,7 +1804,7 @@ dependencies = [
"async-stream", "async-stream",
"async-trait", "async-trait",
"axum", "axum",
"base64 0.21.7", "base64",
"bytes", "bytes",
"h2 0.3.26", "h2 0.3.26",
"http 0.2.12", "http 0.2.12",

View file

@ -9,7 +9,6 @@ crate-type = ["cdylib"]
[dependencies] [dependencies]
async-compression = { version = "0.4.11", features = ["futures-io", "gzip", "brotli"], optional = true } async-compression = { version = "0.4.11", features = ["futures-io", "gzip", "brotli"], optional = true }
async-trait = "0.1.80" async-trait = "0.1.80"
base64 = { version = "0.22.1", optional = true }
bytes = "1.6.0" bytes = "1.6.0"
cfg-if = "1.0.0" cfg-if = "1.0.0"
event-listener = "5.3.1" event-listener = "5.3.1"
@ -17,7 +16,6 @@ fastwebsockets = { version = "0.8.0", features = ["unstable-split"], optional =
flume = "0.11.0" flume = "0.11.0"
futures-rustls = { version = "0.26.0", default-features = false, features = ["tls12", "ring"] } futures-rustls = { version = "0.26.0", default-features = false, features = ["tls12", "ring"] }
futures-util = { version = "0.3.30", features = ["sink"] } futures-util = { version = "0.3.30", features = ["sink"] }
getrandom = { version = "0.2.15", features = ["js", "std"], optional = true }
http = "1.1.0" http = "1.1.0"
http-body-util = "0.1.2" http-body-util = "0.1.2"
hyper = "1.3.1" hyper = "1.3.1"
@ -35,6 +33,10 @@ web-sys = { version = "0.3.69", features = ["BinaryType", "Headers", "MessageEve
webpki-roots = "0.26.3" webpki-roots = "0.26.3"
wisp-mux = { path = "../wisp", features = ["wasm"] } wisp-mux = { path = "../wisp", features = ["wasm"] }
[dependencies.getrandom]
version = "*"
features = ["js"]
[dependencies.ring] [dependencies.ring]
version = "*" version = "*"
features = ["wasm32_unknown_unknown_js"] features = ["wasm32_unknown_unknown_js"]
@ -49,5 +51,5 @@ features = ["nightly"]
[features] [features]
default = ["full"] default = ["full"]
full = ["fastwebsockets", "base64", "async-compression", "getrandom", "hyper-util-wasm/http2"] full = ["fastwebsockets", "async-compression", "hyper-util-wasm/http2"]

View file

@ -8,7 +8,7 @@ mkdir pkg/
RUSTFLAGS='-C target-feature=+atomics,+bulk-memory -Zlocation-detail=none' cargo build --target wasm32-unknown-unknown -Z build-std=panic_abort,std -Z build-std-features=panic_immediate_abort,optimize_for_size --release "$@" RUSTFLAGS='-C target-feature=+atomics,+bulk-memory -Zlocation-detail=none' cargo build --target wasm32-unknown-unknown -Z build-std=panic_abort,std -Z build-std-features=panic_immediate_abort,optimize_for_size --release "$@"
echo "[epx] cargo finished" echo "[epx] cargo finished"
wasm-bindgen --weak-refs --target no-modules --no-modules-global epoxy --out-dir out/ ../target/wasm32-unknown-unknown/release/epoxy_client.wasm wasm-bindgen --target web --out-dir out/ ../target/wasm32-unknown-unknown/release/epoxy_client.wasm
echo "[epx] wasm-bindgen finished" echo "[epx] wasm-bindgen finished"
if ! [ "${RELEASE:-0}" = "1" ]; then if ! [ "${RELEASE:-0}" = "1" ]; then
@ -21,7 +21,21 @@ mv out/epoxy_client_bg.wasm out/epoxy_client_unoptimized.wasm
time wasm-opt $WASMOPTFLAGS -Oz --vacuum --dce --enable-threads --enable-bulk-memory out/epoxy_client_unoptimized.wasm -o out/epoxy_client_bg.wasm time wasm-opt $WASMOPTFLAGS -Oz --vacuum --dce --enable-threads --enable-bulk-memory out/epoxy_client_unoptimized.wasm -o out/epoxy_client_bg.wasm
echo "[epx] wasm-opt finished" echo "[epx] wasm-opt finished"
# === js ===
AUTOGENERATED_SOURCE=$(<"out/epoxy_client.js") AUTOGENERATED_SOURCE=$(<"out/epoxy_client.js")
AUTOGENERATED_SNIPPET_PATH=$(<"out/epoxy_client.js")
# remove everything before the snippet path quote (which is the first quote in the file)
AUTOGENERATED_SNIPPET_PATH=${AUTOGENERATED_SNIPPET_PATH#*$'\''}
# remove everything after the snippet path quote (which is the second quote in the file)
AUTOGENERATED_SNIPPET_PATH=${AUTOGENERATED_SNIPPET_PATH%%$'\''*}
# replace a dot at the start of the var with out
AUTOGENERATED_SNIPPET=$(base64 -w0 ${AUTOGENERATED_SNIPPET_PATH/#./out})
AUTOGENERATED_SOURCE=${AUTOGENERATED_SOURCE//${AUTOGENERATED_SNIPPET_PATH}/data:application/javascript$';'base64,${AUTOGENERATED_SNIPPET}}
# patch for websocket sharedarraybuffer error # patch for websocket sharedarraybuffer error
AUTOGENERATED_SOURCE=${AUTOGENERATED_SOURCE//getObject(arg0).send(getArrayU8FromWasm0(arg1, arg2)/getObject(arg0).send(new Uint8Array(getArrayU8FromWasm0(arg1, arg2)).buffer} AUTOGENERATED_SOURCE=${AUTOGENERATED_SOURCE//getObject(arg0).send(getArrayU8FromWasm0(arg1, arg2)/getObject(arg0).send(new Uint8Array(getArrayU8FromWasm0(arg1, arg2)).buffer}
# patch for safari OOM errors on safari iOS 16/older devices # patch for safari OOM errors on safari iOS 16/older devices
@ -29,32 +43,36 @@ AUTOGENERATED_SOURCE=${AUTOGENERATED_SOURCE//getObject(arg0).send(getArrayU8From
AUTOGENERATED_SOURCE=${AUTOGENERATED_SOURCE//maximum:16384,shared:true/maximum:/iPad|iPhone|iPod/.test(navigator.userAgent)?4096:8192,shared:true} AUTOGENERATED_SOURCE=${AUTOGENERATED_SOURCE//maximum:16384,shared:true/maximum:/iPad|iPhone|iPod/.test(navigator.userAgent)?4096:8192,shared:true}
# patch to set proper wasm path # patch to set proper wasm path
AUTOGENERATED_SOURCE=${AUTOGENERATED_SOURCE//'_bg.wasm'/'.wasm'} AUTOGENERATED_SOURCE=${AUTOGENERATED_SOURCE//'_bg.wasm'/'.wasm'}
echo "$AUTOGENERATED_SOURCE" > pkg/epoxy.js AUTOGENERATED_SOURCE=${AUTOGENERATED_SOURCE//'epoxy_client.wasm'/'epoxy.wasm'}
cp pkg/epoxy.js pkg/epoxy-module.js # delete initSync export
echo "export default epoxy;" >> pkg/epoxy-module.js AUTOGENERATED_SOURCE=${AUTOGENERATED_SOURCE//export $'{' initSync $'}\n'/}
# don't export internals
AUTOGENERATED_SOURCE=${AUTOGENERATED_SOURCE//return __wbg_finalize_init/__wbg_finalize_init}
echo "$AUTOGENERATED_SOURCE" > pkg/epoxy.js
WASM_BASE64=$(base64 -w0 out/epoxy_client_bg.wasm) WASM_BASE64=$(base64 -w0 out/epoxy_client_bg.wasm)
AUTOGENERATED_SOURCE=${AUTOGENERATED_SOURCE//__wbg_init(input, maybe_memory) \{/__wbg_init(maybe_memory) \{$'\n'let input=\'data:application/wasm;base64,$WASM_BASE64\'} AUTOGENERATED_SOURCE=${AUTOGENERATED_SOURCE//__wbg_init(input, maybe_memory) \{/__wbg_init(maybe_memory) \{$'\n'let input=\'data:application/wasm;base64,$WASM_BASE64\'}
AUTOGENERATED_SOURCE=${AUTOGENERATED_SOURCE//return __wbg_finalize_init(instance, module);/__wbg_finalize_init(instance, module);$'\n'return epoxy;}
AUTOGENERATED_SOURCE=${AUTOGENERATED_SOURCE//if (wasm !== undefined) return wasm;/if (wasm !== undefined) return epoxy;}
echo "$AUTOGENERATED_SOURCE" > pkg/epoxy-bundled.js echo "$AUTOGENERATED_SOURCE" > pkg/epoxy-bundled.js
cp pkg/epoxy-bundled.js pkg/epoxy-module-bundled.js # === types ===
echo "export default epoxy;" >> pkg/epoxy-module-bundled.js
AUTOGENERATED_TYPEDEFS=$(<"out/epoxy_client.d.ts") AUTOGENERATED_TYPES=$(<"out/epoxy_client.d.ts")
AUTOGENERATED_TYPEDEFS=${AUTOGENERATED_TYPEDEFS%%export class IntoUnderlyingByteSource*}
echo "$AUTOGENERATED_TYPEDEFS" > pkg/epoxy-module.d.ts AUTOGENERATED_TYPES=${AUTOGENERATED_TYPES//$'\n'export interface InitOutput*InitOutput;$'\n'/}
echo -e "}\ndeclare type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module;" >> pkg/epoxy-module.d.ts AUTOGENERATED_TYPES=${AUTOGENERATED_TYPES//Promise<InitOutput>/Promise<void>}
echo -e "export default function epoxy(module_or_path?: InitInput | Promise<InitInput>, maybe_memory?: WebAssembly.Memory): Promise<typeof wasm_bindgen>;" >> pkg/epoxy-module.d.ts
echo "$AUTOGENERATED_TYPEDEFS" > pkg/epoxy-module-bundled.d.ts echo "$AUTOGENERATED_TYPES" > pkg/epoxy.d.ts
echo -e "}\nexport default function epoxy(maybe_memory?: WebAssembly.Memory): Promise<typeof wasm_bindgen>;" >> pkg/epoxy-module-bundled.d.ts
echo "$AUTOGENERATED_TYPEDEFS" > pkg/epoxy-bundled.d.ts # remove useless comment
echo -e "}\ndeclare function epoxy(maybe_memory?: WebAssembly.Memory): Promise<typeof wasm_bindgen>;" >> pkg/epoxy-bundled.d.ts AUTOGENERATED_TYPES=${AUTOGENERATED_TYPES//$'\n*' If $'`'module_or_path*$'}' module_or_path/}
AUTOGENERATED_TYPES=${AUTOGENERATED_TYPES//module_or_path*, /}
echo "$AUTOGENERATED_TYPES" > pkg/epoxy-bundled.d.ts
cp out/epoxy_client.d.ts pkg/epoxy.d.ts
cp out/epoxy_client_bg.wasm pkg/epoxy.wasm cp out/epoxy_client_bg.wasm pkg/epoxy.wasm
rm -r out/ rm -r out/

View file

@ -1,4 +1,4 @@
import epoxyModule from "./pkg/epoxy-module-bundled.js"; import initEpoxy, { EpoxyClient, EpoxyClientOptions, EpoxyHandlers } from "./pkg/epoxy-bundled.js";
(async () => { (async () => {
const params = (new URL(location.href)).searchParams; const params = (new URL(location.href)).searchParams;
@ -25,15 +25,7 @@ import epoxyModule from "./pkg/epoxy-module-bundled.js";
window.scrollTo(0, document.body.scrollHeight); window.scrollTo(0, document.body.scrollHeight);
} }
const plog = (str) => { await initEpoxy();
console.log(str);
}
const epoxy = await epoxyModule();
const EpoxyClientOptions = epoxy.EpoxyClientOptions;
const EpoxyClient = epoxy.EpoxyClient;
const EpoxyHandlers = epoxy.EpoxyHandlers;
let epoxy_client_options = new EpoxyClientOptions(); let epoxy_client_options = new EpoxyClientOptions();
epoxy_client_options.user_agent = navigator.userAgent; epoxy_client_options.user_agent = navigator.userAgent;
@ -234,7 +226,7 @@ import epoxyModule from "./pkg/epoxy-module-bundled.js";
while (true) { while (true) {
log("sending `data`"); log("sending `data`");
await ws.send("data"); await ws.send("data");
await (new Promise((res, _) => setTimeout(res, 10))); await (new Promise((res, _) => setTimeout(res, 100)));
} }
} else if (should_reconnect_test) { } else if (should_reconnect_test) {
while (true) { while (true) {

View file

@ -21,28 +21,28 @@
"license": "AGPL-3.0-only", "license": "AGPL-3.0-only",
"exports": { "exports": {
".": { ".": {
"import": "./full/epoxy-module-bundled.js", "import": "./full/epoxy-bundled.js",
"types": "./full/epoxy-module-bundled.d.ts" "types": "./full/epoxy-bundled.d.ts"
}, },
"./epoxy": { "./epoxy": {
"import": "./full/epoxy-module.js", "import": "./full/epoxy.js",
"types": "./full/epoxy-module.d.ts" "types": "./full/epoxy.d.ts"
}, },
"./epoxy-bundled": { "./epoxy-bundled": {
"import": "./full/epoxy-module-bundled.js", "import": "./full/epoxy-bundled.js",
"types": "./full/epoxy-module-bundled.d.ts" "types": "./full/epoxy-bundled.d.ts"
}, },
"./minimal-epoxy": { "./minimal-epoxy": {
"import": "./minimal/epoxy-module.js", "import": "./minimal/epoxy.js",
"types": "./minimal/epoxy-module.d.ts" "types": "./minimal/epoxy.d.ts"
}, },
"./minimal-epoxy-bundled": { "./minimal-epoxy-bundled": {
"import": "./minimal/epoxy-module-bundled.js", "import": "./minimal/epoxy-bundled.js",
"types": "./minimal/epoxy-module-bundled.d.ts" "types": "./minimal/epoxy-bundled.d.ts"
} }
}, },
"browser": "./full/epoxy-module-bundled.js", "browser": "./full/epoxy-bundled.js",
"module": "./full/epoxy-module-bundled.js", "module": "./full/epoxy-bundled.js",
"main": "./full/epoxy-module-bundled.js", "main": "./full/epoxy-bundled.js",
"types": "./full/epoxy-module-bundled.d.ts" "types": "./full/epoxy-bundled.d.ts"
} }

View file

@ -18,12 +18,12 @@ use hyper::{body::Incoming, Uri};
use hyper_util_wasm::client::legacy::Client; use hyper_util_wasm::client::legacy::Client;
#[cfg(feature = "full")] #[cfg(feature = "full")]
use io_stream::{EpoxyIoStream, EpoxyUdpStream}; use io_stream::{EpoxyIoStream, EpoxyUdpStream};
use js_sys::{Array, Function, Object, Reflect}; use js_sys::{Array, Function, Object};
use stream_provider::{StreamProvider, StreamProviderService}; use stream_provider::{StreamProvider, StreamProviderService};
use thiserror::Error; use thiserror::Error;
use utils::{ use utils::{
asyncread_to_readablestream_stream, convert_body, entries_of_object, is_null_body, is_redirect, asyncread_to_readablestream_stream, convert_body, entries_of_object, is_null_body, is_redirect,
object_get, object_set, IncomingBody, UriExt, WasmExecutor, object_get, object_set, object_truthy, IncomingBody, UriExt, WasmExecutor,
}; };
use wasm_bindgen::prelude::*; use wasm_bindgen::prelude::*;
use wasm_streams::ReadableStream; use wasm_streams::ReadableStream;
@ -97,10 +97,6 @@ pub enum EpoxyError {
ResponseHeadersFromEntriesFailed, ResponseHeadersFromEntriesFailed,
#[error("Failed to construct response object")] #[error("Failed to construct response object")]
ResponseNewFailed, ResponseNewFailed,
#[error("Failed to construct define_property object")]
DefinePropertyObjFailed,
#[error("Failed to set raw header item")]
RawHeaderSetFailed,
} }
impl From<EpoxyError> for JsValue { impl From<EpoxyError> for JsValue {
@ -222,10 +218,7 @@ pub struct EpoxyClient {
#[wasm_bindgen] #[wasm_bindgen]
impl EpoxyClient { impl EpoxyClient {
#[wasm_bindgen(constructor)] #[wasm_bindgen(constructor)]
pub fn new( pub fn new(wisp_url: String, options: EpoxyClientOptions) -> Result<EpoxyClient, EpoxyError> {
wisp_url: String,
options: EpoxyClientOptions,
) -> Result<EpoxyClient, EpoxyError> {
let wisp_url: Uri = wisp_url.try_into()?; let wisp_url: Uri = wisp_url.try_into()?;
if wisp_url.scheme_str() != Some("wss") && wisp_url.scheme_str() != Some("ws") { if wisp_url.scheme_str() != Some("wss") && wisp_url.scheme_str() != Some("ws") {
return Err(EpoxyError::InvalidUrlScheme); return Err(EpoxyError::InvalidUrlScheme);
@ -406,32 +399,31 @@ impl EpoxyClient {
let host = url.host().ok_or(EpoxyError::NoUrlHost)?; let host = url.host().ok_or(EpoxyError::NoUrlHost)?;
let request_method = object_get(&options, "method") let request_method = object_get(&options, "method")
.and_then(|x| x.as_string()) .as_string()
.unwrap_or_else(|| "GET".to_string()); .unwrap_or_else(|| "GET".to_string());
let request_method: Method = Method::from_str(&request_method)?; let request_method: Method = Method::from_str(&request_method)?;
let request_redirect = object_get(&options, "redirect") let request_redirect = !matches!(
.map(|x| { object_get(&options, "redirect")
!matches!( .as_string()
x.as_string().unwrap_or_default().as_str(), .unwrap_or_default()
"error" | "manual" .as_str(),
) "error" | "manual"
}) );
.unwrap_or(true);
let mut body_content_type: Option<String> = None; let mut body_content_type: Option<String> = None;
let body = match object_get(&options, "body") { let body = match object_truthy(object_get(&options, "body")) {
Some(buf) => { Some(buf) => {
let (body, req) = convert_body(buf) let (body, content_type) = convert_body(buf)
.await .await
.map_err(|_| EpoxyError::InvalidRequestBody)?; .map_err(|_| EpoxyError::InvalidRequestBody)?;
body_content_type = req.headers().get("Content-Type").ok().flatten(); body_content_type = content_type;
Bytes::from(body.to_vec()) Bytes::from(body.to_vec())
} }
None => Bytes::new(), None => Bytes::new(),
}; };
let headers = object_get(&options, "headers").and_then(|val| { let headers = object_truthy(object_get(&options, "headers")).and_then(|val| {
if web_sys::Headers::instanceof(&val) { if web_sys::Headers::instanceof(&val) {
Some(entries_of_object(&Object::from_entries(&val).ok()?)) Some(entries_of_object(&Object::from_entries(&val).ok()?))
} else if val.is_truthy() { } else if val.is_truthy() {
@ -548,42 +540,25 @@ impl EpoxyClient {
) )
.map_err(|_| EpoxyError::ResponseNewFailed)?; .map_err(|_| EpoxyError::ResponseNewFailed)?;
Object::define_property( utils::define_property(&resp, "url", response_uri.to_string().into());
&resp, utils::define_property(&resp, "redirected", redirected.into());
&"url".into(),
&utils::define_property_obj(response_uri.to_string().into(), false)
.map_err(|_| EpoxyError::DefinePropertyObjFailed)?,
);
Object::define_property(
&resp,
&"redirected".into(),
&utils::define_property_obj(redirected.into(), false)
.map_err(|_| EpoxyError::DefinePropertyObjFailed)?,
);
let raw_headers = Object::new(); let raw_headers = Object::new();
for (k, v) in response_headers_raw.iter() { for (k, v) in response_headers_raw.iter() {
let k: JsValue = k.to_string().into(); let k = k.as_str();
let v: JsValue = v.to_str()?.to_string().into(); let v: JsValue = v.to_str()?.to_string().into();
if let Ok(jv) = Reflect::get(&raw_headers, &k) { let jv = object_get(&raw_headers, k);
if jv.is_array() { if jv.is_array() {
let arr = Array::from(&jv); let arr = Array::from(&jv);
arr.push(&v); arr.push(&v);
object_set(&raw_headers, &k, &arr)?; object_set(&raw_headers, &k, arr.into());
} else if jv.is_truthy() { } else if jv.is_truthy() {
object_set(&raw_headers, &k, &Array::of2(&jv, &v))?; object_set(&raw_headers, &k, Array::of2(&jv, &v).into());
} else { } else {
object_set(&raw_headers, &k, &v)?; object_set(&raw_headers, &k, v);
}
} }
} }
Object::define_property( utils::define_property(&resp, "rawHeaders", raw_headers.into());
&resp,
&"rawHeaders".into(),
&utils::define_property_obj(raw_headers.into(), false)
.map_err(|_| EpoxyError::DefinePropertyObjFailed)?,
);
Ok(resp) Ok(resp)
} }

View file

@ -7,10 +7,9 @@ use bytes::{buf::UninitSlice, BufMut, Bytes, BytesMut};
use futures_util::{ready, AsyncRead, Future, Stream, TryStreamExt}; use futures_util::{ready, AsyncRead, Future, Stream, TryStreamExt};
use http::{HeaderValue, Uri}; use http::{HeaderValue, Uri};
use hyper::{body::Body, rt::Executor}; use hyper::{body::Body, rt::Executor};
use js_sys::{Array, ArrayBuffer, Object, Reflect, Uint8Array}; use js_sys::{Array, JsString, Object, Uint8Array};
use pin_project_lite::pin_project; use pin_project_lite::pin_project;
use wasm_bindgen::{prelude::*, JsCast, JsValue}; use wasm_bindgen::{prelude::*, JsCast, JsValue};
use wasm_bindgen_futures::JsFuture;
use crate::EpoxyError; use crate::EpoxyError;
@ -177,54 +176,65 @@ pub fn is_null_body(code: u16) -> bool {
[101, 204, 205, 304].contains(&code) [101, 204, 205, 304].contains(&code)
} }
pub fn object_get(obj: &Object, key: &str) -> Option<JsValue> { #[wasm_bindgen(inline_js = r#"
Reflect::get(obj, &key.into()).ok() export function object_get(obj, k) {
} try {
return obj[k]
pub fn object_set(obj: &Object, key: &JsValue, value: &JsValue) -> Result<(), EpoxyError> { } catch {
if Reflect::set(obj, key, value).map_err(|_| EpoxyError::RawHeaderSetFailed)? { return undefined
Ok(())
} else {
Err(EpoxyError::RawHeaderSetFailed)
} }
};
export function object_set(obj, k, v) {
try { obj[k] = v } catch {}
};
export async function convert_body_inner(body) {
let req = new Request("", { method: "POST", duplex: "half", body });
let type = req.headers.get("content-type");
return [new Uint8Array(await req.arrayBuffer()), type];
} }
pub async fn convert_body(val: JsValue) -> Result<(Uint8Array, web_sys::Request), JsValue> { export function entries_of_object_inner(obj) {
let mut request_init = web_sys::RequestInit::new(); Object.entries(obj).map(x => x.map(String))
request_init.method("POST").body(Some(&val)); }
object_set(&request_init, &"duplex".into(), &"half".into())?;
let req = web_sys::Request::new_with_str_and_init("/", &request_init)?; export function define_property(obj, k, v) {
Ok(( Object.defineProperty(obj, k, { value: v, writable: false });
JsFuture::from(req.array_buffer()?) }
.await?
.dyn_into::<ArrayBuffer>() export function ws_key() {
.map(|x| Uint8Array::new(&x))?, let key = new Uint8Array(16);
req, crypto.getRandomValues(key);
)) return btoa(Array.from(key).map(String.fromCharCode).join(''));
}
"#)]
extern "C" {
pub fn object_get(obj: &Object, key: &str) -> JsValue;
pub fn object_set(obj: &Object, key: &str, val: JsValue);
#[wasm_bindgen(catch)]
async fn convert_body_inner(val: JsValue) -> Result<JsValue, JsValue>;
fn entries_of_object_inner(obj: &Object) -> Vec<Array>;
pub fn define_property(obj: &Object, key: &str, val: JsValue);
pub fn ws_key() -> String;
}
pub async fn convert_body(val: JsValue) -> Result<(Uint8Array, Option<String>), JsValue> {
let req: Array = convert_body_inner(val).await?.unchecked_into();
let str: Option<JsString> = object_truthy(req.at(1)).map(|x| x.unchecked_into());
Ok((req.at(0).unchecked_into(), str.map(Into::into)))
} }
pub fn entries_of_object(obj: &Object) -> Vec<Vec<String>> { pub fn entries_of_object(obj: &Object) -> Vec<Vec<String>> {
Object::entries(obj) entries_of_object_inner(obj)
.to_vec() .into_iter()
.iter() .map(|x| {
.filter_map(|val| { x.iter()
Array::from(val) .map(|x| x.unchecked_into::<JsString>().into())
.to_vec() .collect()
.iter()
.map(|val| val.as_string())
.collect::<Option<Vec<_>>>()
}) })
.collect::<Vec<Vec<_>>>() .collect()
}
pub fn define_property_obj(value: JsValue, writable: bool) -> Result<Object, JsValue> {
let entries: Array = [
Array::of2(&"value".into(), &value),
Array::of2(&"writable".into(), &writable.into()),
]
.iter()
.collect::<Array>();
Object::from_entries(&entries)
} }
pub fn asyncread_to_readablestream_stream<R: AsyncRead>( pub fn asyncread_to_readablestream_stream<R: AsyncRead>(
@ -234,3 +244,11 @@ pub fn asyncread_to_readablestream_stream<R: AsyncRead>(
.map_ok(|x| Uint8Array::from(x.as_ref()).into()) .map_ok(|x| Uint8Array::from(x.as_ref()).into())
.map_err(|x| EpoxyError::from(x).into()) .map_err(|x| EpoxyError::from(x).into())
} }
pub fn object_truthy(val: JsValue) -> Option<JsValue> {
if val.is_truthy() {
Some(val)
} else {
None
}
}

View file

@ -1,12 +1,10 @@
use std::{str::from_utf8, sync::Arc}; use std::{str::from_utf8, sync::Arc};
use base64::{prelude::BASE64_STANDARD, Engine};
use bytes::Bytes; use bytes::Bytes;
use fastwebsockets::{ use fastwebsockets::{
FragmentCollectorRead, Frame, OpCode, Payload, Role, WebSocket, WebSocketWrite, FragmentCollectorRead, Frame, OpCode, Payload, Role, WebSocket, WebSocketWrite,
}; };
use futures_util::lock::Mutex; use futures_util::lock::Mutex;
use getrandom::getrandom;
use http::{ use http::{
header::{ header::{
CONNECTION, HOST, SEC_WEBSOCKET_KEY, SEC_WEBSOCKET_PROTOCOL, SEC_WEBSOCKET_VERSION, CONNECTION, HOST, SEC_WEBSOCKET_KEY, SEC_WEBSOCKET_PROTOCOL, SEC_WEBSOCKET_VERSION,
@ -24,7 +22,9 @@ use wasm_bindgen::{prelude::*, JsError, JsValue};
use wasm_bindgen_futures::spawn_local; use wasm_bindgen_futures::spawn_local;
use crate::{ use crate::{
tokioio::TokioIo, utils::entries_of_object, EpoxyClient, EpoxyError, EpoxyHandlers, HttpBody, tokioio::TokioIo,
utils::{entries_of_object, ws_key},
EpoxyClient, EpoxyError, EpoxyHandlers, HttpBody,
}; };
#[wasm_bindgen] #[wasm_bindgen]
@ -54,17 +54,13 @@ impl EpoxyWebSocket {
let url: Uri = url.try_into()?; let url: Uri = url.try_into()?;
let host = url.host().ok_or(EpoxyError::NoUrlHost)?; let host = url.host().ok_or(EpoxyError::NoUrlHost)?;
let mut rand = [0u8; 16];
getrandom(&mut rand)?;
let key = BASE64_STANDARD.encode(rand);
let mut request = Request::builder() let mut request = Request::builder()
.method(Method::GET) .method(Method::GET)
.uri(url.clone()) .uri(url.clone())
.header(HOST, host) .header(HOST, host)
.header(CONNECTION, "upgrade") .header(CONNECTION, "upgrade")
.header(UPGRADE, "websocket") .header(UPGRADE, "websocket")
.header(SEC_WEBSOCKET_KEY, key) .header(SEC_WEBSOCKET_KEY, ws_key())
.header(SEC_WEBSOCKET_VERSION, "13") .header(SEC_WEBSOCKET_VERSION, "13")
.header(USER_AGENT, user_agent); .header(USER_AGENT, user_agent);