From fdeaf0d35cd03dff45eb88ae961be0c38c0d5de3 Mon Sep 17 00:00:00 2001 From: c0repwn3r Date: Mon, 10 Apr 2023 13:39:01 -0400 Subject: [PATCH] exponential backoff to connections (wait at most 6m25s to connect to server) --- Cargo.lock | 28 +++++++++++++++++ client/Cargo.toml | 2 ++ client/src/lib.rs | 78 +++++++++++++++++++++++++++++++++++++++++++---- justfile | 2 +- web/play.html | 3 +- 5 files changed, 105 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7a0b9abda52771e89265835bd21f7b10df97c874..f5a32c6de3ab6704c2e599d822b8719e23eb7ee2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,17 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "async-recursion" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e97ce7de6cf12de5d7226c73f5ba9811622f4db3a5b91b55c53e987e5f91cba" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.13", +] + [[package]] name = "async_io_stream" version = "0.3.3" @@ -73,11 +84,13 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" name = "client" version = "0.1.0" dependencies = [ + "async-recursion", "console_log", "futures", "js-sys", "lazy_static", "log", + "markdown", "protocol", "rmp-serde", "serde", @@ -421,6 +434,15 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "markdown" +version = "1.0.0-alpha.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98de49c677e95e00eaa74c42a0b07ea55e1e0b1ebca5b2cbc7657f288cd714eb" +dependencies = [ + "unicode-id", +] + [[package]] name = "memchr" version = "2.5.0" @@ -895,6 +917,12 @@ version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" +[[package]] +name = "unicode-id" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d70b6494226b36008c8366c288d77190b3fad2eb4c10533139c1c1f461127f1a" + [[package]] name = "unicode-ident" version = "1.0.8" diff --git a/client/Cargo.toml b/client/Cargo.toml index f1c8dd4f20250492bb87cdeff0d569c4a8ccf884..da6516ba9bd7173a4cc204192c70a36b9f0f46f5 100644 --- a/client/Cargo.toml +++ b/client/Cargo.toml @@ -21,6 +21,8 @@ rmp-serde = "1.1" ws_stream_wasm = "0.7" serde = { version = "1", features = ["derive"] } lazy_static = "1.4" +markdown = "1.0.0-alpha.7" # DO NOT DOWNGRADE +async-recursion = "1" [dependencies.web-sys] diff --git a/client/src/lib.rs b/client/src/lib.rs index 48427e66cb0c6f63ecf55a8bf00dcd8a1154fd56..97e5035c179cf7696aac325d1acba51e80ac99dd 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -3,7 +3,7 @@ use futures::stream::{SplitSink, SplitStream}; use futures::StreamExt; use log::{debug, error, info, Level, trace, warn}; use wasm_bindgen::prelude::*; -use ws_stream_wasm::{WsMessage, WsMeta, WsStream}; +use ws_stream_wasm::{WsErr, WsMessage, WsMeta, WsStream}; use protocol::State; use protocol::PROTOCOL_VERSION; use protocol::MessageS2C; @@ -12,7 +12,12 @@ use futures::SinkExt; use lazy_static::lazy_static; use std::sync::Arc; use std::sync::RwLock; +use std::thread; +use std::time::Duration; +use async_recursion::async_recursion; use futures::FutureExt; +use js_sys::Math::sqrt; +use wasm_bindgen_futures::JsFuture; use web_sys::Window; #[macro_use] @@ -26,11 +31,13 @@ extern { #[wasm_bindgen] pub async fn rust_init(gateway: &str, username: &str) -> Result<(), JsError> { + update_status("Starting logger..."); + console_log::init_with_level(Level::Debug).unwrap(); info!("Logger setup successfully"); - match main(gateway, username).await { + match main(gateway, username, 1).await { Ok(c) => c, Err(e) => { error!("Error initializing gateway client: {}", e); @@ -59,11 +66,41 @@ lazy_static! { })); } -pub async fn main(gateway: &str, username: &str) -> Result<(), Box> { +pub const MAX_CONNECTION_TRIES: i32 = 10; + +#[async_recursion(?Send)] +#[allow(clippy::only_used_in_recursion)] +pub async fn main(gateway: &str, username: &str, backoff: i32) -> Result<(), Box> { + info!("Backing off connection: waiting {} seconds", backoff * backoff); + + wait_for(sleep(backoff * backoff * 1000)).await; + + if backoff > MAX_CONNECTION_TRIES { + update_status("Connection to server failed"); + return Err("Hit backoff limit during reconnection attempt".into()); + } + if backoff == 1 { + update_status("Connecting to server..."); + } else { + update_status(&format!("Connection failed, retrying... (try {}/10)", backoff)); + } + info!("FAST CONNECT: {}", gateway); let gateway_url = url::Url::parse(gateway)?; trace!("Gateway URL parsed"); - let (_ws, ws_stream) = WsMeta::connect(gateway_url, None).await?; + let (_ws, ws_stream) = match WsMeta::connect(gateway_url, None).await { + Ok(r) => r, + Err(e) => { + return match e { + WsErr::ConnectionFailed { .. } => { + main(gateway, username, backoff + 1).await + }, + _ => { + Err(e.into()) + } + } + } + }; trace!("Connected to gateway socket"); let (tx, rx) = ws_stream.split(); @@ -75,6 +112,8 @@ pub async fn main(gateway: &str, username: &str) -> Result<(), Box> { trace!("Split stream, handshaking with server"); + update_status("Handshaking with server..."); + send!(client_data.tx, &MessageC2S::Hello { next_state: State::Play, version: PROTOCOL_VERSION, @@ -105,6 +144,8 @@ pub async fn main(gateway: &str, username: &str) -> Result<(), Box> { CLIENT.write()?.client_data = Some(client_data); + update_status(&format!("Connected! Username: {}", username)); + Ok(()) } @@ -139,8 +180,11 @@ pub async fn update_socket() -> Result<(), JsError> { let document = window.document().expect("should have a document on window"); let chatbox = document.get_element_by_id("chats").expect("chatbox does not exist"); - let new_elem = document.create_element("p").expect("could not create element"); - new_elem.set_inner_html(&format!("{}: {}", from, message)); + let new_elem = document.create_element("div").expect("could not create element"); + + let msg_formatted = markdown::to_html(&format!("**[{}]** {}", from, message)); + + new_elem.set_inner_html(&msg_formatted); chatbox.append_child(&new_elem).unwrap(); }, @@ -151,4 +195,26 @@ pub async fn update_socket() -> Result<(), JsError> { } Ok(()) +} + +#[wasm_bindgen] +pub fn update_status(new_status: &str) { + let window: Window = web_sys::window().expect("no global `window` exists"); + let document = window.document().expect("should have a document on window"); + let status = document.get_element_by_id("status").expect("statusbox does not exist"); + status.set_inner_html(new_status); +} + +#[wasm_bindgen] +pub fn sleep(ms: i32) -> js_sys::Promise { + js_sys::Promise::new(&mut |resolve, _| { + web_sys::window() + .unwrap() + .set_timeout_with_callback_and_timeout_and_arguments_0(&resolve, ms) + .unwrap(); + }) +} + +pub async fn wait_for(promise: js_sys::Promise) -> JsFuture { + wasm_bindgen_futures::JsFuture::from(promise) } \ No newline at end of file diff --git a/justfile b/justfile index 4f49d454f7e088d50a197a8a4cefd327c86d0ac2..101b8e0974e8fbc2c5c3f89044e6ae4fed672f7e 100644 --- a/justfile +++ b/justfile @@ -1,4 +1,4 @@ -run_server: build_client_bundle +run_server: cargo run --bin server run_http: build_client_bundle diff --git a/web/play.html b/web/play.html index 52c49aecbb1444d0193938d796272fb642c9c04b..a731f188b2f1a9e8d77a240dcba9dba99e5c5bf6 100644 --- a/web/play.html +++ b/web/play.html @@ -15,12 +15,13 @@ +

Loading WASM module...