reserved usernames

This commit is contained in:
Adam 2024-08-05 00:08:29 -04:00
parent 959d300455
commit d930583e12
6 changed files with 44 additions and 24 deletions

View file

@ -31,7 +31,7 @@ pub fn Auth() -> impl IntoView {
}) })
.unwrap(), .unwrap(),
); );
set_username.set(input.value()); set_username.set("".to_string());
input.set_value(""); input.set_value("");
} }
} }

View file

@ -4,12 +4,13 @@ A game master server for the popular game [Cards Against Humanity](https://www.c
This started as a problem trying to play games with friends who are all on different platforms. This shall be as cross-platform as it gets. I want it to work on anything that can establish a connection and allow anyone to write any front-end they can dream up whether it be a web page, chat bot, VR, etc. Any clients who share a master server can play together across any platform. This started as a problem trying to play games with friends who are all on different platforms. This shall be as cross-platform as it gets. I want it to work on anything that can establish a connection and allow anyone to write any front-end they can dream up whether it be a web page, chat bot, VR, etc. Any clients who share a master server can play together across any platform.
## Test/Dev: ## Test/Dev:
### Server ### Server
With auto-reload: With auto-reload:
* [Install cargo-watch](https://github.com/watchexec/cargo-watch?tab=readme-ov-file#install)
- [Install cargo-watch](https://github.com/watchexec/cargo-watch?tab=readme-ov-file#install)
Then: Then:
@ -19,14 +20,15 @@ watch -i client -cx run
or if you're lazy like me just run `./test` or if you're lazy like me just run `./test`
Without auto-reload: Without auto-reload:
```sh ```sh
cargo run cargo run
``` ```
### Client ### Client
* [Install Trunk](https://trunkrs.dev/#install)
- [Install Trunk](https://trunkrs.dev/#install)
Then: Then:
@ -37,31 +39,33 @@ trunk serve --open
## Build: ## Build:
### Client ### Client
```sh ```sh
trunk build --release trunk build --release
``` ```
### Server ### Server
```sh ```sh
cargo build cargo build
``` ```
---------- ---
* The server automatically serves the built client from `/dist` at `0.0.0.0:3030`
* Configure any custom clients to connect to `ws://0.0.0.0:3030/websocket` - The server automatically serves the built client from `/dist` at `0.0.0.0:3030`
- Configure any custom clients to connect to `ws://0.0.0.0:3030/websocket`
## TODO: ## TODO:
* finish game logic
* prevent multiple users claiming the same identity and then all losing it - may create a zombie - finish game logic
* prevent zombie users after browser crash - prevent zombie users after browser crash
* figure out proper auth - client's problem? - figure out proper auth - client's problem?
* use db - use db
* test bincode and probably move away from json - test bincode and probably move away from json
* make typescript sdk - make typescript sdk
* support card text editing - support card text editing
* prevent import of cards that have been seen already and edited - prevent import of cards that have been seen already and edited
* handle duplicates - handle duplicates
* efficiency - efficiency
* make demo clients for multiple platforms and screens - make demo clients for multiple platforms and screens

View file

@ -44,7 +44,7 @@ pub async fn on_websocket_connection(stream: WebSocket, state: Arc<AppState>, ad
// Subscribe to receive from global broadcast channel // Subscribe to receive from global broadcast channel
let mut rx = state.tx.subscribe(); let mut rx = state.tx.subscribe();
// Submit new messages from this client to broadcast // Send messages to this client
let mut send_task = tokio::spawn(async move { let mut send_task = tokio::spawn(async move {
while let Ok(msg) = rx.recv().await { while let Ok(msg) = rx.recv().await {
if sender.send(Message::Text(msg)).await.is_err() { if sender.send(Message::Text(msg)).await.is_err() {
@ -53,7 +53,7 @@ pub async fn on_websocket_connection(stream: WebSocket, state: Arc<AppState>, ad
} }
}); });
// Pass messages from broadcast down to this client // Receive messages from this client
let mut recv_task = tokio::spawn(async move { let mut recv_task = tokio::spawn(async move {
while let Some(Ok(message)) = receiver.next().await { while let Some(Ok(message)) = receiver.next().await {
message_handler(state.clone(), addr, message) message_handler(state.clone(), addr, message)

View file

@ -132,6 +132,8 @@ fn handle_user_log_in(
}; };
tracing::debug!("{msg}"); tracing::debug!("{msg}");
} else if state.reserved_names.read().unwrap().contains(&new_name) {
tracing::debug!("name is taken");
} else { } else {
state state
.online_users .online_users
@ -149,8 +151,16 @@ fn handle_user_log_in(
new_name new_name
}; };
// Reserve name
state
.reserved_names
.write()
.unwrap()
.insert(new_name.clone());
tracing::debug!("{msg}"); tracing::debug!("{msg}");
tx.send(to_string::<ChatMessage>(&ChatMessage { text: msg })?)?; tx.send(to_string::<ChatMessage>(&ChatMessage { text: msg })?)?;
tracing::debug!("Name {} reserved.", &new_name);
} }
tracing::debug!( tracing::debug!(
@ -159,6 +169,9 @@ fn handle_user_log_in(
state.offline_users.read().unwrap().len() state.offline_users.read().unwrap().len()
); );
tx.send(games_update(state))?; tx.send(games_update(state))?;
tx.send(chat_meta_update(state))?;
// send the user their new name
Ok(()) Ok(())
} }

View file

@ -4,7 +4,7 @@ use rand::prelude::IteratorRandom;
use rand::thread_rng; use rand::thread_rng;
use serde::Deserialize; use serde::Deserialize;
use std::{ use std::{
collections::HashMap, collections::{HashMap, HashSet},
fs::{read_to_string, File}, fs::{read_to_string, File},
io::{BufRead, BufReader}, io::{BufRead, BufReader},
net::SocketAddr, net::SocketAddr,
@ -339,6 +339,7 @@ pub fn load_names(path: &str) -> Result<Vec<String>> {
// Our shared state // Our shared state
pub struct AppState { pub struct AppState {
// We require unique usernames. This tracks which usernames have been taken. // We require unique usernames. This tracks which usernames have been taken.
pub reserved_names: RwLock<HashSet<String>>,
pub online_users: RwLock<HashMap<SocketAddr, Arc<RwLock<User>>>>, pub online_users: RwLock<HashMap<SocketAddr, Arc<RwLock<User>>>>,
pub offline_users: RwLock<HashMap<String, Arc<RwLock<User>>>>, pub offline_users: RwLock<HashMap<String, Arc<RwLock<User>>>>,
// Channel used to send messages to all connected clients. // Channel used to send messages to all connected clients.

View file

@ -3,7 +3,7 @@
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use axum::{routing::get, Router}; use axum::{routing::get, Router};
use std::{ use std::{
collections::HashMap, collections::{HashMap, HashSet},
net::SocketAddr, net::SocketAddr,
sync::{Arc, RwLock}, sync::{Arc, RwLock},
}; };
@ -28,6 +28,7 @@ async fn main() -> Result<()> {
// Set up application state for use with with_state(). // Set up application state for use with with_state().
// Main Broadcast Channel // Main Broadcast Channel
let (tx, _rx) = broadcast::channel(100); let (tx, _rx) = broadcast::channel(100);
let reserved_names = RwLock::new(HashSet::<String>::new());
let online_users = RwLock::new(HashMap::<SocketAddr, Arc<RwLock<User>>>::new()); let online_users = RwLock::new(HashMap::<SocketAddr, Arc<RwLock<User>>>::new());
let offline_users = RwLock::new(HashMap::<String, Arc<RwLock<User>>>::new()); let offline_users = RwLock::new(HashMap::<String, Arc<RwLock<User>>>::new());
let (packs, packs_meta) = load_cards_from_json("data/cah-cards-full.json")?; let (packs, packs_meta) = load_cards_from_json("data/cah-cards-full.json")?;
@ -36,6 +37,7 @@ async fn main() -> Result<()> {
let last_names = load_names("data/last.txt")?; let last_names = load_names("data/last.txt")?;
let app_state = Arc::new(AppState { let app_state = Arc::new(AppState {
reserved_names,
online_users, online_users,
offline_users, offline_users,
tx, tx,