M Cargo.lock => Cargo.lock +45 -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"
@@ 102,6 113,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
+name = "client"
+version = "0.1.0"
+dependencies = [
+ "console_log",
+ "futures",
+ "js-sys",
+ "lazy_static",
+ "log",
+ "protocol",
+ "rmp-serde",
+ "serde",
+ "url",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "web-sys",
+ "ws_stream_wasm",
+]
+
+[[package]]
name = "colored"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ 435,6 465,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"
@@ 950,6 989,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 +14 -2
@@ 11,7 11,6 @@ crate-type = ["cdylib"]
[dependencies]
wasm-bindgen = "0.2"
js-sys = "0.3"
-web-sys = { version = "0.3", features = ["CanvasRenderingContext2d", "Document", "Element", "HtmlCanvasElement", "Window"]}
console_log = { version = "1", features = ["color"] }
log = "0.4"
futures = { version = "0.3", default-features = false }
@@ 21,4 20,17 @@ starkingdoms-protocol = { version = "0.1.0", path = "../protocol" }
rmp-serde = "1.1"
ws_stream_wasm = "0.7"
serde = { version = "1", features = ["derive"] }
-lazy_static = "1.4">
\ No newline at end of file
+lazy_static = "1.4"
+markdown = "1.0.0-alpha.7" # DO NOT DOWNGRADE
+async-recursion = "1"
+
+
+[dependencies.web-sys]
+version = "0.3.4"
+features = [
+ 'Document',
+ 'Element',
+ 'HtmlElement',
+ 'Node',
+ 'Window',
+]<
\ No newline at end of file
M client/src/lib.rs => client/src/lib.rs +85 -5
@@ 1,19 1,30 @@
use std::error::Error;
use futures::stream::{SplitSink, SplitStream};
use futures::StreamExt;
-use log::{debug, error, info, Level, trace, warn};
+use log::{error, info, Level, trace, warn};
use wasm_bindgen::prelude::*;
use ws_stream_wasm::{WsMessage, WsMeta, WsStream};
use starkingdoms_protocol::State;
use starkingdoms_protocol::PROTOCOL_VERSION;
use starkingdoms_protocol::MessageS2C;
use starkingdoms_protocol::MessageC2S;
+use ws_stream_wasm::{WsErr, WsMessage, WsMeta, WsStream};
+use protocol::State;
+use protocol::PROTOCOL_VERSION;
+use protocol::MessageS2C;
+use protocol::MessageC2S;
use futures::SinkExt;
use lazy_static::lazy_static;
use std::sync::Arc;
use std::sync::RwLock;
+
+
+use async_recursion::async_recursion;
use futures::FutureExt;
+use wasm_bindgen_futures::JsFuture;
+use web_sys::Window;
+
#[macro_use]
pub mod macros;
pub mod chat;
@@ 25,11 36,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);
@@ 58,11 71,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();
@@ 74,6 117,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,
@@ 104,6 149,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(())
}
@@ 133,7 180,18 @@ pub async fn update_socket() -> Result<(), JsError> {
}
MessageS2C::Chat { from, message } => {
info!("[CHAT] {}: {}", from, message);
- // TODO: Handle
+
+ let window: Window = web_sys::window().expect("no global `window` exists");
+ 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("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();
},
_ => {
warn!("server sent unexpected packet {:?}, ignoring", msg);
@@ 142,4 200,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 client/src/macros.rs => client/src/macros.rs +5 -5
@@ 1,8 1,8 @@
-use std::error::Error;
-use std::io;
-use futures::{AsyncRead, AsyncWrite, FutureExt, Stream, StreamExt};
-use futures::stream::SplitStream;
-use serde::{Deserialize, Serialize};
+
+
+use futures::{FutureExt, StreamExt};
+
+use serde::{Serialize};
use ws_stream_wasm::WsMessage;
#[macro_export]
M justfile => justfile +4 -1
@@ 1,6 1,9 @@
-run_server: build_client_bundle
+run_server:
cargo run --bin server
+run_http: build_client_bundle
+ cd web && python3 -m http.server
+
build_client_bundle:
rm -rf web/dist
RUST_BACKTRACE=1 wasm-pack build --target web client
M server/src/client_handler.rs => server/src/client_handler.rs +1 -1
@@ 109,7 109,7 @@ pub async fn handle_client(mgr: ClientManager, remote_addr: SocketAddr, mut rx:
MessageC2S::Chat { message } => {
info!("[{}] CHAT: [{}] {}", remote_addr, username, message);
- for (addr, client_thread) in mgr.handlers.read().await.iter() {
+ for (_addr, client_thread) in mgr.handlers.read().await.iter() {
match client_thread.tx.send(ClientHandlerMessage::ChatMessage { from: username.clone(), message: message.clone() }).await {
Ok(_) => (),
Err(e) => {
M server/src/handler.rs => server/src/handler.rs +2 -2
@@ 1,8 1,8 @@
use std::collections::HashMap;
-use std::error::Error;
+
use std::net::SocketAddr;
use std::sync::Arc;
-use serde::Serialize;
+
use tokio::sync::mpsc::Sender;
use tokio::sync::RwLock;
use tungstenite::Message;
M server/src/macros.rs => server/src/macros.rs +6 -6
@@ 1,9 1,9 @@
-use std::error::Error;
-use std::io;
-use futures::{AsyncRead, AsyncWrite, FutureExt, Stream, StreamExt};
-use futures::stream::SplitStream;
-use serde::{Deserialize, Serialize};
-use tokio_tungstenite::WebSocketStream;
+
+
+use futures::{FutureExt, StreamExt};
+
+use serde::{Serialize};
+
use tungstenite::Message;
#[macro_export]
M server/src/main.rs => server/src/main.rs +2 -2
@@ 4,7 4,7 @@ use std::sync::Arc;
use hyper::{Body, header, Request, Response, Server, server::conn::AddrStream, StatusCode, upgrade};
use hyper::service::{make_service_fn, service_fn};
use tokio_tungstenite::WebSocketStream;
-use tungstenite::{Error, handshake};
+use tungstenite::{handshake};
use futures::stream::StreamExt;
use lazy_static::lazy_static;
use log::{error, info, Level};
@@ 151,7 151,7 @@ async fn main() {
});
let mgr_timer = cmgr.clone();
- let timer_thread = tokio::spawn(async move {
+ let _timer_thread = tokio::spawn(async move {
timer_main(mgr_timer).await;
});
M server/src/timer.rs => server/src/timer.rs +4 -4
@@ 1,7 1,7 @@
-use std::error::Error;
+
use std::time::Duration;
-use log::{error, trace};
-use tokio::sync::mpsc::Receiver;
+use log::{error};
+
use tokio::time::sleep;
use crate::handler::{ClientHandlerMessage, ClientManager};
@@ 9,7 9,7 @@ pub async fn timer_main(mgr: ClientManager) {
loop {
sleep(Duration::from_millis(5)).await;
- for (addr, client_thread) in mgr.handlers.read().await.iter() {
+ for (_addr, client_thread) in mgr.handlers.read().await.iter() {
match client_thread.tx.send(ClientHandlerMessage::Tick).await {
Ok(_) => (),
Err(e) => {
M web/index.html => web/index.html +1 -1
@@ 6,7 6,7 @@
</head>
<body>
- <form target="/play.html" method="GET">
+ <form action="/play.html" method="GET">
<label for="server">Gateway server</label>
<input type="text" name="server" id="server" value="ws://localhost:3000/ws" required />
<br>
M web/play.html => web/play.html +3 -2
@@ 11,16 11,17 @@
<body>
<div class="chatbox">
<div id="chats">
- <p>hello: blsdkjf</p>
+
</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);