Move the Websocket connect_to_game operation into the Client
All checks were successful
Monorepo build / build-flake (push) Successful in 3s

This commit is contained in:
Savanni D'Gerinel 2025-11-03 11:55:07 -05:00
parent 4ed246f062
commit b656b24d00
6 changed files with 97 additions and 64 deletions

1
Cargo.lock generated
View File

@ -3998,6 +3998,7 @@ dependencies = [
"serde-wasm-bindgen 0.6.5",
"serde_json",
"stylist",
"thiserror 2.0.12",
"visions-types",
"wasm-bindgen",
"wasm-bindgen-futures",

View File

@ -12,11 +12,12 @@ markdown = "1.0.0"
serde = { workspace = true }
serde-wasm-bindgen = { workspace = true }
serde_json = { workspace = true }
stylist = { version = "0.13.0", features = ["yew", "yew_integration", "yew_use_style"] }
stylist = { version = "0.13.0", features = ["yew", "yew_integration", "yew_use_style"] }
thiserror = { workspace = true }
visions-types = { path = "../types" }
wasm-bindgen = { workspace = true }
wasm-bindgen-futures = { workspace = true }
wasm-sockets = "1.0.0"
wasm-sockets = "1.0.0"
web-sys = { workspace = true, features = ["Window", "Location"] }
yew = { workspace = true }
yew-router = { workspace = true }

View File

@ -1,21 +1,32 @@
use std::future::Future;
use gloo_console::log;
use gloo_console::{error, log};
use gloo_net::{
http::{Request, Response},
Error,
};
use http::StatusCode;
use serde::de::DeserializeOwned;
use thiserror::Error;
use visions_types::*;
use wasm_sockets::{EventClient, Message};
use web_sys::CloseEvent;
use yew::Callback;
use crate::clone;
const HOST: &'static str = env!("VISIONS_HOST");
#[derive(Debug)]
#[derive(Debug, Error)]
pub enum ClientError {
#[allow(dead_code)]
#[error("Client is not authorized")]
Unauthorized,
#[error("Client failure: {0}")]
Err(u16),
#[error("Request builder failed: {0}")]
RequestError(Error),
}
@ -55,6 +66,13 @@ pub trait Client: Clone + PartialEq {
fn new_card(&self, card: Card) -> impl Future<Output = Result<CardId, ClientError>>;
fn update_card(&self, card: Card) -> impl Future<Output = Result<(), ClientError>>;
fn connect_to_game(
&self,
game: GameId,
on_message: Callback<GameMessage>,
on_socket_closed: Box<dyn Fn(CloseEvent)>,
) -> Result<EventClient, ClientError>;
}
#[derive(Clone, PartialEq)]
@ -260,4 +278,52 @@ impl Client for Connection {
handle_response(response).await
}
fn connect_to_game(
&self,
game_id: GameId,
on_message: Callback<GameMessage>,
on_socket_closed: Box<dyn Fn(CloseEvent)>,
) -> Result<EventClient, ClientError> {
let mut ws_client = EventClient::new("/api/ws").unwrap();
ws_client.set_on_error(Some(Box::new(|error| {
error!("{:#?}", error);
})));
ws_client.set_on_close(Some(on_socket_closed));
let session_id = self.session_id.clone();
clone!(
(session_id, game_id),
ws_client.set_on_connection(Some(Box::new({
move |ws_client: &wasm_sockets::EventClient| {
if let Err(e) = ws_client.send_string(
&serde_json::to_string(&GameRequest::JoinGame(
session_id.clone(),
game_id.clone(),
))
.unwrap(),
) {
log!("An error occurred while sending: {:#?}", e);
}
}
})))
);
ws_client.set_on_message(Some(Box::new({
let on_message = on_message.clone();
move |_client: &wasm_sockets::EventClient, message: wasm_sockets::Message| match message
{
Message::Text(text) => {
let message: GameMessage = serde_json::from_str(&text).unwrap();
on_message.emit(message);
}
Message::Binary(_) => unimplemented!(),
}
})));
Ok(ws_client)
}
}

View File

@ -1,8 +1,8 @@
use std::rc::Rc;
use gloo_console::{error, info, log};
use gloo_console::error;
use visions_types::*;
use wasm_sockets::{EventClient, Message};
use wasm_sockets::EventClient;
use yew::prelude::*;
use crate::{client::Client, clone};
@ -19,7 +19,7 @@ impl SocketState {
.send_string(&serde_json::to_string(&message).unwrap())
.unwrap(),
None => {
log!("no socket available");
error!("no socket available");
}
}
}
@ -60,57 +60,18 @@ pub fn WebsocketProvider<C: Client + Clone + 'static>(
on_message.clone(),
),
move |(socket_state, client, game_id, on_message)| {
let mut ws_client: wasm_sockets::EventClient =
wasm_sockets::EventClient::new("ws://localhost:8080/api/ws").unwrap();
ws_client.set_on_error(Some(Box::new(|error| {
error!("{:#?}", error);
})));
clone!(
socket_state,
ws_client.set_on_close({
Some(Box::new(move |_evt| {
info!("connection closed");
socket_state.set(SocketState { socket: None })
}))
})
let ws_client = client.connect_to_game(
game_id.clone(),
on_message.clone(),
Box::new(clone!(socket_state, move |_evt| socket_state
.set(SocketState { socket: None }))),
);
clone!(
(client, game_id),
ws_client.set_on_connection(Some(Box::new({
move |ws_client: &wasm_sockets::EventClient| {
if let Err(e) = ws_client.send_string(
&serde_json::to_string(&GameRequest::JoinGame(
client.session_id().clone(),
game_id.clone(),
))
.unwrap(),
) {
log!("An error occurred while sending: {:#?}", e);
}
}
})))
);
ws_client.set_on_message(Some(Box::new({
let on_message = on_message.clone();
move |_client: &wasm_sockets::EventClient, message: wasm_sockets::Message| {
match message {
Message::Text(text) => {
let message: GameMessage = serde_json::from_str(&text).unwrap();
on_message.emit(message);
}
Message::Binary(_) => unimplemented!(),
}
}
})));
socket_state.set(SocketState {
socket: Some(Rc::new(ws_client)),
});
match ws_client {
Ok(ws_client) => socket_state.set(SocketState {
socket: Some(Rc::new(ws_client)),
}),
Err(err) => error!("failed to connect to websocket: ", err.to_string()),
}
{
let socket_state = socket_state.clone();

View File

@ -4,7 +4,7 @@ use crate::{
client::{Client, ClientError, Connection},
clone,
};
use gloo_console::error;
use gloo_console::{error, log};
use visions_types::{SessionId, UserOverview};
use wasm_bindgen::JsValue;
use web_sys::{Storage, Window};
@ -139,10 +139,13 @@ pub struct StateProviderProps {
pub fn StateProvider(StateProviderProps { children }: &StateProviderProps) -> Html {
let app_state: UseReducerHandle<AppState> = use_reducer(init_state);
use_effect(clone!(app_state, || {
wasm_bindgen_futures::spawn_local(hydrate_client(app_state));
|| ()
}));
use_effect_with(
app_state.client().cloned(),
clone!(app_state, |_| {
wasm_bindgen_futures::spawn_local(hydrate_client(app_state));
|| ()
}),
);
html! {
<ContextProvider<UseReducerHandle<AppState>> context={app_state}>
@ -172,6 +175,7 @@ async fn hydrate_client_(
if let Ok(Some(session_id)) = storage.get_item("session_id") {
let session_id: SessionId = session_id.into();
let client = Connection::new(session_id.clone());
log!("hydrate_cilent_");
match client.get_self().await {
Ok(user) => set_session(&app_state, client, user),
Err(ClientError::Unauthorized) => {
@ -231,7 +235,6 @@ pub mod tests {
set_session_(
clone!(state, move |app_action| reduce_app_state(state, app_action)),
Some(&mut storage),
session_id.clone(),
overview.clone(),
);

View File

@ -1,4 +1,4 @@
use gloo_console::error;
use gloo_console::{error, log};
use visions_types::*;
use yew::prelude::*;
@ -118,6 +118,7 @@ fn GameList<C: Client + Clone + 'static>(GameListProps { client }: &GameListProp
clone!(
client,
wasm_bindgen_futures::spawn_local(async move {
log!("re-running game listing");
match client.list_games().await {
Ok(games) => game_list.set(games),
Err(ClientError::Unauthorized) => {