From a2e55df0eeea9709175dd0a26c1e09bdaa60841a Mon Sep 17 00:00:00 2001 From: Jomar Milan Date: Sun, 31 May 2026 12:22:41 -0700 Subject: Add websocket route --- Cargo.lock | 172 +++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 2 +- assets/session.js | 7 ++ src/main.rs | 44 +++++++++---- src/session.rs | 6 ++ src/template.rs | 1 + templates/session.html | 4 +- 7 files changed, 220 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2da6bb1..6c7cba4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -68,6 +68,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "31b698c5f9a010f6573133b09e0de5408834d0c82f8d7475a89fc1867a71cd90" dependencies = [ "axum-core", + "base64", "bytes", "form_urlencoded", "futures-util", @@ -86,8 +87,10 @@ dependencies = [ "serde_json", "serde_path_to_error", "serde_urlencoded", + "sha1", "sync_wrapper", "tokio", + "tokio-tungstenite", "tower", "tower-layer", "tower-service", @@ -113,6 +116,12 @@ dependencies = [ "tracing", ] +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + [[package]] name = "basic-toml" version = "0.1.10" @@ -168,6 +177,12 @@ dependencies = [ "typenum", ] +[[package]] +name = "data-encoding" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8" + [[package]] name = "digest" version = "0.10.7" @@ -212,6 +227,12 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" +[[package]] +name = "futures-sink" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" + [[package]] name = "futures-task" version = "0.3.32" @@ -225,6 +246,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" dependencies = [ "futures-core", + "futures-sink", "futures-task", "pin-project-lite", "slab", @@ -240,6 +262,18 @@ dependencies = [ "version_check", ] +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", +] + [[package]] name = "glob" version = "0.3.3" @@ -423,6 +457,15 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + [[package]] name = "proc-macro2" version = "1.0.106" @@ -441,6 +484,41 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "rand" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" +dependencies = [ + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom", +] + [[package]] name = "redox_syscall" version = "0.5.18" @@ -588,6 +666,17 @@ dependencies = [ "serde", ] +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "sha2" version = "0.10.9" @@ -648,6 +737,26 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "tokio" version = "1.52.3" @@ -676,6 +785,18 @@ dependencies = [ "syn", ] +[[package]] +name = "tokio-tungstenite" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f72a05e828585856dacd553fba484c242c46e391fb0e58917c942ee9202915c" +dependencies = [ + "futures-util", + "log", + "tokio", + "tungstenite", +] + [[package]] name = "tower" version = "0.5.3" @@ -724,6 +845,22 @@ dependencies = [ "once_cell", ] +[[package]] +name = "tungstenite" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c01152af293afb9c7c2a57e4b559c5620b421f6d133261c60dd2d0cdb38e6b8" +dependencies = [ + "bytes", + "data-encoding", + "http", + "httparse", + "log", + "rand", + "sha1", + "thiserror", +] + [[package]] name = "typenum" version = "1.20.0" @@ -758,6 +895,15 @@ version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" +[[package]] +name = "wasip2" +version = "1.0.3+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" +dependencies = [ + "wit-bindgen", +] + [[package]] name = "winapi-util" version = "0.1.11" @@ -791,6 +937,32 @@ dependencies = [ "memchr", ] +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + +[[package]] +name = "zerocopy" +version = "0.8.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b065d4f0e55f82fae73202e189638116a87c55ab6b8e6c2721e13dd9d854ad1" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b631b19d36a892ab55420c92dbc83ccd79274f25be714855d3074aa71cab639" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "zmij" version = "1.0.21" diff --git a/Cargo.toml b/Cargo.toml index 593b989..03a3e0d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ edition = "2024" [dependencies] askama = "0.16.0" -axum = "0.8.9" +axum = { version = "0.8.9", features = ["ws"] } rust-embed = "8.11.0" serde = { version = "1.0.228", features = ["derive"] } tokio = { version = "1.52.3", features = ["full"] } diff --git a/assets/session.js b/assets/session.js index b4f95dd..7d00838 100644 --- a/assets/session.js +++ b/assets/session.js @@ -1,5 +1,12 @@ const colorSelect = document.getElementById('color-select'); +const id = document.getElementById('session-script').dataset.id; +const websocket = new WebSocket(`/session/${id}/play`); + +websocket.addEventListener('open', () => { + colorSelect.disabled = false; +}); + colorSelect.addEventListener('change', () => { colorSelect.disabled = true; }); \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index ec09da2..32e5d58 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,13 +4,13 @@ mod template; use crate::session::{HandObject, Session}; use crate::template::{IndexTemplate, SessionTemplate}; use askama::Template; -use axum::extract::{Path, Query, State}; -use axum::http::{header, StatusCode}; +use axum::extract::ws::WebSocket; +use axum::extract::{Path, Query, State, WebSocketUpgrade}; +use axum::http::{StatusCode, header}; use axum::response::{Html, IntoResponse, Response}; -use axum::routing::{get, put}; +use axum::routing::{any, get}; use axum::{Json, Router}; use rust_embed::Embed; -use serde::Deserialize; use std::collections::HashMap; use std::net::SocketAddr; use std::sync::{Arc, Mutex}; @@ -18,7 +18,7 @@ use std::time::{SystemTime, UNIX_EPOCH}; #[derive(Embed)] #[folder = "assets/"] -struct Asset; +struct EmbedAsset; struct AppState { sessions: Mutex>, @@ -38,7 +38,8 @@ async fn main() { .route("/", get(serve_index)) .route("/dist/{*path}", get(serve_static)) .route("/session/{id}", get(visit_session).put(create_session)) - .route("/session/{id}/hands", put(update_hands)) + .route("/session/{id}/hands", get(serve_hands).put(update_hands)) + .route("/session/{id}/play", any(upgrade_play)) .with_state(Arc::new(AppState::new())); let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); @@ -64,9 +65,9 @@ async fn serve_index(State(state): State>) -> Response { } async fn serve_static(Path(path): Path) -> Response { - match Asset::get(path.as_str()) { + match EmbedAsset::get(path.as_str()) { Some(content) => { - let mime = match path.split('.').last() { + let mime = match path.split('.').next_back() { Some("js") => "application/javascript", _ => "application/octet-stream", }; @@ -88,7 +89,7 @@ async fn visit_session( match sessions.get(&id) { Some(session) => match passcode { Some(passcode) if passcode.as_str() == session.passcode => { - serve_template(SessionTemplate { session: &session }) + serve_template(SessionTemplate { id: &id, session }) } _ => (StatusCode::FORBIDDEN, "Incorrect session passcode").into_response(), }, @@ -101,10 +102,7 @@ async fn create_session( Query(query): Query>, State(state): State>, ) -> Response { - let name = query - .get("name") - .map(|name| name.clone()) - .unwrap_or("Unknown".to_string()); + let name = query.get("name").cloned().unwrap_or("Unknown".to_string()); let passcode = SystemTime::now() .duration_since(UNIX_EPOCH) .map(|duration| duration.subsec_nanos()) @@ -119,6 +117,15 @@ async fn create_session( (StatusCode::CREATED, passcode).into_response() } +async fn serve_hands(Path(id): Path, State(state): State>) -> Response { + let sessions = state.sessions.lock().unwrap(); + + match sessions.get(&id) { + Some(session) => Json(session.hands.keys().collect::>()).into_response(), + None => (StatusCode::NOT_FOUND, "Session does not exist").into_response(), + } +} + async fn update_hands( Path(id): Path, State(state): State>, @@ -134,3 +141,14 @@ async fn update_hands( None => (StatusCode::NOT_FOUND, "Session does not exist").into_response(), } } + +async fn upgrade_play(ws: WebSocketUpgrade, State(state): State>) -> Response { + ws.on_upgrade(|socket| handle_play(socket, state)) +} + +#[allow(unused_variables)] +async fn handle_play(mut socket: WebSocket, state: Arc) { + while let Some(msg) = socket.recv().await { + todo!() + } +} diff --git a/src/session.rs b/src/session.rs index 11dd5b8..c105af3 100644 --- a/src/session.rs +++ b/src/session.rs @@ -7,12 +7,18 @@ pub struct Session { pub hands: HashMap>, } +// TODO: The values on these variants will be used in the future and there will be more variants. +// Once this happens, the dead_code lint should no longer be suppressed. #[derive(Deserialize)] +#[allow(dead_code)] pub enum HandObject { CustomDeck(CustomDeck), } +// TODO: These fields will be used in the future. When they are, the dead_code lint should no longer +// be suppressed. #[derive(Deserialize)] +#[allow(dead_code)] pub struct CustomDeck { /// The path/URL of the face cardsheet. face: String, diff --git a/src/template.rs b/src/template.rs index 5424883..8a36a09 100644 --- a/src/template.rs +++ b/src/template.rs @@ -11,5 +11,6 @@ pub struct IndexTemplate<'a> { #[derive(Template)] #[template(path = "session.html")] pub struct SessionTemplate<'a> { + pub id: &'a String, pub session: &'a Session, } diff --git a/templates/session.html b/templates/session.html index 6319d05..9133df6 100644 --- a/templates/session.html +++ b/templates/session.html @@ -3,14 +3,14 @@ {{session.steam_name}}'s game - +

{{session.steam_name}}'s game

Player Selection

-
{% for (color, _) in session.hands %} -- cgit v1.2.3