Add user status messages.

This commit is contained in:
yukirij 2024-09-04 15:34:54 -07:00
parent 0dc3b63ea4
commit a7684bea16
14 changed files with 357 additions and 89 deletions

View File

@ -12,9 +12,13 @@ use crate::app::{
type StreamType = Arc<RwLock<SplitSink<WebSocketStream<TokioIo<Upgraded>>, Message>>>;
#[derive(Clone)]
pub struct Connection {
pub bus:u32,
pub stream:StreamType,
pub auth:Option<AuthToken>,
pub session:Option<SessionToken>,
pub prev:u32,
pub next:u32,
}

View File

@ -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<u8>) -> Vec<u8>
{
[
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<u8>) -> Vec<u8>
{
[
crate::util::pack::pack_u16(code),
data,
].concat()
}

View File

@ -5,7 +5,7 @@ pub type SessionSecret = [u8; 8];
#[derive(Clone)]
pub struct Player {
pub user:Option<u32>,
pub user:u32,
pub connections:Vec<u32>,
}

View File

@ -1,5 +1,6 @@
pub const FC_ENABLE :u32 = 0x0000_0001;
#[derive(Clone, Copy)]
pub struct UserStatus {
pub await_flags:u32,
}
@ -12,6 +13,7 @@ impl UserStatus {
}
}
#[derive(Clone)]
pub struct User {
pub id:u32,
pub flags:u32,
@ -19,6 +21,8 @@ pub struct User {
pub secret:Vec<u8>,
pub na_key:u32,
pub connection:Option<u32>,
pub status:UserStatus,
pub challenges:Vec<u32>,
}

View File

@ -19,6 +19,8 @@ pub async fn thread_system(mut app:App, bus:Bus<protocol::QRPacket>)
let rng = SystemRandom::new();
let argon_config = argon2::Config::default();
let mut send_user_status = Vec::<u32>::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<protocol::QRPacket>)
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,17 +63,47 @@ pub async fn thread_system(mut app:App, bus:Bus<protocol::QRPacket>)
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;
socket.close().await.ok();
@ -114,6 +153,8 @@ pub async fn thread_system(mut app:App, bus:Bus<protocol::QRPacket>)
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<protocol::QRPacket>)
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<protocol::QRPacket>)
app.auths.set(&response.token, Authentication {
key:response.token,
secret:response.secret,
user:*uid,
user:uid,
});
break;
}
}
// Attach authentication to connection
// Mark send status.
send_user_status.push(uid);
// 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<protocol::QRPacket>)
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<protocol::QRPacket>)
}
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<protocol::QRPacket>)
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) {
let dawn_handle = if let Some(user) = app.get_user_by_id(session.p_dawn.user) {
user.handle.clone()
} else { String::new() }
} else { String::new() };
let dusk_handle = if let Some(uid) = session.p_dusk.user {
if let Some(user) = app.get_user_by_id(uid) {
let dusk_handle = if let Some(user) = app.get_user_by_id(session.p_dusk.user) {
user.handle.clone()
} else { String::new() }
} else { String::new() };
response.records.push(PacketSessionListResponseRecord {
@ -355,9 +425,8 @@ pub async fn thread_system(mut app:App, bus:Bus<protocol::QRPacket>)
// 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<protocol::QRPacket>)
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<protocol::QRPacket>)
};
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<protocol::QRPacket>)
// 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<protocol::QRPacket>)
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<protocol::QRPacket>)
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<protocol::QRPacket>)
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<protocol::QRPacket>)
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<protocol::QRPacket>)
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<protocol::QRPacket>)
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<protocol::QRPacket>)
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<protocol::QRPacket>)
_ => { 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,18 +774,14 @@ 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) {
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) {
if let Some(user) = app.get_user_by_id(session.p_dusk.user) {
response.dusk_handle = user.handle.clone();
}
}
// Get history
response.history = session.game.history.clone();

View File

@ -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;

View File

@ -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::*;

View File

@ -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<u8>
{
let mut result = Vec::new();
result.append(&mut pack_u16(self.challenge as u16));
result.append(&mut pack_u16(self.resume as u16));
result
}
}

View File

@ -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(),
})

View File

@ -31,7 +31,7 @@ body>nav{
display:flex;
position:relative;
flex-flow:column nowrap;
width:9rem;
width:10rem;
height:100%;
background-color:#282828;

View File

@ -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,

View File

@ -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() {

View File

@ -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:[ ],

View File

@ -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;
}
},
};