Add user status messages.
This commit is contained in:
parent
0dc3b63ea4
commit
a7684bea16
@ -12,9 +12,13 @@ use crate::app::{
|
|||||||
|
|
||||||
type StreamType = Arc<RwLock<SplitSink<WebSocketStream<TokioIo<Upgraded>>, Message>>>;
|
type StreamType = Arc<RwLock<SplitSink<WebSocketStream<TokioIo<Upgraded>>, Message>>>;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct Connection {
|
pub struct Connection {
|
||||||
pub bus:u32,
|
pub bus:u32,
|
||||||
pub stream:StreamType,
|
pub stream:StreamType,
|
||||||
pub auth:Option<AuthToken>,
|
pub auth:Option<AuthToken>,
|
||||||
pub session:Option<SessionToken>,
|
pub session:Option<SessionToken>,
|
||||||
|
|
||||||
|
pub prev:u32,
|
||||||
|
pub next:u32,
|
||||||
}
|
}
|
||||||
|
@ -4,9 +4,7 @@ use sparse::Sparse;
|
|||||||
use pool::Pool;
|
use pool::Pool;
|
||||||
use trie::Trie;
|
use trie::Trie;
|
||||||
use crate::{
|
use crate::{
|
||||||
system::filesystem::FileSystem,
|
protocol::QRPacket, system::filesystem::FileSystem, util::Chain
|
||||||
util::Chain,
|
|
||||||
protocol::QRPacket,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub mod connection; use connection::Connection;
|
pub mod connection; use connection::Connection;
|
||||||
@ -148,16 +146,8 @@ impl App {
|
|||||||
use tokio_tungstenite::tungstenite::Message;
|
use tokio_tungstenite::tungstenite::Message;
|
||||||
use futures::SinkExt;
|
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 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;
|
let mut socket = conn.stream.write().await;
|
||||||
|
|
||||||
match response.data {
|
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()
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@ pub type SessionSecret = [u8; 8];
|
|||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Player {
|
pub struct Player {
|
||||||
pub user:Option<u32>,
|
pub user:u32,
|
||||||
pub connections:Vec<u32>,
|
pub connections:Vec<u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
pub const FC_ENABLE :u32 = 0x0000_0001;
|
pub const FC_ENABLE :u32 = 0x0000_0001;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
pub struct UserStatus {
|
pub struct UserStatus {
|
||||||
pub await_flags:u32,
|
pub await_flags:u32,
|
||||||
}
|
}
|
||||||
@ -12,12 +13,15 @@ impl UserStatus {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct User {
|
pub struct User {
|
||||||
pub id:u32,
|
pub id:u32,
|
||||||
pub flags:u32,
|
pub flags:u32,
|
||||||
pub handle:String,
|
pub handle:String,
|
||||||
pub secret:Vec<u8>,
|
pub secret:Vec<u8>,
|
||||||
pub na_key:u32,
|
pub na_key:u32,
|
||||||
|
|
||||||
|
pub connection:Option<u32>,
|
||||||
|
|
||||||
pub status:UserStatus,
|
pub status:UserStatus,
|
||||||
pub challenges:Vec<u32>,
|
pub challenges:Vec<u32>,
|
||||||
|
@ -19,6 +19,8 @@ pub async fn thread_system(mut app:App, bus:Bus<protocol::QRPacket>)
|
|||||||
let rng = SystemRandom::new();
|
let rng = SystemRandom::new();
|
||||||
let argon_config = argon2::Config::default();
|
let argon_config = argon2::Config::default();
|
||||||
|
|
||||||
|
let mut send_user_status = Vec::<u32>::new();
|
||||||
|
|
||||||
while let Some(packet) = bus.receive_wait() {
|
while let Some(packet) = bus.receive_wait() {
|
||||||
let qr = packet.data;
|
let qr = packet.data;
|
||||||
|
|
||||||
@ -41,7 +43,14 @@ pub async fn thread_system(mut app:App, bus:Bus<protocol::QRPacket>)
|
|||||||
stream: request.stream,
|
stream: request.stream,
|
||||||
auth: None,
|
auth: None,
|
||||||
session: 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);
|
println!("Connect: {}", id);
|
||||||
|
|
||||||
@ -54,16 +63,46 @@ pub async fn thread_system(mut app:App, bus:Bus<protocol::QRPacket>)
|
|||||||
|
|
||||||
QRPacketData::QDisconn => {
|
QRPacketData::QDisconn => {
|
||||||
// Uninitialize connection
|
// 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
|
// Disassociate session if present
|
||||||
if let Some(session_token) = conn.session {
|
if let Some(session_token) = conn.session {
|
||||||
if let Some(session) = app.sessions.get_mut(&session_token) {
|
if let Some(session) = app.sessions.get_mut(&session_token) {
|
||||||
if user_id == session.p_dawn.user { session.remove_connection(0, qr.id); }
|
if user_id == Some(session.p_dawn.user) { session.remove_connection(0, qr.id); }
|
||||||
else if user_id == session.p_dusk.user { session.remove_connection(1, qr.id); }
|
else if user_id == Some(session.p_dusk.user) { session.remove_connection(1, qr.id); }
|
||||||
else { session.remove_connection(2, 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
|
// Close socket
|
||||||
let mut socket = conn.stream.write().await;
|
let mut socket = conn.stream.write().await;
|
||||||
@ -113,6 +152,8 @@ pub async fn thread_system(mut app:App, bus:Bus<protocol::QRPacket>)
|
|||||||
handle:display_name,
|
handle:display_name,
|
||||||
secret,
|
secret,
|
||||||
na_key:salt_id,
|
na_key:salt_id,
|
||||||
|
|
||||||
|
connection:Some(qr.id),
|
||||||
|
|
||||||
status:UserStatus::new(),
|
status:UserStatus::new(),
|
||||||
challenges:Vec::new(),
|
challenges:Vec::new(),
|
||||||
@ -175,16 +216,16 @@ pub async fn thread_system(mut app:App, bus:Bus<protocol::QRPacket>)
|
|||||||
if is_valid {
|
if is_valid {
|
||||||
|
|
||||||
// Get user data from handle
|
// 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) => {
|
Some(uid) => {
|
||||||
if let Some(tuid) = app.user_id.get(*uid as isize) {
|
if let Some(tuid) = app.user_id.get(uid as isize).cloned() {
|
||||||
if let Some(user) = app.users.get(*tuid) {
|
if let Some(user) = app.users.get(tuid).cloned() {
|
||||||
// Get user salt
|
// 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
|
// Verify salted secret against user data
|
||||||
if argon2::verify_raw(&request.secret.as_bytes(), salt, &user.secret, &argon_config).unwrap_or(false) {
|
if argon2::verify_raw(&request.secret.as_bytes(), &salt, &user.secret, &argon_config).unwrap_or(false) {
|
||||||
println!("Authenticated user '{}' id {}", user.handle, *uid);
|
println!("Authenticated user '{}' id {}", user.handle, uid);
|
||||||
|
|
||||||
// Generate authentication token and secret
|
// Generate authentication token and secret
|
||||||
response.status = STATUS_OK;
|
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 {
|
app.auths.set(&response.token, Authentication {
|
||||||
key:response.token,
|
key:response.token,
|
||||||
secret:response.secret,
|
secret:response.secret,
|
||||||
user:*uid,
|
user:uid,
|
||||||
});
|
});
|
||||||
break;
|
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) {
|
if let Some(conn) = app.connections.get_mut(qr.id as usize) {
|
||||||
conn.auth = Some(response.token);
|
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 {
|
} else {
|
||||||
println!("notice: password verification failed.");
|
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();
|
let mut response = PacketAuthResumeResponse::new();
|
||||||
response.status = STATUS_ERROR;
|
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.
|
// Compare full secret length to reduce time-based attacks.
|
||||||
let mut valid = true;
|
let mut valid = true;
|
||||||
@ -241,11 +301,31 @@ pub async fn thread_system(mut app:App, bus:Bus<protocol::QRPacket>)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if valid {
|
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);
|
conn.auth = Some(request.token);
|
||||||
response.status = STATUS_OK;
|
response.status = STATUS_OK;
|
||||||
|
true
|
||||||
} else {
|
} else {
|
||||||
response.status = STATUS_SERVER_ERROR;
|
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();
|
let token = app.session_time.get(id).unwrap();
|
||||||
if let Some(session) = app.sessions.get(token) {
|
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 {
|
let mut valid = match request.game_state {
|
||||||
1 => !session.game.complete && (session.p_dawn.user.is_none() || session.p_dusk.user.is_none()),
|
1 => !session.game.complete,
|
||||||
2 => !session.game.complete && (session.p_dawn.user.is_some() && session.p_dusk.user.is_some()),
|
2 => !session.game.complete,
|
||||||
3 => session.game.complete,
|
3 => session.game.complete,
|
||||||
_ => true,
|
_ => 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);
|
valid &= !request.is_live || (session.p_dawn.connections.len() > 0 && session.p_dusk.connections.len() > 0);
|
||||||
|
|
||||||
if valid {
|
if valid {
|
||||||
let player :u8 = if user_id.is_some() {
|
let player :u8 = if user_id.is_some() {
|
||||||
if session.p_dawn.user == user_id { 1 }
|
if Some(session.p_dawn.user) == user_id { 1 }
|
||||||
else if session.p_dusk.user == user_id { 2 }
|
else if Some(session.p_dusk.user) == user_id { 2 }
|
||||||
else { 0 }
|
else { 0 }
|
||||||
} else { 0 };
|
} else { 0 };
|
||||||
let is_turn = player != 0 && (session.game.turn & 1) == player as u16 - 1;
|
let is_turn = player != 0 && (session.game.turn & 1) == player as u16 - 1;
|
||||||
|
|
||||||
let dawn_handle = if let Some(uid) = session.p_dawn.user {
|
let dawn_handle = if let Some(user) = app.get_user_by_id(session.p_dawn.user) {
|
||||||
if let Some(user) = app.get_user_by_id(uid) {
|
user.handle.clone()
|
||||||
user.handle.clone()
|
|
||||||
} else { String::new() }
|
|
||||||
} else { String::new() };
|
} else { String::new() };
|
||||||
|
|
||||||
let dusk_handle = if let Some(uid) = session.p_dusk.user {
|
let dusk_handle = if let Some(user) = app.get_user_by_id(session.p_dusk.user) {
|
||||||
if let Some(user) = app.get_user_by_id(uid) {
|
user.handle.clone()
|
||||||
user.handle.clone()
|
|
||||||
} else { String::new() }
|
|
||||||
} else { String::new() };
|
} else { String::new() };
|
||||||
|
|
||||||
response.records.push(PacketSessionListResponseRecord {
|
response.records.push(PacketSessionListResponseRecord {
|
||||||
@ -355,9 +425,8 @@ pub async fn thread_system(mut app:App, bus:Bus<protocol::QRPacket>)
|
|||||||
|
|
||||||
// Verify client is authenticated
|
// Verify client is authenticated
|
||||||
if user_id.is_some() {
|
if user_id.is_some() {
|
||||||
|
|
||||||
// Resume session if user is player
|
// 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.");
|
println!("User resumes session.");
|
||||||
response.status = STATUS_OK;
|
response.status = STATUS_OK;
|
||||||
true
|
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) {
|
if let Some(conn) = app.connections.get_mut(qr.id as usize) {
|
||||||
conn.session = Some(session.token);
|
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
|
0
|
||||||
} else if user_id == session.p_dusk.user {
|
} else if user_id == Some(session.p_dusk.user) {
|
||||||
1
|
1
|
||||||
} else {
|
} else {
|
||||||
2
|
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 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 !session.game.complete {
|
||||||
if (user_id == session.p_dawn.user && session.game.turn & 1 == 0)
|
if (user_id == Some(session.p_dawn.user) && session.game.turn & 1 == 0)
|
||||||
|| (user_id == session.p_dusk.user && session.game.turn & 1 == 1) {
|
|| (user_id == Some(session.p_dusk.user) && session.game.turn & 1 == 1) {
|
||||||
|
|
||||||
session.game.process(&play).ok();
|
session.game.process(&play).ok();
|
||||||
app.filesystem.session_history_push(session.id, 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.
|
// Verify that session exists.
|
||||||
if let Some(session_token) = session_id {
|
if let Some(session_token) = session_id {
|
||||||
if let Some(session) = app.sessions.get_mut(&session_token) {
|
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); }
|
if user_id == Some(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); }
|
else if user_id == Some(session.p_dusk.user) { session.remove_connection(1, qr.id); }
|
||||||
else { session.remove_connection(2, 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.status = STATUS_OK;
|
||||||
response = generate_game_state(&app, &session);
|
response = generate_game_state(&app, &session);
|
||||||
|
|
||||||
if user_id.is_some() {
|
if user_id == Some(session.p_dawn.user) { response.player = 0; }
|
||||||
if user_id == session.p_dawn.user { response.player = 0; }
|
else if user_id == Some(session.p_dusk.user) { response.player = 1; }
|
||||||
else if user_id == session.p_dusk.user { response.player = 1; }
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
response.status = STATUS_ERROR;
|
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(sid) = session_id {
|
||||||
if let Some(session) = app.sessions.get_mut(&sid) {
|
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 !session.game.complete {
|
||||||
if (user_id == session.p_dawn.user && session.game.turn & 1 == 0)
|
if (user_id == Some(session.p_dawn.user) && session.game.turn & 1 == 0)
|
||||||
|| (user_id == session.p_dusk.user && session.game.turn & 1 == 1) {
|
|| (user_id == Some(session.p_dusk.user) && session.game.turn & 1 == 1) {
|
||||||
|
|
||||||
// Check validation of play
|
// Check validation of play
|
||||||
if request.turn == session.game.turn {
|
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())
|
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");
|
println!("Request: Challenge");
|
||||||
|
|
||||||
if let Some(user_id) = user_id {
|
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_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) {
|
if let Some(chal_user) = app.get_user_by_id_mut(chal_id) {
|
||||||
let mut find = false;
|
let mut find = false;
|
||||||
for ch in &chal_user.challenges {
|
for ch in &chal_user.challenges {
|
||||||
if *ch == user_id { find = true; }
|
if *ch == user_id { find = true; }
|
||||||
}
|
}
|
||||||
if !find {
|
if !find {
|
||||||
chal_user.challenges.push(user_id);
|
chal_user.challenges.push(user_id);
|
||||||
|
send_user_status.push(chal_id);
|
||||||
} else {
|
} else {
|
||||||
println!("notice: duplicate challenge.");
|
println!("notice: duplicate challenge.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -585,6 +658,7 @@ pub async fn thread_system(mut app:App, bus:Bus<protocol::QRPacket>)
|
|||||||
if rng.fill(&mut seats).is_err() {
|
if rng.fill(&mut seats).is_err() {
|
||||||
println!("RNG error");
|
println!("RNG error");
|
||||||
}
|
}
|
||||||
|
println!("rng {}", seats[0]);
|
||||||
|
|
||||||
// Build session.
|
// Build session.
|
||||||
let mut session = Session {
|
let mut session = Session {
|
||||||
@ -593,11 +667,11 @@ pub async fn thread_system(mut app:App, bus:Bus<protocol::QRPacket>)
|
|||||||
secret,
|
secret,
|
||||||
game:game::Game::new(),
|
game:game::Game::new(),
|
||||||
p_dawn:Player {
|
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(),
|
connections:Vec::new(),
|
||||||
},
|
},
|
||||||
p_dusk:Player {
|
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(),
|
||||||
},
|
},
|
||||||
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);
|
app.session_time.set(chain_id, token);
|
||||||
|
|
||||||
response.token = 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(QRPacket::new(0, QRPacketData::None)) }
|
||||||
} {
|
} {
|
||||||
Some(response) => { app.send_response(response).await; }
|
Some(response) => {
|
||||||
|
app.send_response(response).await;
|
||||||
|
}
|
||||||
None => { }
|
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;
|
response.player = 2;
|
||||||
|
|
||||||
// Get Dawn handle
|
// Get Dawn handle
|
||||||
if let Some(id) = session.p_dawn.user {
|
if let Some(user) = app.get_user_by_id(session.p_dawn.user) {
|
||||||
if let Some(user) = app.get_user_by_id(id) {
|
response.dawn_handle = user.handle.clone();
|
||||||
response.dawn_handle = user.handle.clone();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get Dusk handle
|
// Get Dusk handle
|
||||||
if let Some(id) = session.p_dusk.user {
|
if let Some(user) = app.get_user_by_id(session.p_dusk.user) {
|
||||||
if let Some(user) = app.get_user_by_id(id) {
|
response.dusk_handle = user.handle.clone();
|
||||||
response.dusk_handle = user.handle.clone();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get history
|
// Get history
|
||||||
|
@ -27,6 +27,8 @@ pub const CODE_AUTH :u16 = 0x0011;
|
|||||||
pub const CODE_AUTH_RESUME :u16 = 0x0012;
|
pub const CODE_AUTH_RESUME :u16 = 0x0012;
|
||||||
pub const CODE_AUTH_REVOKE :u16 = 0x0013;
|
pub const CODE_AUTH_REVOKE :u16 = 0x0013;
|
||||||
|
|
||||||
|
pub const CODE_STATUS :u16 = 0x001F;
|
||||||
|
|
||||||
pub const CODE_SESSION_LIST :u16 = 0x0020;
|
pub const CODE_SESSION_LIST :u16 = 0x0020;
|
||||||
pub const CODE_SESSION_JOIN :u16 = 0x0021;
|
pub const CODE_SESSION_JOIN :u16 = 0x0021;
|
||||||
pub const CODE_SESSION_VIEW :u16 = 0x0022;
|
pub const CODE_SESSION_VIEW :u16 = 0x0022;
|
||||||
|
@ -4,6 +4,8 @@ mod register; pub use register::*;
|
|||||||
mod auth; pub use auth::*;
|
mod auth; pub use auth::*;
|
||||||
mod resume; pub use resume::*;
|
mod resume; pub use resume::*;
|
||||||
|
|
||||||
|
mod status; pub use status::*;
|
||||||
|
|
||||||
mod session_list; pub use session_list::*;
|
mod session_list; pub use session_list::*;
|
||||||
//mod session_create; pub use session_create::*;
|
//mod session_create; pub use session_create::*;
|
||||||
mod session_view; pub use session_view::*;
|
mod session_view; pub use session_view::*;
|
||||||
|
31
server/src/protocol/packet/status.rs
Normal file
31
server/src/protocol/packet/status.rs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
@ -140,10 +140,10 @@ impl FileSystem {
|
|||||||
// Write session information
|
// Write session information
|
||||||
file.write(&session.token).map_err(|_| ())?;
|
file.write(&session.token).map_err(|_| ())?;
|
||||||
file.write(&session.secret).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_u8(0)).map_err(|_| ())?;
|
||||||
file.write(&pack_u32(session.p_dawn.user.unwrap_or(0))).map_err(|_| ())?;
|
file.write(&pack_u32(session.p_dawn.user)).map_err(|_| ())?;
|
||||||
file.write(&pack_u8(session.p_dusk.user.is_some() as u8)).map_err(|_| ())?;
|
file.write(&pack_u8(0)).map_err(|_| ())?;
|
||||||
file.write(&pack_u32(session.p_dusk.user.unwrap_or(0))).map_err(|_| ())?;
|
file.write(&pack_u32(session.p_dusk.user)).map_err(|_| ())?;
|
||||||
file.write(&pack_u64(session.time)).map_err(|_| ())?;
|
file.write(&pack_u64(session.time)).map_err(|_| ())?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -178,15 +178,11 @@ impl FileSystem {
|
|||||||
|
|
||||||
file.read_exact(&mut buffer_u8).map_err(|_| ())?;
|
file.read_exact(&mut buffer_u8).map_err(|_| ())?;
|
||||||
file.read_exact(&mut buffer_u32).map_err(|_| ())?;
|
file.read_exact(&mut buffer_u32).map_err(|_| ())?;
|
||||||
let dawn = if unpack_u8(&buffer_u8, &mut 0) != 0 {
|
let dawn = unpack_u32(&buffer_u32, &mut 0);
|
||||||
Some(unpack_u32(&buffer_u32, &mut 0))
|
|
||||||
} else { None };
|
|
||||||
|
|
||||||
file.read_exact(&mut buffer_u8).map_err(|_| ())?;
|
file.read_exact(&mut buffer_u8).map_err(|_| ())?;
|
||||||
file.read_exact(&mut buffer_u32).map_err(|_| ())?;
|
file.read_exact(&mut buffer_u32).map_err(|_| ())?;
|
||||||
let dusk = if unpack_u8(&buffer_u8, &mut 0) != 0 {
|
let dusk = unpack_u32(&buffer_u32, &mut 0);
|
||||||
Some(unpack_u32(&buffer_u32, &mut 0))
|
|
||||||
} else { None };
|
|
||||||
|
|
||||||
file.read_exact(&mut buffer_u64).map_err(|_| ())?;
|
file.read_exact(&mut buffer_u64).map_err(|_| ())?;
|
||||||
let time = unpack_u64(&buffer_u64, &mut 0);
|
let time = unpack_u64(&buffer_u64, &mut 0);
|
||||||
@ -395,6 +391,8 @@ impl FileSystem {
|
|||||||
secret,
|
secret,
|
||||||
na_key,
|
na_key,
|
||||||
|
|
||||||
|
connection:None,
|
||||||
|
|
||||||
status:UserStatus::new(),
|
status:UserStatus::new(),
|
||||||
challenges:Vec::new(),
|
challenges:Vec::new(),
|
||||||
})
|
})
|
||||||
|
@ -31,7 +31,7 @@ body>nav{
|
|||||||
display:flex;
|
display:flex;
|
||||||
position:relative;
|
position:relative;
|
||||||
flex-flow:column nowrap;
|
flex-flow:column nowrap;
|
||||||
width:9rem;
|
width:10rem;
|
||||||
height:100%;
|
height:100%;
|
||||||
|
|
||||||
background-color:#282828;
|
background-color:#282828;
|
||||||
|
@ -12,6 +12,11 @@ let CONTEXT = {
|
|||||||
Data: null,
|
Data: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let STATUS = {
|
||||||
|
challenge: 0,
|
||||||
|
resume: 0,
|
||||||
|
};
|
||||||
|
|
||||||
const Status = {
|
const Status = {
|
||||||
Ok :0x0000,
|
Ok :0x0000,
|
||||||
Error :0x0001,
|
Error :0x0001,
|
||||||
@ -30,6 +35,8 @@ const OpCode = {
|
|||||||
Resume :0x0012,
|
Resume :0x0012,
|
||||||
Deauthenticate :0x0013,
|
Deauthenticate :0x0013,
|
||||||
|
|
||||||
|
Status :0x001F,
|
||||||
|
|
||||||
SessionList :0x0020,
|
SessionList :0x0020,
|
||||||
//SessionJoin :0x0021,
|
//SessionJoin :0x0021,
|
||||||
SessionView :0x0022,
|
SessionView :0x0022,
|
||||||
|
@ -705,11 +705,14 @@ const SCENES = {
|
|||||||
records:[],
|
records:[],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let button_requests = UI.button(LANG("requests"), () => { LOAD(SCENES.ChallengeList); });
|
||||||
|
button_requests.setAttribute("id", "button-challenge-requests");
|
||||||
|
|
||||||
UI.mainmenu("browse");
|
UI.mainmenu("browse");
|
||||||
UI.mainnav(
|
UI.mainnav(
|
||||||
[
|
[
|
||||||
UI.button(LANG("users"), () => { LOAD(SCENES.Challenge); }),
|
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))]),
|
UI.div([UI.text(LANG_PAGEOF(0, 0, 0))]),
|
||||||
@ -784,11 +787,14 @@ const SCENES = {
|
|||||||
records:[],
|
records:[],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let button_requests = UI.button(LANG("requests"), () => { LOAD(SCENES.ChallengeList); });
|
||||||
|
button_requests.setAttribute("id", "button-challenge-requests");
|
||||||
|
|
||||||
UI.mainmenu("browse");
|
UI.mainmenu("browse");
|
||||||
UI.mainnav(
|
UI.mainnav(
|
||||||
[
|
[
|
||||||
UI.button(LANG("users"), () => { LOAD(SCENES.Challenge); }),
|
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))]),
|
UI.div([UI.text(LANG_PAGEOF(0, 0, 0))]),
|
||||||
@ -878,6 +884,8 @@ function LOAD(scene, data=null) {
|
|||||||
UI.rebuild();
|
UI.rebuild();
|
||||||
SCENE = new scene();
|
SCENE = new scene();
|
||||||
if(!SCENE.load(data)) { LOAD(SCENES.Browse); }
|
if(!SCENE.load(data)) { LOAD(SCENES.Browse); }
|
||||||
|
|
||||||
|
UI.update_status();
|
||||||
}
|
}
|
||||||
|
|
||||||
function UNLOAD() {
|
function UNLOAD() {
|
||||||
|
@ -118,6 +118,24 @@ function MESSAGE(event) {
|
|||||||
console.log("RECV Deauthenticate");
|
console.log("RECV Deauthenticate");
|
||||||
} break;
|
} 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: {
|
case OpCode.SessionList: {
|
||||||
console.log("RECV Session list");
|
console.log("RECV Session list");
|
||||||
|
|
||||||
@ -290,6 +308,8 @@ function MESSAGE(event) {
|
|||||||
} break;
|
} break;
|
||||||
|
|
||||||
case OpCode.ChallengeAnswer: {
|
case OpCode.ChallengeAnswer: {
|
||||||
|
console.log("RECV ChallengeAnswer");
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
status:0,
|
status:0,
|
||||||
token:new Uint8Array(8),
|
token:new Uint8Array(8),
|
||||||
@ -304,6 +324,8 @@ function MESSAGE(event) {
|
|||||||
} break;
|
} break;
|
||||||
|
|
||||||
case OpCode.ChallengeList: {
|
case OpCode.ChallengeList: {
|
||||||
|
console.log("RECV ChallengeList");
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
status:0,
|
status:0,
|
||||||
challenges:[ ],
|
challenges:[ ],
|
||||||
@ -334,6 +356,8 @@ function MESSAGE(event) {
|
|||||||
} break;
|
} break;
|
||||||
|
|
||||||
case OpCode.UserList: {
|
case OpCode.UserList: {
|
||||||
|
console.log("RECV UserList");
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
status:0,
|
status:0,
|
||||||
users:[ ],
|
users:[ ],
|
||||||
|
64
www/js/ui.js
64
www/js/ui.js
@ -117,8 +117,11 @@ const UI = {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if(features.session === true) {
|
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("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"));
|
top.push(UI.button(LANG("browse"), () => { LOAD(SCENES.Browse); }, page == "browse"));
|
||||||
if(sessionStorage.getItem("auth") !== null) {
|
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("Join", () => { LOAD(SCENES.Join); }, page == "join"));
|
||||||
}
|
}
|
||||||
top.push(UI.button(LANG("live"), () => { LOAD(SCENES.Live); }, page == "live"));
|
top.push(UI.button(LANG("live"), () => { LOAD(SCENES.Live); }, page == "live"));
|
||||||
@ -376,4 +382,58 @@ const UI = {
|
|||||||
document.body.appendChild(MENU);
|
document.body.appendChild(MENU);
|
||||||
document.body.appendChild(MAIN);
|
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;
|
||||||
|
}
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user