~starkingdoms/starkingdoms

fdeaf0d35cd03dff45eb88ae961be0c38c0d5de3 — c0repwn3r 2 years ago 4365b12
exponential backoff to connections (wait at most 6m25s to connect to server)
5 files changed, 105 insertions(+), 8 deletions(-)

M Cargo.lock
M client/Cargo.toml
M client/src/lib.rs
M justfile
M web/play.html
M Cargo.lock => Cargo.lock +28 -0
@@ 3,6 3,17 @@
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"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 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",


@@ 422,6 435,15 @@ dependencies = [
]

[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 896,6 918,12 @@ 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"
source = "registry+https://github.com/rust-lang/crates.io-index"

M client/Cargo.toml => client/Cargo.toml +2 -0
@@ 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]

M client/src/lib.rs => client/src/lib.rs +72 -6
@@ 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<dyn Error>> {
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<dyn Error>> {
    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<dyn Error>> {

    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<dyn Error>> {

    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!("<b>{}</b>: {}", 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

M justfile => justfile +1 -1
@@ 1,4 1,4 @@
run_server: build_client_bundle
run_server:
    cargo run --bin server

run_http: build_client_bundle

M web/play.html => web/play.html +2 -1
@@ 15,12 15,13 @@
            </div>
            <input id="chat-value" type="text" placeholder="chat text goes here" />
            <button id="chat-submit">submit</button>
            <p id="status">Loading WASM module...</p>
        </div>

        <script type="module">
            // If you're getting build errors here | you need to run `just build_client_bundle` first, to compile client code
            //                                     v
            import init, { rust_init, send_chat, update_socket } from "./dist/client.js";
            import init, { rust_init, send_chat, update_socket, update_status } from "./dist/client.js";
            init().then(() => {
                const urlSearchParams = new URLSearchParams(window.location.search);