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

View file

@ -9,7 +9,6 @@ crate-type = ["cdylib"]
[dependencies]
async-compression = { version = "0.4.11", features = ["futures-io", "gzip", "brotli"], optional = true }
async-trait = "0.1.80"
base64 = { version = "0.22.1", optional = true }
bytes = "1.6.0"
cfg-if = "1.0.0"
event-listener = "5.3.1"
@ -17,7 +16,6 @@ fastwebsockets = { version = "0.8.0", features = ["unstable-split"], optional =
flume = "0.11.0"
futures-rustls = { version = "0.26.0", default-features = false, features = ["tls12", "ring"] }
futures-util = { version = "0.3.30", features = ["sink"] }
getrandom = { version = "0.2.15", features = ["js", "std"], optional = true }
http = "1.1.0"
http-body-util = "0.1.2"
hyper = "1.3.1"
@ -35,6 +33,10 @@ web-sys = { version = "0.3.69", features = ["BinaryType", "Headers", "MessageEve
webpki-roots = "0.26.3"
wisp-mux = { path = "../wisp", features = ["wasm"] }
[dependencies.getrandom]
version = "*"
features = ["js"]
[dependencies.ring]
version = "*"
features = ["wasm32_unknown_unknown_js"]
@ -49,5 +51,5 @@ features = ["nightly"]
[features]
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 "$@"
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"
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
echo "[epx] wasm-opt finished"
# === 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
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
@ -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}
# patch to set proper wasm path
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
echo "export default epoxy;" >> pkg/epoxy-module.js
# delete initSync export
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)
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
cp pkg/epoxy-bundled.js pkg/epoxy-module-bundled.js
echo "export default epoxy;" >> pkg/epoxy-module-bundled.js
# === types ===
AUTOGENERATED_TYPEDEFS=$(<"out/epoxy_client.d.ts")
AUTOGENERATED_TYPEDEFS=${AUTOGENERATED_TYPEDEFS%%export class IntoUnderlyingByteSource*}
echo "$AUTOGENERATED_TYPEDEFS" > pkg/epoxy-module.d.ts
echo -e "}\ndeclare type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module;" >> pkg/epoxy-module.d.ts
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 -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
echo -e "}\ndeclare function epoxy(maybe_memory?: WebAssembly.Memory): Promise<typeof wasm_bindgen>;" >> pkg/epoxy-bundled.d.ts
AUTOGENERATED_TYPES=$(<"out/epoxy_client.d.ts")
AUTOGENERATED_TYPES=${AUTOGENERATED_TYPES//$'\n'export interface InitOutput*InitOutput;$'\n'/}
AUTOGENERATED_TYPES=${AUTOGENERATED_TYPES//Promise<InitOutput>/Promise<void>}
echo "$AUTOGENERATED_TYPES" > pkg/epoxy.d.ts
# remove useless comment
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
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 () => {
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);
}
const plog = (str) => {
console.log(str);
}
const epoxy = await epoxyModule();
const EpoxyClientOptions = epoxy.EpoxyClientOptions;
const EpoxyClient = epoxy.EpoxyClient;
const EpoxyHandlers = epoxy.EpoxyHandlers;
await initEpoxy();
let epoxy_client_options = new EpoxyClientOptions();
epoxy_client_options.user_agent = navigator.userAgent;
@ -234,7 +226,7 @@ import epoxyModule from "./pkg/epoxy-module-bundled.js";
while (true) {
log("sending `data`");
await ws.send("data");
await (new Promise((res, _) => setTimeout(res, 10)));
await (new Promise((res, _) => setTimeout(res, 100)));
}
} else if (should_reconnect_test) {
while (true) {

View file

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

View file

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

View file

@ -7,10 +7,9 @@ use bytes::{buf::UninitSlice, BufMut, Bytes, BytesMut};
use futures_util::{ready, AsyncRead, Future, Stream, TryStreamExt};
use http::{HeaderValue, Uri};
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 wasm_bindgen::{prelude::*, JsCast, JsValue};
use wasm_bindgen_futures::JsFuture;
use crate::EpoxyError;
@ -177,54 +176,65 @@ pub fn is_null_body(code: u16) -> bool {
[101, 204, 205, 304].contains(&code)
}
pub fn object_get(obj: &Object, key: &str) -> Option<JsValue> {
Reflect::get(obj, &key.into()).ok()
}
pub fn object_set(obj: &Object, key: &JsValue, value: &JsValue) -> Result<(), EpoxyError> {
if Reflect::set(obj, key, value).map_err(|_| EpoxyError::RawHeaderSetFailed)? {
Ok(())
} else {
Err(EpoxyError::RawHeaderSetFailed)
#[wasm_bindgen(inline_js = r#"
export function object_get(obj, k) {
try {
return obj[k]
} catch {
return undefined
}
};
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> {
let mut request_init = web_sys::RequestInit::new();
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)?;
Ok((
JsFuture::from(req.array_buffer()?)
.await?
.dyn_into::<ArrayBuffer>()
.map(|x| Uint8Array::new(&x))?,
req,
))
export function entries_of_object_inner(obj) {
Object.entries(obj).map(x => x.map(String))
}
export function define_property(obj, k, v) {
Object.defineProperty(obj, k, { value: v, writable: false });
}
export function ws_key() {
let key = new Uint8Array(16);
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>> {
Object::entries(obj)
.to_vec()
.iter()
.filter_map(|val| {
Array::from(val)
.to_vec()
.iter()
.map(|val| val.as_string())
.collect::<Option<Vec<_>>>()
entries_of_object_inner(obj)
.into_iter()
.map(|x| {
x.iter()
.map(|x| x.unchecked_into::<JsString>().into())
.collect()
})
.collect::<Vec<Vec<_>>>()
}
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)
.collect()
}
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_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 base64::{prelude::BASE64_STANDARD, Engine};
use bytes::Bytes;
use fastwebsockets::{
FragmentCollectorRead, Frame, OpCode, Payload, Role, WebSocket, WebSocketWrite,
};
use futures_util::lock::Mutex;
use getrandom::getrandom;
use http::{
header::{
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 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]
@ -54,17 +54,13 @@ impl EpoxyWebSocket {
let url: Uri = url.try_into()?;
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()
.method(Method::GET)
.uri(url.clone())
.header(HOST, host)
.header(CONNECTION, "upgrade")
.header(UPGRADE, "websocket")
.header(SEC_WEBSOCKET_KEY, key)
.header(SEC_WEBSOCKET_KEY, ws_key())
.header(SEC_WEBSOCKET_VERSION, "13")
.header(USER_AGENT, user_agent);