diff options
| author | Jomar Milan <jomarm@jomarm.com> | 2026-06-02 21:15:27 -0700 |
|---|---|---|
| committer | Jomar Milan <jomarm@jomarm.com> | 2026-06-02 21:15:27 -0700 |
| commit | ec2097b11f93ad968e171c0e961e0e06652ce44b (patch) | |
| tree | d404d2b7a021b57e19d2f668ae463ff311b4a988 | |
| parent | a2e55df0eeea9709175dd0a26c1e09bdaa60841a (diff) | |
Implement play websocket handshake
| -rw-r--r-- | Cargo.lock | 1 | ||||
| -rw-r--r-- | Cargo.toml | 1 | ||||
| -rw-r--r-- | assets/session.js | 27 | ||||
| -rw-r--r-- | src/main.rs | 10 | ||||
| -rw-r--r-- | src/play.rs | 67 | ||||
| -rw-r--r-- | templates/session.html | 3 |
6 files changed, 97 insertions, 12 deletions
@@ -576,6 +576,7 @@ dependencies = [ "axum", "rust-embed", "serde", + "serde_json", "tokio", ] @@ -8,4 +8,5 @@ askama = "0.16.0" axum = { version = "0.8.9", features = ["ws"] } rust-embed = "8.11.0" serde = { version = "1.0.228", features = ["derive"] } +serde_json = "1.0.150" tokio = { version = "1.52.3", features = ["full"] } diff --git a/assets/session.js b/assets/session.js index 7d00838..c607594 100644 --- a/assets/session.js +++ b/assets/session.js @@ -4,7 +4,32 @@ const id = document.getElementById('session-script').dataset.id; const websocket = new WebSocket(`/session/${id}/play`); websocket.addEventListener('open', () => { - colorSelect.disabled = false; + websocket.send(JSON.stringify({ + "Initialize": { id } + })); +}); + +websocket.addEventListener('message', (event) => { + const message = JSON.parse(event.data); + + if (Object.hasOwn(message, 'Initialize')) { + const payload = message.Initialize; + for (let element in Array.from(colorSelect.getElementsByClassName('color-select-colors'))) { + element.remove(); + } + payload.colors.forEach((color) => { + const selection = document.createElement('option'); + selection.value = color; + selection.innerText = color; + selection.classList.add('color-select-colors'); + colorSelect.appendChild(selection); + }); + colorSelect.disabled = false; + } +}); + +websocket.addEventListener('close', () => { + colorSelect.disabled = true; }); colorSelect.addEventListener('change', () => { diff --git a/src/main.rs b/src/main.rs index 32e5d58..b157b71 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,10 +1,11 @@ +mod play; mod session; mod template; +use crate::play::handle_play; use crate::session::{HandObject, Session}; use crate::template::{IndexTemplate, SessionTemplate}; use askama::Template; -use axum::extract::ws::WebSocket; use axum::extract::{Path, Query, State, WebSocketUpgrade}; use axum::http::{StatusCode, header}; use axum::response::{Html, IntoResponse, Response}; @@ -145,10 +146,3 @@ async fn update_hands( async fn upgrade_play(ws: WebSocketUpgrade, State(state): State<Arc<AppState>>) -> Response { ws.on_upgrade(|socket| handle_play(socket, state)) } - -#[allow(unused_variables)] -async fn handle_play(mut socket: WebSocket, state: Arc<AppState>) { - while let Some(msg) = socket.recv().await { - todo!() - } -} diff --git a/src/play.rs b/src/play.rs new file mode 100644 index 0000000..e88ae7e --- /dev/null +++ b/src/play.rs @@ -0,0 +1,67 @@ +use crate::AppState; +use axum::extract::ws::{Message, Utf8Bytes, WebSocket}; +use serde::{Deserialize, Serialize}; +use std::error::Error; +use std::sync::Arc; + +#[derive(Deserialize)] +enum IncomingPlayMessage { + Initialize { id: String }, +} + +#[derive(Serialize)] +enum OutgoingPlayMessage<'a> { + Initialize { colors: Vec<&'a String> }, +} + +struct PlayState { + id: Option<String>, +} + +pub async fn handle_play(mut socket: WebSocket, app_state: Arc<AppState>) { + let mut play_state = PlayState { id: None }; + + while let Some(msg) = socket.recv().await { + let mut process = async |msg: Result<Message, axum::Error>| -> Result<(), Box<dyn Error>> { + let msg: IncomingPlayMessage = serde_json::from_str(msg?.to_text()?)?; + match msg { + IncomingPlayMessage::Initialize { id } => { + // Blocked so that the mutex guard is dropped after cloning the color names, + // preventing a potential deadlock when using .await after sending something + // through the socket, which would be possible if using tokio::sync::Mutex. + // Of course, the sessions Mutex is std::sync::Mutex instead, which does not + // allow locking the mutex through .await anyway. + let colors: Vec<String> = { + let sessions = app_state.sessions.lock().unwrap(); + // TODO: Non-string Error might be useful + let session = sessions.get(&id).ok_or("Session did not exist")?; + + session.hands.keys().cloned().collect() + }; + + play_state.id = Some(id); + + let response = OutgoingPlayMessage::Initialize { + colors: colors.iter().collect(), + }; + socket + .send(Message::Text(Utf8Bytes::from(serde_json::to_string( + &response, + )?))) + .await?; + } + } + Ok(()) + }; + + if let Ok(Message::Text(_)) = msg + && let Err(err) = process(msg).await + { + eprintln!( + "Encountered an error while handling a message from a player: {}", + err + ); + break; + } + } +} diff --git a/templates/session.html b/templates/session.html index 9133df6..122c80b 100644 --- a/templates/session.html +++ b/templates/session.html @@ -13,9 +13,6 @@ <select id="color-select" disabled> <option value="">None selected</option> <hr> - {% for (color, _) in session.hands %} - <option value="{{color}}">{{color}}</option> - {% endfor %} </select> </form> </body> |
