From 98526aa347f14eecc6885ff6249d9bc18f6bae42 Mon Sep 17 00:00:00 2001 From: Toshit Chawda Date: Sat, 27 Jul 2024 20:52:05 -0700 Subject: [PATCH] remove non esmodules builds, use a js snippet --- Cargo.lock | 13 ++--- client/Cargo.toml | 8 ++-- client/build.sh | 54 ++++++++++++++------- client/demo.js | 14 ++---- client/package.json | 28 +++++------ client/src/lib.rs | 81 +++++++++++-------------------- client/src/utils.rs | 104 +++++++++++++++++++++++----------------- client/src/websocket.rs | 12 ++--- 8 files changed, 154 insertions(+), 160 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 41c10ef..d14e72e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", diff --git a/client/Cargo.toml b/client/Cargo.toml index 4c14a45..678b3eb 100644 --- a/client/Cargo.toml +++ b/client/Cargo.toml @@ -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"] diff --git a/client/build.sh b/client/build.sh index 3351eea..6470be4 100755 --- a/client/build.sh +++ b/client/build.sh @@ -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, maybe_memory?: WebAssembly.Memory): Promise;" >> 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;" >> pkg/epoxy-module-bundled.d.ts -echo "$AUTOGENERATED_TYPEDEFS" > pkg/epoxy-bundled.d.ts -echo -e "}\ndeclare function epoxy(maybe_memory?: WebAssembly.Memory): Promise;" >> 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/Promise} + +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/ diff --git a/client/demo.js b/client/demo.js index 46e9779..783e90a 100644 --- a/client/demo.js +++ b/client/demo.js @@ -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) { diff --git a/client/package.json b/client/package.json index e146893..7861c44 100644 --- a/client/package.json +++ b/client/package.json @@ -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" } diff --git a/client/src/lib.rs b/client/src/lib.rs index 4c50829..92058af 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -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 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 { + pub fn new(wisp_url: String, options: EpoxyClientOptions) -> Result { 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 = 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) } diff --git a/client/src/utils.rs b/client/src/utils.rs index 90a4e61..259bb01 100644 --- a/client/src/utils.rs +++ b/client/src/utils.rs @@ -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 { - 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::() - .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; + + fn entries_of_object_inner(obj: &Object) -> Vec; + 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), JsValue> { + let req: Array = convert_body_inner(val).await?.unchecked_into(); + let str: Option = 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> { - Object::entries(obj) - .to_vec() - .iter() - .filter_map(|val| { - Array::from(val) - .to_vec() - .iter() - .map(|val| val.as_string()) - .collect::>>() + entries_of_object_inner(obj) + .into_iter() + .map(|x| { + x.iter() + .map(|x| x.unchecked_into::().into()) + .collect() }) - .collect::>>() -} - -pub fn define_property_obj(value: JsValue, writable: bool) -> Result { - let entries: Array = [ - Array::of2(&"value".into(), &value), - Array::of2(&"writable".into(), &writable.into()), - ] - .iter() - .collect::(); - Object::from_entries(&entries) + .collect() } pub fn asyncread_to_readablestream_stream( @@ -234,3 +244,11 @@ pub fn asyncread_to_readablestream_stream( .map_ok(|x| Uint8Array::from(x.as_ref()).into()) .map_err(|x| EpoxyError::from(x).into()) } + +pub fn object_truthy(val: JsValue) -> Option { + if val.is_truthy() { + Some(val) + } else { + None + } +} diff --git a/client/src/websocket.rs b/client/src/websocket.rs index 9f9bd0d..4c399e4 100644 --- a/client/src/websocket.rs +++ b/client/src/websocket.rs @@ -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);