From a7684bea169a3b3271c4084269335410951609da Mon Sep 17 00:00:00 2001 From: yukirij Date: Wed, 4 Sep 2024 15:34:54 -0700 Subject: [PATCH] Add user status messages. --- server/src/app/connection.rs | 4 + server/src/app/mod.rs | 71 ++++++++-- server/src/app/session.rs | 2 +- server/src/app/user.rs | 4 + server/src/manager/data.rs | 203 +++++++++++++++++++-------- server/src/protocol/code.rs | 2 + server/src/protocol/packet/mod.rs | 2 + server/src/protocol/packet/status.rs | 31 ++++ server/src/system/filesystem/mod.rs | 18 ++- www/css/main.css | 2 +- www/js/const.js | 7 + www/js/scene.js | 12 +- www/js/system.js | 24 ++++ www/js/ui.js | 64 ++++++++- 14 files changed, 357 insertions(+), 89 deletions(-) create mode 100644 server/src/protocol/packet/status.rs diff --git a/server/src/app/connection.rs b/server/src/app/connection.rs index 180287c..6ce9840 100644 --- a/server/src/app/connection.rs +++ b/server/src/app/connection.rs @@ -12,9 +12,13 @@ use crate::app::{ type StreamType = Arc>, Message>>>; +#[derive(Clone)] pub struct Connection { pub bus:u32, pub stream:StreamType, pub auth:Option, pub session:Option, + + pub prev:u32, + pub next:u32, } diff --git a/server/src/app/mod.rs b/server/src/app/mod.rs index 57b8d6a..cdf3243 100644 --- a/server/src/app/mod.rs +++ b/server/src/app/mod.rs @@ -4,9 +4,7 @@ use sparse::Sparse; use pool::Pool; use trie::Trie; use crate::{ - system::filesystem::FileSystem, - util::Chain, - protocol::QRPacket, + protocol::QRPacket, system::filesystem::FileSystem, util::Chain }; pub mod connection; use connection::Connection; @@ -148,16 +146,8 @@ impl App { use tokio_tungstenite::tungstenite::Message; use futures::SinkExt; - fn encode_response(code:u16, data:Vec) -> Vec - { - [ - crate::util::pack::pack_u16(code), - data, - ].concat() - } - if match response.data { QRPacketData::None => false, _ => true } { - if let Some(conn) = self.connections.get_mut(response.id as usize) { + if let Some(conn) = self.connections.get(response.id as usize) { let mut socket = conn.stream.write().await; match response.data { @@ -226,4 +216,61 @@ impl App { } } } + + pub async fn send_status(&mut self, user_id:u32) + { + use crate::protocol::*; + use tokio_tungstenite::tungstenite::Message; + use futures::SinkExt; + + let mut response = PacketStatusResponse::new(); + + if let Some(user) = self.get_user_by_id(user_id).cloned() { + + // Get challenges + response.challenge = if user.challenges.len() < 10 { user.challenges.len() as u16 } else { 10 }; + + // Get awaiting sessions + let mut next_id = self.session_time.begin(); + while let Some(id) = next_id { + let token = self.session_time.get(id).unwrap(); + if let Some(session) = self.sessions.get(token) { + + // Add to resume if session has user and is user turn. + if (session.p_dawn.user == user_id && (session.game.turn & 1) == 0) || (session.p_dusk.user == user_id && (session.game.turn & 1) == 1) { + response.resume += 1; + } + + if response.resume >= 10 { break; } + next_id = self.session_time.next(id); + } + } + + // Send packet to connections + if let Some(end_conn_id) = user.connection { + let mut conn_id = end_conn_id; + + loop { + if let Some(conn) = self.connections.get_mut(conn_id as usize) { + let mut socket = conn.stream.write().await; + + socket.send(Message::Binary( + encode_response(CODE_STATUS, response.encode()) + )).await.ok(); + + conn_id = conn.next; + if conn_id == end_conn_id { break; } + } else { break; } + } + } + } + } +} + +fn encode_response(code:u16, data:Vec) -> Vec +{ + [ + crate::util::pack::pack_u16(code), + data, + ].concat() } diff --git a/server/src/app/session.rs b/server/src/app/session.rs index 6a0c58a..9db6f30 100644 --- a/server/src/app/session.rs +++ b/server/src/app/session.rs @@ -5,7 +5,7 @@ pub type SessionSecret = [u8; 8]; #[derive(Clone)] pub struct Player { - pub user:Option, + pub user:u32, pub connections:Vec, } diff --git a/server/src/app/user.rs b/server/src/app/user.rs index 9a9bf8d..95a8f55 100644 --- a/server/src/app/user.rs +++ b/server/src/app/user.rs @@ -1,5 +1,6 @@ pub const FC_ENABLE :u32 = 0x0000_0001; +#[derive(Clone, Copy)] pub struct UserStatus { pub await_flags:u32, } @@ -12,12 +13,15 @@ impl UserStatus { } } +#[derive(Clone)] pub struct User { pub id:u32, pub flags:u32, pub handle:String, pub secret:Vec, pub na_key:u32, + + pub connection:Option, pub status:UserStatus, pub challenges:Vec, diff --git a/server/src/manager/data.rs b/server/src/manager/data.rs index 714db36..0e89599 100644 --- a/server/src/manager/data.rs +++ b/server/src/manager/data.rs @@ -19,6 +19,8 @@ pub async fn thread_system(mut app:App, bus:Bus) let rng = SystemRandom::new(); let argon_config = argon2::Config::default(); + let mut send_user_status = Vec::::new(); + while let Some(packet) = bus.receive_wait() { let qr = packet.data; @@ -41,7 +43,14 @@ pub async fn thread_system(mut app:App, bus:Bus) stream: request.stream, auth: None, session: None, + + prev:0, + next:0, }); + if let Some(conn) = app.connections.get_mut(id) { + conn.prev = id as u32; + conn.next = id as u32; + } println!("Connect: {}", id); @@ -54,16 +63,46 @@ pub async fn thread_system(mut app:App, bus:Bus) QRPacketData::QDisconn => { // Uninitialize connection - if if let Some(conn) = app.connections.get_mut(qr.id as usize) { + if if let Some(conn) = app.connections.get(qr.id as usize).cloned() { // Disassociate session if present if let Some(session_token) = conn.session { if let Some(session) = app.sessions.get_mut(&session_token) { - if user_id == session.p_dawn.user { session.remove_connection(0, qr.id); } - else if user_id == session.p_dusk.user { session.remove_connection(1, qr.id); } + if user_id == Some(session.p_dawn.user) { session.remove_connection(0, qr.id); } + else if user_id == Some(session.p_dusk.user) { session.remove_connection(1, qr.id); } else { session.remove_connection(2, qr.id); } } } + + // Remove connection from chain. + if let Some(auth_id) = conn.auth { + if let Some(auth) = app.auths.get(&auth_id).cloned() { + + // Update user connection reference. + if let Some(user) = app.get_user_by_id_mut(auth.user) { + if Some(qr.id) == user.connection { + + // Set connection to next if exists. + if conn.next != qr.id { + user.connection = Some(conn.next); + } else { + user.connection = None; + } + } + } + + // Link prev and next connections. + if conn.next != qr.id { + if let Some(prev_conn) = app.connections.get_mut(conn.prev as usize) { + prev_conn.next = conn.next; + } + + if let Some(next_conn) = app.connections.get_mut(conn.next as usize) { + next_conn.prev = conn.prev; + } + } + } + } // Close socket let mut socket = conn.stream.write().await; @@ -113,6 +152,8 @@ pub async fn thread_system(mut app:App, bus:Bus) handle:display_name, secret, na_key:salt_id, + + connection:Some(qr.id), status:UserStatus::new(), challenges:Vec::new(), @@ -175,16 +216,16 @@ pub async fn thread_system(mut app:App, bus:Bus) if is_valid { // Get user data from handle - match app.user_handle.get(request.handle.to_lowercase().as_bytes()) { + match app.user_handle.get(request.handle.to_lowercase().as_bytes()).cloned() { Some(uid) => { - if let Some(tuid) = app.user_id.get(*uid as isize) { - if let Some(user) = app.users.get(*tuid) { + if let Some(tuid) = app.user_id.get(uid as isize).cloned() { + if let Some(user) = app.users.get(tuid).cloned() { // Get user salt - if let Some(salt) = app.salts.get(user.na_key as isize) { + if let Some(salt) = app.salts.get(user.na_key as isize).cloned() { // Verify salted secret against user data - if argon2::verify_raw(&request.secret.as_bytes(), salt, &user.secret, &argon_config).unwrap_or(false) { - println!("Authenticated user '{}' id {}", user.handle, *uid); + if argon2::verify_raw(&request.secret.as_bytes(), &salt, &user.secret, &argon_config).unwrap_or(false) { + println!("Authenticated user '{}' id {}", user.handle, uid); // Generate authentication token and secret response.status = STATUS_OK; @@ -196,15 +237,34 @@ pub async fn thread_system(mut app:App, bus:Bus) app.auths.set(&response.token, Authentication { key:response.token, secret:response.secret, - user:*uid, + user:uid, }); break; } } + + // Mark send status. + send_user_status.push(uid); - // Attach authentication to connection + // Attach authentication to connection. if let Some(conn) = app.connections.get_mut(qr.id as usize) { conn.auth = Some(response.token); + if let Some(cid) = user.connection { + conn.prev = cid; + } + } + + // Add connection to chain. + if let Some(user_cid) = user.connection { + if let Some(existing) = app.connections.get(user_cid as usize).cloned() { + if let Some(conn) = app.connections.get_mut(qr.id as usize) { + conn.next = existing.next; + } + } + } else { + if let Some(user) = app.users.get_mut(tuid) { + user.connection = Some(qr.id); + } } } else { println!("notice: password verification failed."); @@ -232,7 +292,7 @@ pub async fn thread_system(mut app:App, bus:Bus) let mut response = PacketAuthResumeResponse::new(); response.status = STATUS_ERROR; - if let Some(auth) = app.auths.get(&request.token) { + if let Some(auth) = app.auths.get(&request.token).cloned() { // Compare full secret length to reduce time-based attacks. let mut valid = true; @@ -241,11 +301,31 @@ pub async fn thread_system(mut app:App, bus:Bus) } if valid { - if let Some(conn) = app.connections.get_mut(qr.id as usize) { + if if let Some(conn) = app.connections.get_mut(qr.id as usize) { conn.auth = Some(request.token); response.status = STATUS_OK; + true } else { response.status = STATUS_SERVER_ERROR; + false + } { + // Add connection to chain. + if let Some(user) = app.get_user_by_id(auth.user).cloned() { + if let Some(user_cid) = user.connection { + if let Some(existing) = app.connections.get(user_cid as usize).cloned() { + if let Some(conn) = app.connections.get_mut(qr.id as usize) { + conn.next = existing.next; + } + } + } else { + if let Some(user) = app.get_user_by_id_mut(auth.user) { + user.connection = Some(qr.id); + } + } + + // Mark send user status. + send_user_status.push(auth.user); + } } } } @@ -281,39 +361,29 @@ pub async fn thread_system(mut app:App, bus:Bus) let token = app.session_time.get(id).unwrap(); if let Some(session) = app.sessions.get(token) { - // Requirements: - // - GameState must be None or match state of session. - // - Joinable must have either player slot empty. - // - IsPlayer must have the current user in either player slot. - // - IsLive must have both users connected to the session. - let mut valid = match request.game_state { - 1 => !session.game.complete && (session.p_dawn.user.is_none() || session.p_dusk.user.is_none()), - 2 => !session.game.complete && (session.p_dawn.user.is_some() && session.p_dusk.user.is_some()), + 1 => !session.game.complete, + 2 => !session.game.complete, 3 => session.game.complete, _ => true, }; - valid &= !request.is_player || session.p_dawn.user == user_id || session.p_dusk.user == user_id; + valid &= !request.is_player || Some(session.p_dawn.user) == user_id || Some(session.p_dusk.user) == user_id; valid &= !request.is_live || (session.p_dawn.connections.len() > 0 && session.p_dusk.connections.len() > 0); if valid { let player :u8 = if user_id.is_some() { - if session.p_dawn.user == user_id { 1 } - else if session.p_dusk.user == user_id { 2 } + if Some(session.p_dawn.user) == user_id { 1 } + else if Some(session.p_dusk.user) == user_id { 2 } else { 0 } } else { 0 }; let is_turn = player != 0 && (session.game.turn & 1) == player as u16 - 1; - let dawn_handle = if let Some(uid) = session.p_dawn.user { - if let Some(user) = app.get_user_by_id(uid) { - user.handle.clone() - } else { String::new() } + let dawn_handle = if let Some(user) = app.get_user_by_id(session.p_dawn.user) { + user.handle.clone() } else { String::new() }; - let dusk_handle = if let Some(uid) = session.p_dusk.user { - if let Some(user) = app.get_user_by_id(uid) { - user.handle.clone() - } else { String::new() } + let dusk_handle = if let Some(user) = app.get_user_by_id(session.p_dusk.user) { + user.handle.clone() } else { String::new() }; response.records.push(PacketSessionListResponseRecord { @@ -355,9 +425,8 @@ pub async fn thread_system(mut app:App, bus:Bus) // Verify client is authenticated if user_id.is_some() { - // Resume session if user is player - if session.p_dawn.user == user_id || session.p_dusk.user == user_id { + if Some(session.p_dawn.user) == user_id || Some(session.p_dusk.user) == user_id { println!("User resumes session."); response.status = STATUS_OK; true @@ -376,9 +445,9 @@ pub async fn thread_system(mut app:App, bus:Bus) if let Some(conn) = app.connections.get_mut(qr.id as usize) { conn.session = Some(session.token); } - let mode = if user_id == session.p_dawn.user { + let mode = if user_id == Some(session.p_dawn.user) { 0 - } else if user_id == session.p_dusk.user { + } else if user_id == Some(session.p_dusk.user) { 1 } else { 2 @@ -404,9 +473,9 @@ pub async fn thread_system(mut app:App, bus:Bus) }; if let Some(session) = app.sessions.get_mut(&request.token) { - if !session.game.complete && (session.p_dawn.user.is_some() && session.p_dusk.user.is_some()) { - if (user_id == session.p_dawn.user && session.game.turn & 1 == 0) - || (user_id == session.p_dusk.user && session.game.turn & 1 == 1) { + if !session.game.complete { + if (user_id == Some(session.p_dawn.user) && session.game.turn & 1 == 0) + || (user_id == Some(session.p_dusk.user) && session.game.turn & 1 == 1) { session.game.process(&play).ok(); app.filesystem.session_history_push(session.id, play).ok(); @@ -439,8 +508,8 @@ pub async fn thread_system(mut app:App, bus:Bus) // Verify that session exists. if let Some(session_token) = session_id { if let Some(session) = app.sessions.get_mut(&session_token) { - if user_id.is_some() && user_id == session.p_dawn.user { session.remove_connection(0, qr.id); } - else if user_id.is_some() && user_id == session.p_dusk.user { session.remove_connection(1, qr.id); } + if user_id == Some(session.p_dawn.user) { session.remove_connection(0, qr.id); } + else if user_id == Some(session.p_dusk.user) { session.remove_connection(1, qr.id); } else { session.remove_connection(2, qr.id); } } } @@ -458,10 +527,8 @@ pub async fn thread_system(mut app:App, bus:Bus) response.status = STATUS_OK; response = generate_game_state(&app, &session); - if user_id.is_some() { - if user_id == session.p_dawn.user { response.player = 0; } - else if user_id == session.p_dusk.user { response.player = 1; } - } + if user_id == Some(session.p_dawn.user) { response.player = 0; } + else if user_id == Some(session.p_dusk.user) { response.player = 1; } } else { response.status = STATUS_ERROR; } @@ -478,9 +545,9 @@ pub async fn thread_system(mut app:App, bus:Bus) if let Some(sid) = session_id { if let Some(session) = app.sessions.get_mut(&sid) { - if !session.game.complete && (session.p_dawn.user.is_some() && session.p_dusk.user.is_some()) { - if (user_id == session.p_dawn.user && session.game.turn & 1 == 0) - || (user_id == session.p_dusk.user && session.game.turn & 1 == 1) { + if !session.game.complete { + if (user_id == Some(session.p_dawn.user) && session.game.turn & 1 == 0) + || (user_id == Some(session.p_dusk.user) && session.game.turn & 1 == 1) { // Check validation of play if request.turn == session.game.turn { @@ -499,6 +566,10 @@ pub async fn thread_system(mut app:App, bus:Bus) QRPacketData::QGamePlay(request.clone()) )); } + + // Send status to players. + send_user_status.push(session.p_dawn.user); + send_user_status.push(session.p_dusk.user); } } } @@ -525,20 +596,22 @@ pub async fn thread_system(mut app:App, bus:Bus) println!("Request: Challenge"); if let Some(user_id) = user_id { - if let Some(chal_id) = app.user_handle.get(request.handle.to_lowercase().as_bytes()) { - if let Some(chal_user) = app.get_user_by_id_mut(*chal_id) { + if let Some(chal_id) = app.user_handle.get(request.handle.to_lowercase().as_bytes()).cloned() { + if let Some(chal_user) = app.get_user_by_id_mut(chal_id) { let mut find = false; for ch in &chal_user.challenges { if *ch == user_id { find = true; } } if !find { chal_user.challenges.push(user_id); + send_user_status.push(chal_id); } else { println!("notice: duplicate challenge."); } } } } + None } @@ -585,6 +658,7 @@ pub async fn thread_system(mut app:App, bus:Bus) if rng.fill(&mut seats).is_err() { println!("RNG error"); } + println!("rng {}", seats[0]); // Build session. let mut session = Session { @@ -593,11 +667,11 @@ pub async fn thread_system(mut app:App, bus:Bus) secret, game:game::Game::new(), p_dawn:Player { - user:if (seats[0] & 8) == 0 { Some(user_id) } else { Some(chal_id) }, + user:if (seats[0] & 2) == 0 { user_id } else { chal_id }, connections:Vec::new(), }, p_dusk:Player { - user:if (seats[0] & 8) == 0 { Some(chal_id) } else { Some(user_id) }, + user:if (seats[0] & 2) == 0 { chal_id } else { user_id }, connections:Vec::new(), }, connections:Vec::new(), @@ -616,7 +690,11 @@ pub async fn thread_system(mut app:App, bus:Bus) app.session_time.set(chain_id, token); response.token = token; + + send_user_status.push(chal_id); } + + send_user_status.push(user_id); } } @@ -673,9 +751,16 @@ pub async fn thread_system(mut app:App, bus:Bus) _ => { Some(QRPacket::new(0, QRPacketData::None)) } } { - Some(response) => { app.send_response(response).await; } + Some(response) => { + app.send_response(response).await; + } None => { } } + + for user_id in &send_user_status { + app.send_status(*user_id).await; + } + send_user_status.clear(); } } @@ -689,17 +774,13 @@ fn generate_game_state(app:&App, session:&Session) -> protocol::PacketGameStateR response.player = 2; // Get Dawn handle - if let Some(id) = session.p_dawn.user { - if let Some(user) = app.get_user_by_id(id) { - response.dawn_handle = user.handle.clone(); - } + if let Some(user) = app.get_user_by_id(session.p_dawn.user) { + response.dawn_handle = user.handle.clone(); } // Get Dusk handle - if let Some(id) = session.p_dusk.user { - if let Some(user) = app.get_user_by_id(id) { - response.dusk_handle = user.handle.clone(); - } + if let Some(user) = app.get_user_by_id(session.p_dusk.user) { + response.dusk_handle = user.handle.clone(); } // Get history diff --git a/server/src/protocol/code.rs b/server/src/protocol/code.rs index d8911ff..f7b2341 100644 --- a/server/src/protocol/code.rs +++ b/server/src/protocol/code.rs @@ -27,6 +27,8 @@ pub const CODE_AUTH :u16 = 0x0011; pub const CODE_AUTH_RESUME :u16 = 0x0012; pub const CODE_AUTH_REVOKE :u16 = 0x0013; +pub const CODE_STATUS :u16 = 0x001F; + pub const CODE_SESSION_LIST :u16 = 0x0020; pub const CODE_SESSION_JOIN :u16 = 0x0021; pub const CODE_SESSION_VIEW :u16 = 0x0022; diff --git a/server/src/protocol/packet/mod.rs b/server/src/protocol/packet/mod.rs index b446c54..1092b6e 100644 --- a/server/src/protocol/packet/mod.rs +++ b/server/src/protocol/packet/mod.rs @@ -4,6 +4,8 @@ mod register; pub use register::*; mod auth; pub use auth::*; mod resume; pub use resume::*; +mod status; pub use status::*; + mod session_list; pub use session_list::*; //mod session_create; pub use session_create::*; mod session_view; pub use session_view::*; diff --git a/server/src/protocol/packet/status.rs b/server/src/protocol/packet/status.rs new file mode 100644 index 0000000..7d8f19d --- /dev/null +++ b/server/src/protocol/packet/status.rs @@ -0,0 +1,31 @@ +use crate::util::pack::pack_u16; + +use super::Packet; + +#[derive(Clone)] +pub struct PacketStatusResponse { + pub challenge:u16, + pub resume:u16, +} +impl PacketStatusResponse { + pub fn new() -> Self + { + Self { + challenge:0, + resume:0, + } + } +} +impl Packet for PacketStatusResponse { + type Data = Self; + + fn encode(&self) -> Vec + { + let mut result = Vec::new(); + + result.append(&mut pack_u16(self.challenge as u16)); + result.append(&mut pack_u16(self.resume as u16)); + + result + } +} diff --git a/server/src/system/filesystem/mod.rs b/server/src/system/filesystem/mod.rs index 8f35ad5..d68c798 100644 --- a/server/src/system/filesystem/mod.rs +++ b/server/src/system/filesystem/mod.rs @@ -140,10 +140,10 @@ impl FileSystem { // Write session information file.write(&session.token).map_err(|_| ())?; file.write(&session.secret).map_err(|_| ())?; - file.write(&pack_u8(session.p_dawn.user.is_some() as u8)).map_err(|_| ())?; - file.write(&pack_u32(session.p_dawn.user.unwrap_or(0))).map_err(|_| ())?; - file.write(&pack_u8(session.p_dusk.user.is_some() as u8)).map_err(|_| ())?; - file.write(&pack_u32(session.p_dusk.user.unwrap_or(0))).map_err(|_| ())?; + file.write(&pack_u8(0)).map_err(|_| ())?; + file.write(&pack_u32(session.p_dawn.user)).map_err(|_| ())?; + file.write(&pack_u8(0)).map_err(|_| ())?; + file.write(&pack_u32(session.p_dusk.user)).map_err(|_| ())?; file.write(&pack_u64(session.time)).map_err(|_| ())?; Ok(()) @@ -178,15 +178,11 @@ impl FileSystem { file.read_exact(&mut buffer_u8).map_err(|_| ())?; file.read_exact(&mut buffer_u32).map_err(|_| ())?; - let dawn = if unpack_u8(&buffer_u8, &mut 0) != 0 { - Some(unpack_u32(&buffer_u32, &mut 0)) - } else { None }; + let dawn = unpack_u32(&buffer_u32, &mut 0); file.read_exact(&mut buffer_u8).map_err(|_| ())?; file.read_exact(&mut buffer_u32).map_err(|_| ())?; - let dusk = if unpack_u8(&buffer_u8, &mut 0) != 0 { - Some(unpack_u32(&buffer_u32, &mut 0)) - } else { None }; + let dusk = unpack_u32(&buffer_u32, &mut 0); file.read_exact(&mut buffer_u64).map_err(|_| ())?; let time = unpack_u64(&buffer_u64, &mut 0); @@ -395,6 +391,8 @@ impl FileSystem { secret, na_key, + connection:None, + status:UserStatus::new(), challenges:Vec::new(), }) diff --git a/www/css/main.css b/www/css/main.css index bc6b389..8f4535a 100644 --- a/www/css/main.css +++ b/www/css/main.css @@ -31,7 +31,7 @@ body>nav{ display:flex; position:relative; flex-flow:column nowrap; - width:9rem; + width:10rem; height:100%; background-color:#282828; diff --git a/www/js/const.js b/www/js/const.js index a36da11..752164a 100644 --- a/www/js/const.js +++ b/www/js/const.js @@ -12,6 +12,11 @@ let CONTEXT = { Data: null, }; +let STATUS = { + challenge: 0, + resume: 0, +}; + const Status = { Ok :0x0000, Error :0x0001, @@ -30,6 +35,8 @@ const OpCode = { Resume :0x0012, Deauthenticate :0x0013, + Status :0x001F, + SessionList :0x0020, //SessionJoin :0x0021, SessionView :0x0022, diff --git a/www/js/scene.js b/www/js/scene.js index 155fff2..aaf450c 100644 --- a/www/js/scene.js +++ b/www/js/scene.js @@ -705,11 +705,14 @@ const SCENES = { records:[], }; + let button_requests = UI.button(LANG("requests"), () => { LOAD(SCENES.ChallengeList); }); + button_requests.setAttribute("id", "button-challenge-requests"); + UI.mainmenu("browse"); UI.mainnav( [ UI.button(LANG("users"), () => { LOAD(SCENES.Challenge); }), - UI.button(LANG("requests"), () => { LOAD(SCENES.ChallengeList); }), + button_requests, ], [ UI.div([UI.text(LANG_PAGEOF(0, 0, 0))]), @@ -784,11 +787,14 @@ const SCENES = { records:[], }; + let button_requests = UI.button(LANG("requests"), () => { LOAD(SCENES.ChallengeList); }); + button_requests.setAttribute("id", "button-challenge-requests"); + UI.mainmenu("browse"); UI.mainnav( [ UI.button(LANG("users"), () => { LOAD(SCENES.Challenge); }), - UI.button(LANG("requests"), () => { LOAD(SCENES.ChallengeList); }), + button_requests, ], [ UI.div([UI.text(LANG_PAGEOF(0, 0, 0))]), @@ -878,6 +884,8 @@ function LOAD(scene, data=null) { UI.rebuild(); SCENE = new scene(); if(!SCENE.load(data)) { LOAD(SCENES.Browse); } + + UI.update_status(); } function UNLOAD() { diff --git a/www/js/system.js b/www/js/system.js index d88568f..18cf4bc 100644 --- a/www/js/system.js +++ b/www/js/system.js @@ -118,6 +118,24 @@ function MESSAGE(event) { console.log("RECV Deauthenticate"); } break; + case OpCode.Status: { + console.log("RECV Status"); + + if(bytes.length - index == 4) { + // Challenge count + result = UNPACK.u16(bytes, index); + index = result.index; + STATUS.challenge = result.data; + + // Resume count + result = UNPACK.u16(bytes, index); + index = result.index; + STATUS.resume = result.data; + + UI.update_status(); + } + } break; + case OpCode.SessionList: { console.log("RECV Session list"); @@ -290,6 +308,8 @@ function MESSAGE(event) { } break; case OpCode.ChallengeAnswer: { + console.log("RECV ChallengeAnswer"); + data = { status:0, token:new Uint8Array(8), @@ -304,6 +324,8 @@ function MESSAGE(event) { } break; case OpCode.ChallengeList: { + console.log("RECV ChallengeList"); + data = { status:0, challenges:[ ], @@ -334,6 +356,8 @@ function MESSAGE(event) { } break; case OpCode.UserList: { + console.log("RECV UserList"); + data = { status:0, users:[ ], diff --git a/www/js/ui.js b/www/js/ui.js index a4cd141..f5c2cab 100644 --- a/www/js/ui.js +++ b/www/js/ui.js @@ -117,8 +117,11 @@ const UI = { } } else { if(features.session === true) { + let button_challenge = UI.button(LANG("challenge"), () => { LOAD(SCENES.Challenge); }); + button_challenge.setAttribute("id", "button-challenge"); + //left.appendChild(UI.button("Await", () => { })); - left.appendChild(UI.button(LANG("challenge"), () => { LOAD(SCENES.Challenge); })); + left.appendChild(button_challenge); } } @@ -150,7 +153,10 @@ const UI = { top.push(UI.button(LANG("browse"), () => { LOAD(SCENES.Browse); }, page == "browse")); if(sessionStorage.getItem("auth") !== null) { - top.push(UI.button(LANG("resume"), () => { LOAD(SCENES.Continue); }, page == "continue")); + let button_resume = UI.button(LANG("resume"), () => { LOAD(SCENES.Continue); }, page == "continue"); + button_resume.setAttribute("id", "button-resume"); + + top.push(button_resume); //top.push(UI.button("Join", () => { LOAD(SCENES.Join); }, page == "join")); } top.push(UI.button(LANG("live"), () => { LOAD(SCENES.Live); }, page == "live")); @@ -376,4 +382,58 @@ const UI = { document.body.appendChild(MENU); document.body.appendChild(MAIN); }, + + update_status() { + let b_challenge = document.getElementById("button-challenge"); + let b_requests = document.getElementById("button-challenge-requests"); + let b_resume = document.getElementById("button-resume"); + + if(b_challenge !== null) { + let text = LANG("challenge"); + + if(STATUS.challenge > 0) { + if(STATUS.challenge < 10) { + text += " " + String.fromCharCode("❶".charCodeAt(0) + (STATUS.challenge - 1)); + } else { + text += " ❾"; + } + } else { + text += " ⓿"; + } + + b_challenge.innerText = text; + } + + if(b_requests !== null) { + let text = LANG("requests"); + + if(STATUS.challenge > 0) { + if(STATUS.challenge < 10) { + text += " " + String.fromCharCode("❶".charCodeAt(0) + (STATUS.challenge - 1)); + } else { + text += " ❾"; + } + } else { + text += " ⓿"; + } + + b_requests.innerText = text; + } + + if(b_resume !== null) { + let text = LANG("resume"); + + if(STATUS.resume > 0) { + if(STATUS.resume < 10) { + text += " " + String.fromCharCode("❶".charCodeAt(0) + (STATUS.resume - 1)); + } else { + text += " ❾"; + } + } else { + text += " ⓿"; + } + + b_resume.innerText = text; + } + }, };